ClassicWindowTheme.cpp 12 KB

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