ClassicWindowTheme.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. /*
  2. * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. *
  8. * 1. Redistributions of source code must retain the above copyright notice, this
  9. * list of conditions and the following disclaimer.
  10. *
  11. * 2. Redistributions in binary form must reproduce the above copyright notice,
  12. * this list of conditions and the following disclaimer in the documentation
  13. * and/or other materials provided with the distribution.
  14. *
  15. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  16. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  17. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  18. * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  19. * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  20. * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  21. * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  22. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  23. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  24. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  25. */
  26. #include <LibGfx/Bitmap.h>
  27. #include <LibGfx/ClassicWindowTheme.h>
  28. #include <LibGfx/FontDatabase.h>
  29. #include <LibGfx/Painter.h>
  30. #include <LibGfx/Palette.h>
  31. #include <LibGfx/StylePainter.h>
  32. namespace Gfx {
  33. static constexpr int menu_bar_height = 19;
  34. ClassicWindowTheme::ClassicWindowTheme()
  35. {
  36. }
  37. ClassicWindowTheme::~ClassicWindowTheme()
  38. {
  39. }
  40. Gfx::IntRect ClassicWindowTheme::title_bar_icon_rect(WindowType window_type, const IntRect& window_rect, const Palette& palette) const
  41. {
  42. if (window_type == WindowType::ToolWindow)
  43. return {};
  44. auto titlebar_rect = title_bar_rect(window_type, window_rect, palette);
  45. Gfx::IntRect icon_rect {
  46. titlebar_rect.x() + 2,
  47. titlebar_rect.y(),
  48. 16,
  49. 16,
  50. };
  51. icon_rect.center_vertically_within(titlebar_rect);
  52. icon_rect.move_by(0, 1);
  53. return icon_rect;
  54. }
  55. Gfx::IntRect ClassicWindowTheme::title_bar_text_rect(WindowType window_type, const IntRect& window_rect, const Palette& palette) const
  56. {
  57. auto titlebar_rect = title_bar_rect(window_type, window_rect, palette);
  58. auto titlebar_icon_rect = title_bar_icon_rect(window_type, window_rect, palette);
  59. return {
  60. titlebar_rect.x() + 3 + (titlebar_icon_rect.is_empty() ? 0 : (titlebar_icon_rect.width() + 2)),
  61. titlebar_rect.y(),
  62. titlebar_rect.width() - 5 - (titlebar_icon_rect.is_empty() ? 0 : (titlebar_icon_rect.width() + 2)),
  63. titlebar_rect.height()
  64. };
  65. }
  66. void ClassicWindowTheme::paint_normal_frame(Painter& painter, WindowState window_state, const IntRect& window_rect, const StringView& title_text, const Bitmap& icon, const Palette& palette, const IntRect& leftmost_button_rect, int menu_row_count) const
  67. {
  68. auto frame_rect = frame_rect_for_window(WindowType::Normal, window_rect, palette, menu_row_count);
  69. frame_rect.set_location({ 0, 0 });
  70. Gfx::StylePainter::paint_window_frame(painter, frame_rect, palette);
  71. auto& title_font = FontDatabase::default_bold_font();
  72. auto titlebar_rect = title_bar_rect(WindowType::Normal, window_rect, palette);
  73. auto titlebar_icon_rect = title_bar_icon_rect(WindowType::Normal, window_rect, palette);
  74. auto titlebar_inner_rect = title_bar_text_rect(WindowType::Normal, window_rect, palette);
  75. auto titlebar_title_rect = titlebar_inner_rect;
  76. titlebar_title_rect.set_width(FontDatabase::default_bold_font().width(title_text));
  77. auto [title_color, border_color, border_color2, stripes_color, shadow_color] = compute_frame_colors(window_state, palette);
  78. painter.draw_line(titlebar_rect.bottom_left().translated(0, 1), titlebar_rect.bottom_right().translated(0, 1), palette.button());
  79. painter.draw_line(titlebar_rect.bottom_left().translated(0, 2), titlebar_rect.bottom_right().translated(0, 2), palette.button());
  80. painter.fill_rect_with_gradient(titlebar_rect, border_color, border_color2);
  81. int stripe_right = leftmost_button_rect.left() - 3;
  82. if (stripes_color.alpha() > 0) {
  83. int stripe_left = titlebar_title_rect.right() + 5;
  84. if (stripe_left && stripe_right && stripe_left < stripe_right) {
  85. for (int i = 2; i <= titlebar_inner_rect.height() - 2; i += 2) {
  86. painter.draw_line({ stripe_left, titlebar_inner_rect.y() + i }, { stripe_right, titlebar_inner_rect.y() + i }, stripes_color);
  87. }
  88. }
  89. }
  90. auto clipped_title_rect = titlebar_title_rect;
  91. clipped_title_rect.set_width(stripe_right - clipped_title_rect.x());
  92. if (!clipped_title_rect.is_empty()) {
  93. painter.draw_text(clipped_title_rect.translated(1, 2), title_text, title_font, Gfx::TextAlignment::CenterLeft, shadow_color, Gfx::TextElision::Right);
  94. // FIXME: The translated(0, 1) wouldn't be necessary if we could center text based on its baseline.
  95. painter.draw_text(clipped_title_rect.translated(0, 1), title_text, title_font, Gfx::TextAlignment::CenterLeft, title_color, Gfx::TextElision::Right);
  96. }
  97. painter.draw_scaled_bitmap(titlebar_icon_rect, icon, icon.rect());
  98. }
  99. void ClassicWindowTheme::paint_tool_window_frame(Painter& painter, WindowState window_state, const IntRect& window_rect, const StringView& title_text, const Palette& palette, const IntRect& leftmost_button_rect) const
  100. {
  101. auto frame_rect = frame_rect_for_window(WindowType::ToolWindow, window_rect, palette, 0);
  102. frame_rect.set_location({ 0, 0 });
  103. Gfx::StylePainter::paint_window_frame(painter, frame_rect, palette);
  104. auto& title_font = FontDatabase::default_bold_font();
  105. auto titlebar_rect = title_bar_rect(WindowType::ToolWindow, window_rect, palette);
  106. auto titlebar_inner_rect = title_bar_text_rect(WindowType::ToolWindow, window_rect, palette);
  107. auto titlebar_title_rect = titlebar_inner_rect;
  108. titlebar_title_rect.set_width(FontDatabase::default_bold_font().width(title_text));
  109. auto [title_color, border_color, border_color2, stripes_color, shadow_color] = compute_frame_colors(window_state, palette);
  110. painter.draw_line(titlebar_rect.bottom_left().translated(0, 1), titlebar_rect.bottom_right().translated(0, 1), palette.button());
  111. painter.draw_line(titlebar_rect.bottom_left().translated(0, 2), titlebar_rect.bottom_right().translated(0, 2), palette.button());
  112. painter.fill_rect_with_gradient(titlebar_rect, border_color, border_color2);
  113. int stripe_right = leftmost_button_rect.left() - 3;
  114. auto clipped_title_rect = titlebar_title_rect;
  115. clipped_title_rect.set_width(stripe_right - clipped_title_rect.x());
  116. if (!clipped_title_rect.is_empty()) {
  117. painter.draw_text(clipped_title_rect.translated(1, 2), title_text, title_font, Gfx::TextAlignment::CenterLeft, shadow_color, Gfx::TextElision::Right);
  118. // FIXME: The translated(0, 1) wouldn't be necessary if we could center text based on its baseline.
  119. painter.draw_text(clipped_title_rect.translated(0, 1), title_text, title_font, Gfx::TextAlignment::CenterLeft, title_color, Gfx::TextElision::Right);
  120. }
  121. }
  122. IntRect ClassicWindowTheme::menu_bar_rect(WindowType window_type, const IntRect& window_rect, const Palette& palette, int menu_row_count) const
  123. {
  124. if (window_type != WindowType::Normal)
  125. return {};
  126. return { 4, 4 + title_bar_height(window_type, palette) + 2, window_rect.width(), menu_bar_height * menu_row_count };
  127. }
  128. IntRect ClassicWindowTheme::title_bar_rect(WindowType window_type, const IntRect& window_rect, const Palette& palette) const
  129. {
  130. auto& title_font = FontDatabase::default_bold_font();
  131. auto window_titlebar_height = title_bar_height(window_type, palette);
  132. // FIXME: The top of the titlebar doesn't get redrawn properly if this padding is different
  133. int total_vertical_padding = title_font.glyph_height() - 1;
  134. if (window_type == WindowType::Notification)
  135. return { window_rect.width() + 3, total_vertical_padding / 2 - 1, window_titlebar_height, window_rect.height() };
  136. return { 4, 4, window_rect.width(), window_titlebar_height };
  137. }
  138. ClassicWindowTheme::FrameColors ClassicWindowTheme::compute_frame_colors(WindowState state, const Palette& palette) const
  139. {
  140. switch (state) {
  141. case WindowState::Highlighted:
  142. return { palette.highlight_window_title(), palette.highlight_window_border1(), palette.highlight_window_border2(), palette.highlight_window_title_stripes(), palette.highlight_window_title_shadow() };
  143. case WindowState::Moving:
  144. return { palette.moving_window_title(), palette.moving_window_border1(), palette.moving_window_border2(), palette.moving_window_title_stripes(), palette.moving_window_title_shadow() };
  145. case WindowState::Active:
  146. return { palette.active_window_title(), palette.active_window_border1(), palette.active_window_border2(), palette.active_window_title_stripes(), palette.active_window_title_shadow() };
  147. case WindowState::Inactive:
  148. return { palette.inactive_window_title(), palette.inactive_window_border1(), palette.inactive_window_border2(), palette.inactive_window_title_stripes(), palette.inactive_window_title_shadow() };
  149. default:
  150. VERIFY_NOT_REACHED();
  151. }
  152. }
  153. void ClassicWindowTheme::paint_notification_frame(Painter& painter, const IntRect& window_rect, const Palette& palette, const IntRect& close_button_rect) const
  154. {
  155. auto frame_rect = frame_rect_for_window(WindowType::Notification, window_rect, palette, 0);
  156. frame_rect.set_location({ 0, 0 });
  157. Gfx::StylePainter::paint_window_frame(painter, frame_rect, palette);
  158. auto titlebar_rect = title_bar_rect(WindowType::Notification, window_rect, palette);
  159. painter.fill_rect_with_gradient(Gfx::Orientation::Vertical, titlebar_rect, palette.active_window_border1(), palette.active_window_border2());
  160. if (palette.active_window_title_stripes().alpha() > 0) {
  161. int stripe_top = close_button_rect.bottom() + 4;
  162. int stripe_bottom = window_rect.height() - 3;
  163. if (stripe_top && stripe_bottom && stripe_top < stripe_bottom) {
  164. for (int i = 2; i <= palette.window_title_height() - 2; i += 2) {
  165. painter.draw_line({ titlebar_rect.x() + i, stripe_top }, { titlebar_rect.x() + i, stripe_bottom }, palette.active_window_title_stripes());
  166. }
  167. }
  168. }
  169. }
  170. IntRect ClassicWindowTheme::frame_rect_for_window(WindowType window_type, const IntRect& window_rect, const Gfx::Palette& palette, int menu_row_count) const
  171. {
  172. auto window_titlebar_height = title_bar_height(window_type, palette);
  173. switch (window_type) {
  174. case WindowType::Normal:
  175. case WindowType::ToolWindow:
  176. return {
  177. window_rect.x() - 4,
  178. window_rect.y() - window_titlebar_height - 6 - menu_row_count * menu_bar_height,
  179. window_rect.width() + 8,
  180. window_rect.height() + 10 + window_titlebar_height + menu_row_count * menu_bar_height
  181. };
  182. case WindowType::Notification:
  183. return {
  184. window_rect.x() - 3,
  185. window_rect.y() - 3,
  186. window_rect.width() + 6 + window_titlebar_height,
  187. window_rect.height() + 6
  188. };
  189. default:
  190. return window_rect;
  191. }
  192. }
  193. Vector<IntRect> ClassicWindowTheme::layout_buttons(WindowType window_type, const IntRect& window_rect, const Palette& palette, size_t buttons) const
  194. {
  195. int window_button_width = palette.window_title_button_width();
  196. int window_button_height = palette.window_title_button_height();
  197. int pos;
  198. Vector<IntRect> button_rects;
  199. if (window_type == WindowType::Notification)
  200. pos = title_bar_rect(window_type, window_rect, palette).top() + 2;
  201. else
  202. pos = title_bar_text_rect(window_type, window_rect, palette).right() + 1;
  203. for (size_t i = 0; i < buttons; i++) {
  204. if (window_type == WindowType::Notification) {
  205. // The button height & width have to be equal or it leaks out of its area
  206. Gfx::IntRect rect { 0, pos, window_button_height, window_button_height };
  207. rect.center_horizontally_within(title_bar_rect(window_type, window_rect, palette));
  208. button_rects.append(rect);
  209. pos += window_button_height;
  210. } else {
  211. pos -= window_button_width;
  212. Gfx::IntRect rect { pos, 0, window_button_width, window_button_height };
  213. rect.center_vertically_within(title_bar_text_rect(window_type, window_rect, palette));
  214. button_rects.append(rect);
  215. }
  216. }
  217. return button_rects;
  218. }
  219. int ClassicWindowTheme::title_bar_height(WindowType window_type, const Palette& palette) const
  220. {
  221. auto& title_font = FontDatabase::default_bold_font();
  222. switch (window_type) {
  223. case WindowType::Normal:
  224. case WindowType::Notification:
  225. return max(palette.window_title_height(), title_font.glyph_height() + 8);
  226. case WindowType::ToolWindow:
  227. return max(palette.window_title_height() - 4, title_font.glyph_height() + 4);
  228. default:
  229. return 0;
  230. }
  231. }
  232. }