mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-28 11:02:36 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			250 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			250 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
 | |
|  *
 | |
|  * SPDX-License-Identifier: BSD-2-Clause
 | |
|  */
 | |
| 
 | |
| #include <LibGfx/Bitmap.h>
 | |
| #include <LibGfx/ClassicWindowTheme.h>
 | |
| #include <LibGfx/FontDatabase.h>
 | |
| #include <LibGfx/Painter.h>
 | |
| #include <LibGfx/Palette.h>
 | |
| #include <LibGfx/StylePainter.h>
 | |
| 
 | |
| namespace Gfx {
 | |
| 
 | |
| static constexpr int menubar_height = 20;
 | |
| 
 | |
| ClassicWindowTheme::ClassicWindowTheme()
 | |
| {
 | |
| }
 | |
| 
 | |
| ClassicWindowTheme::~ClassicWindowTheme()
 | |
| {
 | |
| }
 | |
| 
 | |
| Gfx::IntRect ClassicWindowTheme::titlebar_icon_rect(WindowType window_type, const IntRect& window_rect, const Palette& palette) const
 | |
| {
 | |
|     if (window_type == WindowType::ToolWindow)
 | |
|         return {};
 | |
| 
 | |
|     auto titlebar_rect = this->titlebar_rect(window_type, window_rect, palette);
 | |
|     Gfx::IntRect icon_rect {
 | |
|         titlebar_rect.x() + 2,
 | |
|         titlebar_rect.y(),
 | |
|         16,
 | |
|         16,
 | |
|     };
 | |
|     icon_rect.center_vertically_within(titlebar_rect);
 | |
|     icon_rect.translate_by(0, 1);
 | |
|     return icon_rect;
 | |
| }
 | |
| 
 | |
| Gfx::IntRect ClassicWindowTheme::titlebar_text_rect(WindowType window_type, const IntRect& window_rect, const Palette& palette) const
 | |
| {
 | |
|     auto titlebar_rect = this->titlebar_rect(window_type, window_rect, palette);
 | |
|     auto titlebar_icon_rect = this->titlebar_icon_rect(window_type, window_rect, palette);
 | |
|     return {
 | |
|         titlebar_rect.x() + 3 + (titlebar_icon_rect.is_empty() ? 0 : (titlebar_icon_rect.width() + 2)),
 | |
|         titlebar_rect.y(),
 | |
|         titlebar_rect.width() - 5 - (titlebar_icon_rect.is_empty() ? 0 : (titlebar_icon_rect.width() + 2)),
 | |
|         titlebar_rect.height()
 | |
|     };
 | |
| }
 | |
| 
 | |
| void ClassicWindowTheme::paint_normal_frame(Painter& painter, WindowState window_state, const IntRect& window_rect, StringView window_title, const Bitmap& icon, const Palette& palette, const IntRect& leftmost_button_rect, int menu_row_count, [[maybe_unused]] bool window_modified) const
 | |
| {
 | |
|     auto frame_rect = frame_rect_for_window(WindowType::Normal, window_rect, palette, menu_row_count);
 | |
|     frame_rect.set_location({ 0, 0 });
 | |
|     Gfx::StylePainter::paint_window_frame(painter, frame_rect, palette);
 | |
| 
 | |
|     auto& title_font = FontDatabase::default_font().bold_variant();
 | |
| 
 | |
|     auto titlebar_rect = this->titlebar_rect(WindowType::Normal, window_rect, palette);
 | |
|     auto titlebar_icon_rect = this->titlebar_icon_rect(WindowType::Normal, window_rect, palette);
 | |
|     auto titlebar_inner_rect = titlebar_text_rect(WindowType::Normal, window_rect, palette);
 | |
|     auto titlebar_title_rect = titlebar_inner_rect;
 | |
|     titlebar_title_rect.set_width(title_font.width(window_title));
 | |
| 
 | |
|     auto [title_color, border_color, border_color2, stripes_color, shadow_color] = compute_frame_colors(window_state, palette);
 | |
| 
 | |
|     painter.draw_line(titlebar_rect.bottom_left().translated(0, 1), titlebar_rect.bottom_right().translated(0, 1), palette.button());
 | |
|     painter.draw_line(titlebar_rect.bottom_left().translated(0, 2), titlebar_rect.bottom_right().translated(0, 2), palette.button());
 | |
| 
 | |
|     painter.fill_rect_with_gradient(titlebar_rect, border_color, border_color2);
 | |
| 
 | |
|     int stripe_right = leftmost_button_rect.left() - 3;
 | |
|     if (stripes_color.alpha() > 0) {
 | |
|         int stripe_left = titlebar_title_rect.right() + 5;
 | |
|         if (stripe_left && stripe_right && stripe_left < stripe_right) {
 | |
|             for (int i = 2; i <= titlebar_inner_rect.height() - 2; i += 2) {
 | |
|                 painter.draw_line({ stripe_left, titlebar_inner_rect.y() + i }, { stripe_right, titlebar_inner_rect.y() + i }, stripes_color);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     auto clipped_title_rect = titlebar_title_rect;
 | |
|     clipped_title_rect.set_width(stripe_right - clipped_title_rect.x());
 | |
|     if (!clipped_title_rect.is_empty()) {
 | |
|         painter.draw_text(clipped_title_rect.translated(1, 2), window_title, title_font, Gfx::TextAlignment::CenterLeft, shadow_color, Gfx::TextElision::Right);
 | |
|         // FIXME: The translated(0, 1) wouldn't be necessary if we could center text based on its baseline.
 | |
|         painter.draw_text(clipped_title_rect.translated(0, 1), window_title, title_font, Gfx::TextAlignment::CenterLeft, title_color, Gfx::TextElision::Right);
 | |
|     }
 | |
| 
 | |
|     painter.draw_scaled_bitmap(titlebar_icon_rect, icon, icon.rect());
 | |
| }
 | |
| 
 | |
| void ClassicWindowTheme::paint_tool_window_frame(Painter& painter, WindowState window_state, const IntRect& window_rect, StringView title_text, const Palette& palette, const IntRect& leftmost_button_rect) const
 | |
| {
 | |
|     auto frame_rect = frame_rect_for_window(WindowType::ToolWindow, window_rect, palette, 0);
 | |
|     frame_rect.set_location({ 0, 0 });
 | |
|     Gfx::StylePainter::paint_window_frame(painter, frame_rect, palette);
 | |
| 
 | |
|     auto& title_font = FontDatabase::default_font().bold_variant();
 | |
| 
 | |
|     auto titlebar_rect = this->titlebar_rect(WindowType::ToolWindow, window_rect, palette);
 | |
|     auto titlebar_inner_rect = titlebar_text_rect(WindowType::ToolWindow, window_rect, palette);
 | |
|     auto titlebar_title_rect = titlebar_inner_rect;
 | |
|     titlebar_title_rect.set_width(title_font.width(title_text));
 | |
| 
 | |
|     auto [title_color, border_color, border_color2, stripes_color, shadow_color] = compute_frame_colors(window_state, palette);
 | |
| 
 | |
|     painter.draw_line(titlebar_rect.bottom_left().translated(0, 1), titlebar_rect.bottom_right().translated(0, 1), palette.button());
 | |
|     painter.draw_line(titlebar_rect.bottom_left().translated(0, 2), titlebar_rect.bottom_right().translated(0, 2), palette.button());
 | |
| 
 | |
|     painter.fill_rect_with_gradient(titlebar_rect, border_color, border_color2);
 | |
| 
 | |
|     int stripe_right = leftmost_button_rect.left() - 3;
 | |
| 
 | |
|     auto clipped_title_rect = titlebar_title_rect;
 | |
|     clipped_title_rect.set_width(stripe_right - clipped_title_rect.x());
 | |
|     if (!clipped_title_rect.is_empty()) {
 | |
|         painter.draw_text(clipped_title_rect.translated(1, 2), title_text, title_font, Gfx::TextAlignment::CenterLeft, shadow_color, Gfx::TextElision::Right);
 | |
|         // FIXME: The translated(0, 1) wouldn't be necessary if we could center text based on its baseline.
 | |
|         painter.draw_text(clipped_title_rect.translated(0, 1), title_text, title_font, Gfx::TextAlignment::CenterLeft, title_color, Gfx::TextElision::Right);
 | |
|     }
 | |
| }
 | |
| 
 | |
| IntRect ClassicWindowTheme::menubar_rect(WindowType window_type, const IntRect& window_rect, const Palette& palette, int menu_row_count) const
 | |
| {
 | |
|     if (window_type != WindowType::Normal)
 | |
|         return {};
 | |
|     return { 4, 3 + titlebar_height(window_type, palette) + 2, window_rect.width(), menubar_height * menu_row_count };
 | |
| }
 | |
| 
 | |
| IntRect ClassicWindowTheme::titlebar_rect(WindowType window_type, const IntRect& window_rect, const Palette& palette) const
 | |
| {
 | |
|     auto& title_font = FontDatabase::default_font().bold_variant();
 | |
|     auto window_titlebar_height = titlebar_height(window_type, palette);
 | |
|     // FIXME: The top of the titlebar doesn't get redrawn properly if this padding is different
 | |
|     int total_vertical_padding = title_font.glyph_height() - 1;
 | |
| 
 | |
|     if (window_type == WindowType::Notification)
 | |
|         return { window_rect.width() + 3, total_vertical_padding / 2 - 1, window_titlebar_height, window_rect.height() };
 | |
|     return { 4, 4, window_rect.width(), window_titlebar_height };
 | |
| }
 | |
| 
 | |
| ClassicWindowTheme::FrameColors ClassicWindowTheme::compute_frame_colors(WindowState state, const Palette& palette) const
 | |
| {
 | |
|     switch (state) {
 | |
|     case WindowState::Highlighted:
 | |
|         return { palette.highlight_window_title(), palette.highlight_window_border1(), palette.highlight_window_border2(), palette.highlight_window_title_stripes(), palette.highlight_window_title_shadow() };
 | |
|     case WindowState::Moving:
 | |
|         return { palette.moving_window_title(), palette.moving_window_border1(), palette.moving_window_border2(), palette.moving_window_title_stripes(), palette.moving_window_title_shadow() };
 | |
|     case WindowState::Active:
 | |
|         return { palette.active_window_title(), palette.active_window_border1(), palette.active_window_border2(), palette.active_window_title_stripes(), palette.active_window_title_shadow() };
 | |
|     case WindowState::Inactive:
 | |
|         return { palette.inactive_window_title(), palette.inactive_window_border1(), palette.inactive_window_border2(), palette.inactive_window_title_stripes(), palette.inactive_window_title_shadow() };
 | |
|     default:
 | |
|         VERIFY_NOT_REACHED();
 | |
|     }
 | |
| }
 | |
| 
 | |
| void ClassicWindowTheme::paint_notification_frame(Painter& painter, const IntRect& window_rect, const Palette& palette, const IntRect& close_button_rect) const
 | |
| {
 | |
|     auto frame_rect = frame_rect_for_window(WindowType::Notification, window_rect, palette, 0);
 | |
|     frame_rect.set_location({ 0, 0 });
 | |
|     Gfx::StylePainter::paint_window_frame(painter, frame_rect, palette);
 | |
| 
 | |
|     auto titlebar_rect = this->titlebar_rect(WindowType::Notification, window_rect, palette);
 | |
|     painter.fill_rect_with_gradient(Gfx::Orientation::Vertical, titlebar_rect, palette.active_window_border1(), palette.active_window_border2());
 | |
| 
 | |
|     if (palette.active_window_title_stripes().alpha() > 0) {
 | |
|         int stripe_top = close_button_rect.bottom() + 4;
 | |
|         int stripe_bottom = window_rect.height() - 3;
 | |
|         if (stripe_top && stripe_bottom && stripe_top < stripe_bottom) {
 | |
|             for (int i = 2; i <= palette.window_title_height() - 2; i += 2) {
 | |
|                 painter.draw_line({ titlebar_rect.x() + i, stripe_top }, { titlebar_rect.x() + i, stripe_bottom }, palette.active_window_title_stripes());
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| IntRect ClassicWindowTheme::frame_rect_for_window(WindowType window_type, const IntRect& window_rect, const Gfx::Palette& palette, int menu_row_count) const
 | |
| {
 | |
|     auto window_titlebar_height = titlebar_height(window_type, palette);
 | |
| 
 | |
|     switch (window_type) {
 | |
|     case WindowType::Normal:
 | |
|     case WindowType::ToolWindow:
 | |
|         return {
 | |
|             window_rect.x() - 4,
 | |
|             window_rect.y() - window_titlebar_height - 5 - menu_row_count * menubar_height,
 | |
|             window_rect.width() + 8,
 | |
|             window_rect.height() + 9 + window_titlebar_height + menu_row_count * menubar_height
 | |
|         };
 | |
|     case WindowType::Notification:
 | |
|         return {
 | |
|             window_rect.x() - 3,
 | |
|             window_rect.y() - 3,
 | |
|             window_rect.width() + 6 + window_titlebar_height,
 | |
|             window_rect.height() + 6
 | |
|         };
 | |
|     default:
 | |
|         return window_rect;
 | |
|     }
 | |
| }
 | |
| 
 | |
| Vector<IntRect> ClassicWindowTheme::layout_buttons(WindowType window_type, const IntRect& window_rect, const Palette& palette, size_t buttons) const
 | |
| {
 | |
|     int window_button_width = palette.window_title_button_width();
 | |
|     int window_button_height = palette.window_title_button_height();
 | |
|     int pos;
 | |
|     Vector<IntRect> button_rects;
 | |
|     if (window_type == WindowType::Notification)
 | |
|         pos = titlebar_rect(window_type, window_rect, palette).top() + 2;
 | |
|     else
 | |
|         pos = titlebar_text_rect(window_type, window_rect, palette).right() + 1;
 | |
| 
 | |
|     for (size_t i = 0; i < buttons; i++) {
 | |
|         if (window_type == WindowType::Notification) {
 | |
|             // The button height & width have to be equal or it leaks out of its area
 | |
|             Gfx::IntRect rect { 0, pos, window_button_height, window_button_height };
 | |
|             rect.center_horizontally_within(titlebar_rect(window_type, window_rect, palette));
 | |
|             button_rects.append(rect);
 | |
|             pos += window_button_height;
 | |
|         } else {
 | |
|             pos -= window_button_width;
 | |
|             Gfx::IntRect rect { pos, 0, window_button_width, window_button_height };
 | |
|             rect.center_vertically_within(titlebar_text_rect(window_type, window_rect, palette));
 | |
|             button_rects.append(rect);
 | |
|         }
 | |
|     }
 | |
|     return button_rects;
 | |
| }
 | |
| 
 | |
| int ClassicWindowTheme::titlebar_height(WindowType window_type, const Palette& palette) const
 | |
| {
 | |
|     auto& title_font = FontDatabase::default_font().bold_variant();
 | |
|     switch (window_type) {
 | |
|     case WindowType::Normal:
 | |
|     case WindowType::Notification:
 | |
|         return max(palette.window_title_height(), title_font.glyph_height() + 8);
 | |
|     case WindowType::ToolWindow:
 | |
|         return max(palette.window_title_height() - 4, title_font.glyph_height() + 4);
 | |
|     default:
 | |
|         return 0;
 | |
|     }
 | |
| }
 | |
| 
 | |
| }
 | 
