ClassicWindowTheme.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. /*
  2. * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2021-2022, Filiph Sandström <filiph.sandstrom@filfatstudios.com>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <LibGfx/Bitmap.h>
  8. #include <LibGfx/ClassicWindowTheme.h>
  9. #include <LibGfx/Font/FontDatabase.h>
  10. #include <LibGfx/Painter.h>
  11. #include <LibGfx/Palette.h>
  12. #include <LibGfx/StylePainter.h>
  13. namespace Gfx {
  14. static constexpr int menubar_height = 20;
  15. Gfx::IntRect ClassicWindowTheme::titlebar_icon_rect(WindowType window_type, IntRect const& window_rect, Palette const& palette) const
  16. {
  17. if (window_type == WindowType::ToolWindow)
  18. return {};
  19. auto titlebar_rect = this->titlebar_rect(window_type, window_rect, palette);
  20. Gfx::IntRect icon_rect {
  21. titlebar_rect.x() + 2,
  22. titlebar_rect.y(),
  23. 16,
  24. 16,
  25. };
  26. icon_rect.center_vertically_within(titlebar_rect);
  27. icon_rect.translate_by(0, 1);
  28. return icon_rect;
  29. }
  30. Gfx::IntRect ClassicWindowTheme::titlebar_text_rect(WindowType window_type, IntRect const& window_rect, Palette const& palette) const
  31. {
  32. auto titlebar_rect = this->titlebar_rect(window_type, window_rect, palette);
  33. auto titlebar_icon_rect = this->titlebar_icon_rect(window_type, window_rect, palette);
  34. return {
  35. titlebar_rect.x() + 3 + (titlebar_icon_rect.is_empty() ? 0 : (titlebar_icon_rect.width() + 2)),
  36. titlebar_rect.y(),
  37. titlebar_rect.width() - 5 - (titlebar_icon_rect.is_empty() ? 0 : (titlebar_icon_rect.width() + 2)),
  38. titlebar_rect.height()
  39. };
  40. }
  41. void ClassicWindowTheme::paint_normal_frame(Painter& painter, WindowState window_state, IntRect const& window_rect, StringView window_title, Bitmap const& icon, Palette const& palette, IntRect const& leftmost_button_rect, int menu_row_count, [[maybe_unused]] bool window_modified) const
  42. {
  43. auto frame_rect = frame_rect_for_window(WindowType::Normal, window_rect, palette, menu_row_count);
  44. frame_rect.set_location({ 0, 0 });
  45. Gfx::StylePainter::paint_window_frame(painter, frame_rect, palette);
  46. auto& title_font = FontDatabase::window_title_font();
  47. auto titlebar_rect = this->titlebar_rect(WindowType::Normal, window_rect, palette);
  48. auto titlebar_icon_rect = this->titlebar_icon_rect(WindowType::Normal, window_rect, palette);
  49. auto titlebar_inner_rect = titlebar_text_rect(WindowType::Normal, window_rect, palette);
  50. auto titlebar_title_rect = titlebar_inner_rect;
  51. titlebar_title_rect.set_width(title_font.width(window_title));
  52. auto [title_color, border_color, border_color2, stripes_color, shadow_color] = compute_frame_colors(window_state, palette);
  53. painter.draw_line(titlebar_rect.bottom_left().translated(0, 1), titlebar_rect.bottom_right().translated(0, 1), palette.button());
  54. painter.draw_line(titlebar_rect.bottom_left().translated(0, 2), titlebar_rect.bottom_right().translated(0, 2), palette.button());
  55. painter.fill_rect_with_gradient(titlebar_rect, border_color, border_color2);
  56. auto title_alignment = palette.title_alignment();
  57. int stripe_right = leftmost_button_rect.left() - 3;
  58. if (stripes_color.alpha() > 0) {
  59. switch (title_alignment) {
  60. case Gfx::TextAlignment::CenterLeft: {
  61. int stripe_left = titlebar_title_rect.right() + 5;
  62. if (stripe_left && stripe_right && stripe_left < stripe_right) {
  63. for (int i = 2; i <= titlebar_inner_rect.height() - 2; i += 2) {
  64. painter.draw_line({ stripe_left, titlebar_inner_rect.y() + i }, { stripe_right, titlebar_inner_rect.y() + i }, stripes_color);
  65. }
  66. }
  67. break;
  68. }
  69. case Gfx::TextAlignment::CenterRight: {
  70. for (int i = 2; i <= titlebar_inner_rect.height() - 2; i += 2) {
  71. painter.draw_line({ titlebar_inner_rect.left(), titlebar_inner_rect.y() + i }, { stripe_right - titlebar_title_rect.width() - 3, titlebar_inner_rect.y() + i }, stripes_color);
  72. }
  73. break;
  74. }
  75. case Gfx::TextAlignment::Center: {
  76. auto stripe_width = (leftmost_button_rect.left() / 2 - titlebar_title_rect.width() / 2) - titlebar_icon_rect.width() - 3;
  77. for (int i = 2; i <= titlebar_inner_rect.height() - 2; i += 2) {
  78. painter.draw_line({ titlebar_inner_rect.left(), titlebar_inner_rect.y() + i }, { titlebar_inner_rect.left() + stripe_width, titlebar_inner_rect.y() + i }, stripes_color);
  79. painter.draw_line({ stripe_right - stripe_width, titlebar_inner_rect.y() + i }, { stripe_right, titlebar_inner_rect.y() + i }, stripes_color);
  80. }
  81. break;
  82. }
  83. default:
  84. dbgln("Unhandled title alignment!");
  85. }
  86. }
  87. auto clipped_title_rect = titlebar_title_rect;
  88. clipped_title_rect.set_width(stripe_right - clipped_title_rect.x());
  89. if (!clipped_title_rect.is_empty()) {
  90. painter.draw_text(clipped_title_rect.translated(1, 2), window_title, title_font, title_alignment, shadow_color, Gfx::TextElision::Right);
  91. // FIXME: The translated(0, 1) wouldn't be necessary if we could center text based on its baseline.
  92. painter.draw_text(clipped_title_rect.translated(0, 1), window_title, title_font, title_alignment, title_color, Gfx::TextElision::Right);
  93. }
  94. painter.draw_scaled_bitmap(titlebar_icon_rect, icon, icon.rect());
  95. }
  96. void ClassicWindowTheme::paint_tool_window_frame(Painter& painter, WindowState window_state, IntRect const& window_rect, StringView title_text, Palette const& palette, IntRect const& leftmost_button_rect) const
  97. {
  98. auto frame_rect = frame_rect_for_window(WindowType::ToolWindow, window_rect, palette, 0);
  99. frame_rect.set_location({ 0, 0 });
  100. Gfx::StylePainter::paint_window_frame(painter, frame_rect, palette);
  101. auto& title_font = FontDatabase::window_title_font();
  102. auto titlebar_rect = this->titlebar_rect(WindowType::ToolWindow, window_rect, palette);
  103. auto titlebar_inner_rect = titlebar_text_rect(WindowType::ToolWindow, window_rect, palette);
  104. auto titlebar_title_rect = titlebar_inner_rect;
  105. titlebar_title_rect.set_width(title_font.width(title_text));
  106. auto [title_color, border_color, border_color2, stripes_color, shadow_color] = compute_frame_colors(window_state, palette);
  107. painter.draw_line(titlebar_rect.bottom_left().translated(0, 1), titlebar_rect.bottom_right().translated(0, 1), palette.button());
  108. painter.draw_line(titlebar_rect.bottom_left().translated(0, 2), titlebar_rect.bottom_right().translated(0, 2), palette.button());
  109. painter.fill_rect_with_gradient(titlebar_rect, border_color, border_color2);
  110. int stripe_right = leftmost_button_rect.left() - 3;
  111. auto clipped_title_rect = titlebar_title_rect;
  112. clipped_title_rect.set_width(stripe_right - clipped_title_rect.x());
  113. if (!clipped_title_rect.is_empty()) {
  114. painter.draw_text(clipped_title_rect.translated(1, 2), title_text, title_font, Gfx::TextAlignment::CenterLeft, shadow_color, Gfx::TextElision::Right);
  115. // FIXME: The translated(0, 1) wouldn't be necessary if we could center text based on its baseline.
  116. painter.draw_text(clipped_title_rect.translated(0, 1), title_text, title_font, Gfx::TextAlignment::CenterLeft, title_color, Gfx::TextElision::Right);
  117. }
  118. }
  119. IntRect ClassicWindowTheme::menubar_rect(WindowType window_type, IntRect const& window_rect, Palette const& palette, int menu_row_count) const
  120. {
  121. if (window_type != WindowType::Normal)
  122. return {};
  123. return { palette.window_border_thickness(), palette.window_border_thickness() - 1 + titlebar_height(window_type, palette) + 2, window_rect.width(), menubar_height * menu_row_count };
  124. }
  125. IntRect ClassicWindowTheme::titlebar_rect(WindowType window_type, IntRect const& window_rect, Palette const& palette) const
  126. {
  127. auto& title_font = FontDatabase::window_title_font();
  128. auto window_titlebar_height = titlebar_height(window_type, palette);
  129. // FIXME: The top of the titlebar doesn't get redrawn properly if this padding is different
  130. int total_vertical_padding = title_font.glyph_height() - 1;
  131. if (window_type == WindowType::Notification)
  132. return { window_rect.width() + 3, total_vertical_padding / 2 - 1, window_titlebar_height, window_rect.height() };
  133. return { palette.window_border_thickness(), palette.window_border_thickness(), window_rect.width(), window_titlebar_height };
  134. }
  135. ClassicWindowTheme::FrameColors ClassicWindowTheme::compute_frame_colors(WindowState state, Palette const& palette) const
  136. {
  137. switch (state) {
  138. case WindowState::Highlighted:
  139. return { palette.highlight_window_title(), palette.highlight_window_border1(), palette.highlight_window_border2(), palette.highlight_window_title_stripes(), palette.highlight_window_title_shadow() };
  140. case WindowState::Moving:
  141. return { palette.moving_window_title(), palette.moving_window_border1(), palette.moving_window_border2(), palette.moving_window_title_stripes(), palette.moving_window_title_shadow() };
  142. case WindowState::Active:
  143. return { palette.active_window_title(), palette.active_window_border1(), palette.active_window_border2(), palette.active_window_title_stripes(), palette.active_window_title_shadow() };
  144. case WindowState::Inactive:
  145. return { palette.inactive_window_title(), palette.inactive_window_border1(), palette.inactive_window_border2(), palette.inactive_window_title_stripes(), palette.inactive_window_title_shadow() };
  146. default:
  147. VERIFY_NOT_REACHED();
  148. }
  149. }
  150. void ClassicWindowTheme::paint_notification_frame(Painter& painter, IntRect const& window_rect, Palette const& palette, IntRect const& close_button_rect) const
  151. {
  152. auto frame_rect = frame_rect_for_window(WindowType::Notification, window_rect, palette, 0);
  153. frame_rect.set_location({ 0, 0 });
  154. Gfx::StylePainter::paint_window_frame(painter, frame_rect, palette);
  155. auto titlebar_rect = this->titlebar_rect(WindowType::Notification, window_rect, palette);
  156. painter.fill_rect_with_gradient(Gfx::Orientation::Vertical, titlebar_rect, palette.active_window_border1(), palette.active_window_border2());
  157. if (palette.active_window_title_stripes().alpha() > 0) {
  158. int stripe_top = close_button_rect.bottom() + 4;
  159. int stripe_bottom = window_rect.height() - 3;
  160. if (stripe_top && stripe_bottom && stripe_top < stripe_bottom) {
  161. for (int i = 2; i <= palette.window_title_height() - 2; i += 2) {
  162. painter.draw_line({ titlebar_rect.x() + i, stripe_top }, { titlebar_rect.x() + i, stripe_bottom }, palette.active_window_title_stripes());
  163. }
  164. }
  165. }
  166. }
  167. IntRect ClassicWindowTheme::frame_rect_for_window(WindowType window_type, IntRect const& window_rect, Gfx::Palette const& palette, int menu_row_count) const
  168. {
  169. auto window_titlebar_height = titlebar_height(window_type, palette);
  170. auto border_thickness = palette.window_border_thickness();
  171. switch (window_type) {
  172. case WindowType::Normal:
  173. case WindowType::ToolWindow:
  174. return {
  175. window_rect.x() - border_thickness,
  176. window_rect.y() - window_titlebar_height - border_thickness - 1 - menu_row_count * menubar_height,
  177. window_rect.width() + (border_thickness * 2),
  178. window_rect.height() + (border_thickness * 2) + 1 + window_titlebar_height + menu_row_count * menubar_height
  179. };
  180. case WindowType::Notification:
  181. return {
  182. window_rect.x() - 3,
  183. window_rect.y() - 3,
  184. window_rect.width() + 6 + window_titlebar_height,
  185. window_rect.height() + 6
  186. };
  187. default:
  188. return window_rect;
  189. }
  190. }
  191. Vector<IntRect> ClassicWindowTheme::layout_buttons(WindowType window_type, IntRect const& window_rect, Palette const& palette, size_t buttons) const
  192. {
  193. int window_button_width = palette.window_title_button_width();
  194. int window_button_height = palette.window_title_button_height();
  195. int pos;
  196. Vector<IntRect> button_rects;
  197. if (window_type == WindowType::Notification)
  198. pos = titlebar_rect(window_type, window_rect, palette).top() + 2;
  199. else
  200. pos = titlebar_text_rect(window_type, window_rect, palette).right() + 1;
  201. for (size_t i = 0; i < buttons; i++) {
  202. if (window_type == WindowType::Notification) {
  203. // The button height & width have to be equal or it leaks out of its area
  204. Gfx::IntRect rect { 0, pos, window_button_height, window_button_height };
  205. rect.center_horizontally_within(titlebar_rect(window_type, window_rect, palette));
  206. button_rects.append(rect);
  207. pos += window_button_height;
  208. } else {
  209. pos -= window_button_width;
  210. Gfx::IntRect rect { pos, 0, window_button_width, window_button_height };
  211. rect.center_vertically_within(titlebar_text_rect(window_type, window_rect, palette));
  212. button_rects.append(rect);
  213. }
  214. }
  215. return button_rects;
  216. }
  217. int ClassicWindowTheme::titlebar_height(WindowType window_type, Palette const& palette) const
  218. {
  219. auto& title_font = FontDatabase::window_title_font();
  220. switch (window_type) {
  221. case WindowType::Normal:
  222. case WindowType::Notification:
  223. return max(palette.window_title_height(), title_font.glyph_height() + 8);
  224. case WindowType::ToolWindow:
  225. return max(palette.window_title_height() - 4, title_font.glyph_height() + 4);
  226. default:
  227. return 0;
  228. }
  229. }
  230. }