/* * Copyright (c) 2020, Andreas Kling * Copyright (c) 2021-2022, Filiph Sandström * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include 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); auto title_alignment = palette.title_alignment(); int stripe_right = leftmost_button_rect.left() - 3; if (stripes_color.alpha() > 0) { switch (title_alignment) { case Gfx::TextAlignment::CenterLeft: { 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); } } break; } case Gfx::TextAlignment::CenterRight: { for (int i = 2; i <= titlebar_inner_rect.height() - 2; i += 2) { 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); } break; } case Gfx::TextAlignment::Center: { auto stripe_width = (leftmost_button_rect.left() / 2 - titlebar_title_rect.width() / 2) - titlebar_icon_rect.width() - 3; for (int i = 2; i <= titlebar_inner_rect.height() - 2; i += 2) { 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); painter.draw_line({ stripe_right - stripe_width, titlebar_inner_rect.y() + i }, { stripe_right, titlebar_inner_rect.y() + i }, stripes_color); } break; } default: dbgln("Unhandled title alignment!"); } } 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, title_alignment, 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, title_alignment, 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 { palette.window_border_thickness(), palette.window_border_thickness() - 1 + 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 { palette.window_border_thickness(), palette.window_border_thickness(), 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); auto border_thickness = palette.window_border_thickness(); switch (window_type) { case WindowType::Normal: case WindowType::ToolWindow: return { window_rect.x() - border_thickness, window_rect.y() - window_titlebar_height - border_thickness - 1 - menu_row_count * menubar_height, window_rect.width() + (border_thickness * 2), window_rect.height() + (border_thickness * 2) + 1 + 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 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 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; } } }