diff --git a/Libraries/LibGfx/CMakeLists.txt b/Libraries/LibGfx/CMakeLists.txt index e0f13a0e7a..58d50882f2 100644 --- a/Libraries/LibGfx/CMakeLists.txt +++ b/Libraries/LibGfx/CMakeLists.txt @@ -3,6 +3,7 @@ set(SOURCES Bitmap.cpp BMPLoader.cpp CharacterBitmap.cpp + ClassicWindowTheme.cpp Color.cpp DisjointRectSet.cpp Emoji.cpp diff --git a/Libraries/LibGfx/ClassicWindowTheme.cpp b/Libraries/LibGfx/ClassicWindowTheme.cpp new file mode 100644 index 0000000000..ccf9c9a5c2 --- /dev/null +++ b/Libraries/LibGfx/ClassicWindowTheme.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +namespace Gfx { + +ClassicWindowTheme::ClassicWindowTheme() +{ +} + +ClassicWindowTheme::~ClassicWindowTheme() +{ +} + +Gfx::IntRect ClassicWindowTheme::title_bar_icon_rect(WindowType window_type, const IntRect& window_rect, const Palette& palette) const +{ + auto titlebar_rect = title_bar_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.move_by(0, 1); + return icon_rect; +} + +Gfx::IntRect ClassicWindowTheme::title_bar_text_rect(WindowType window_type, const IntRect& window_rect, const Palette& palette) const +{ + auto titlebar_rect = title_bar_rect(window_type, window_rect, palette); + auto titlebar_icon_rect = title_bar_icon_rect(window_type, window_rect, palette); + return { + titlebar_rect.x() + 3 + titlebar_icon_rect.width() + 2, + titlebar_rect.y(), + titlebar_rect.width() - 5 - titlebar_icon_rect.width() - 2, + titlebar_rect.height() + }; +} + +void ClassicWindowTheme::paint_normal_frame(Painter& painter, WindowState window_state, const IntRect& outer_rect, const IntRect& window_rect, const StringView& title_text, const Bitmap& icon, const Palette& palette, const IntRect& leftmost_button_rect) const +{ + Gfx::StylePainter::paint_window_frame(painter, outer_rect, palette); + + auto& title_font = Font::default_bold_font(); + + auto titlebar_rect = title_bar_rect(WindowType::Normal, window_rect, palette); + auto titlebar_icon_rect = title_bar_icon_rect(WindowType::Normal, window_rect, palette); + auto titlebar_inner_rect = title_bar_text_rect(WindowType::Normal, window_rect, palette); + auto titlebar_title_rect = titlebar_inner_rect; + titlebar_title_rect.set_width(Font::default_bold_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_left = titlebar_title_rect.right() + 4; + int stripe_right = leftmost_button_rect.left() - 3; + 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), 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); + } + + painter.blit(titlebar_icon_rect.location(), icon, icon.rect()); +} + +IntRect ClassicWindowTheme::title_bar_rect(WindowType window_type, const IntRect& window_rect, const Palette& palette) const +{ + auto& title_font = Font::default_bold_font(); + auto window_titlebar_height = palette.window_title_height(); + int total_vertical_padding = window_titlebar_height - title_font.glyph_height(); + + if (window_type == WindowType::Notification) + return { window_rect.width() + 3, total_vertical_padding / 2 - 1, window_titlebar_height, window_rect.height() }; + return { 4, total_vertical_padding / 2, 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: + ASSERT_NOT_REACHED(); + } +} + +void ClassicWindowTheme::paint_notification_frame(Painter&, const IntRect&, const Palette&) const +{ +} + +} diff --git a/Libraries/LibGfx/ClassicWindowTheme.h b/Libraries/LibGfx/ClassicWindowTheme.h new file mode 100644 index 0000000000..f425bcfcac --- /dev/null +++ b/Libraries/LibGfx/ClassicWindowTheme.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include +#include + +namespace Gfx { + +class ClassicWindowTheme final : public WindowTheme { +public: + ClassicWindowTheme(); + virtual ~ClassicWindowTheme() override; + + virtual void paint_normal_frame(Painter&, WindowState, const IntRect& outer_rect, const IntRect& window_rect, const StringView& title, const Bitmap& icon, const Palette&, const IntRect& leftmost_button_rect) const override; + virtual void paint_notification_frame(Painter&, const IntRect&, const Palette&) const override; + + virtual IntRect title_bar_rect(WindowType, const IntRect& window_rect, const Palette&) const override; + virtual IntRect title_bar_icon_rect(WindowType, const IntRect& window_rect, const Palette&) const override; + virtual IntRect title_bar_text_rect(WindowType, const IntRect& window_rect, const Palette&) const override; + +private: + struct FrameColors { + Color title_color; + Color border_color; + Color border_color2; + Color title_stripes_color; + Color title_shadow_color; + }; + + FrameColors compute_frame_colors(WindowState, const Palette&) const; +}; + +} diff --git a/Services/WindowServer/WindowFrame.cpp b/Services/WindowServer/WindowFrame.cpp index 52c6ea26ca..21728051bb 100644 --- a/Services/WindowServer/WindowFrame.cpp +++ b/Services/WindowServer/WindowFrame.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -131,51 +132,29 @@ void WindowFrame::did_set_maximized(Badge, bool maximized) Gfx::IntRect WindowFrame::title_bar_rect() const { - auto window_titlebar_height = WindowManager::the().palette().window_title_height(); - int total_vertical_padding = window_titlebar_height - WindowManager::the().window_title_font().glyph_height(); - - if (m_window.type() == WindowType::Notification) - return { m_window.width() + 3, total_vertical_padding / 2 - 1, window_titlebar_height, m_window.height() }; - return { 4, total_vertical_padding / 2, m_window.width(), window_titlebar_height }; + return Gfx::WindowTheme::current().title_bar_rect(m_window.type() == WindowType::Notification ? Gfx::WindowTheme::WindowType::Notification : Gfx::WindowTheme::WindowType::Normal, m_window.rect(), WindowManager::the().palette()); } Gfx::IntRect WindowFrame::title_bar_icon_rect() const { - auto titlebar_rect = title_bar_rect(); - Gfx::IntRect icon_rect { - titlebar_rect.x() + 2, - titlebar_rect.y(), - 16, - 16, - }; - icon_rect.center_vertically_within(titlebar_rect); - icon_rect.move_by(0, 1); - return icon_rect; + return Gfx::WindowTheme::current().title_bar_icon_rect(m_window.type() == WindowType::Notification ? Gfx::WindowTheme::WindowType::Notification : Gfx::WindowTheme::WindowType::Normal, m_window.rect(), WindowManager::the().palette()); } Gfx::IntRect WindowFrame::title_bar_text_rect() const { - auto titlebar_rect = title_bar_rect(); - auto titlebar_icon_rect = title_bar_icon_rect(); - return { - titlebar_rect.x() + 3 + titlebar_icon_rect.width() + 2, - titlebar_rect.y(), - titlebar_rect.width() - 5 - titlebar_icon_rect.width() - 2, - titlebar_rect.height() - }; + return Gfx::WindowTheme::current().title_bar_text_rect(m_window.type() == WindowType::Notification ? Gfx::WindowTheme::WindowType::Notification : Gfx::WindowTheme::WindowType::Normal, m_window.rect(), WindowManager::the().palette()); } -WindowFrame::FrameColors WindowFrame::compute_frame_colors() const +Gfx::WindowTheme::WindowState WindowFrame::window_state_for_theme() const { auto& wm = WindowManager::the(); - auto palette = wm.palette(); if (&m_window == wm.m_highlight_window) - return { palette.highlight_window_title(), palette.highlight_window_border1(), palette.highlight_window_border2(), palette.highlight_window_title_stripes(), palette.highlight_window_title_shadow() }; + return Gfx::WindowTheme::WindowState::Highlighted; if (&m_window == wm.m_move_window) - return { palette.moving_window_title(), palette.moving_window_border1(), palette.moving_window_border2(), palette.moving_window_title_stripes(), palette.moving_window_title_shadow() }; + return Gfx::WindowTheme::WindowState::Moving; if (wm.is_active_window_or_accessory(m_window)) - return { palette.active_window_title(), palette.active_window_border1(), palette.active_window_border2(), palette.active_window_title_stripes(), palette.active_window_title_shadow() }; - return { palette.inactive_window_title(), palette.inactive_window_border1(), palette.inactive_window_border2(), palette.inactive_window_title_stripes(), palette.inactive_window_title_shadow() }; + return Gfx::WindowTheme::WindowState::Active; + return Gfx::WindowTheme::WindowState::Inactive; } void WindowFrame::paint_notification_frame(Gfx::Painter& painter) @@ -212,41 +191,8 @@ void WindowFrame::paint_normal_frame(Gfx::Painter& painter) title_text = window.title(); } - Gfx::StylePainter::paint_window_frame(painter, outer_rect, palette); - - auto titlebar_rect = title_bar_rect(); - auto titlebar_icon_rect = title_bar_icon_rect(); - auto titlebar_inner_rect = title_bar_text_rect(); - auto titlebar_title_rect = titlebar_inner_rect; - titlebar_title_rect.set_width(WindowManager::the().window_title_font().width(title_text)); - - auto [title_color, border_color, border_color2, stripes_color, shadow_color] = compute_frame_colors(); - - auto& wm = WindowManager::the(); - 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()); - auto leftmost_button_rect = m_buttons.is_empty() ? Gfx::IntRect() : m_buttons.last().relative_rect(); - - painter.fill_rect_with_gradient(titlebar_rect, border_color, border_color2); - - int stripe_left = titlebar_title_rect.right() + 4; - int stripe_right = leftmost_button_rect.left() - 3; - 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), title_text, wm.window_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, wm.window_title_font(), Gfx::TextAlignment::CenterLeft, title_color, Gfx::TextElision::Right); - } - - painter.blit(titlebar_icon_rect.location(), window.icon(), window.icon().rect()); + Gfx::WindowTheme::current().paint_normal_frame(painter, window_state_for_theme(), outer_rect, m_window.rect(), title_text, m_window.icon(), palette, leftmost_button_rect); } void WindowFrame::paint(Gfx::Painter& painter) diff --git a/Services/WindowServer/WindowFrame.h b/Services/WindowServer/WindowFrame.h index 21e8950373..6b8738cc4a 100644 --- a/Services/WindowServer/WindowFrame.h +++ b/Services/WindowServer/WindowFrame.h @@ -29,6 +29,7 @@ #include #include #include +#include namespace WindowServer { @@ -60,15 +61,7 @@ private: void paint_notification_frame(Gfx::Painter&); void paint_normal_frame(Gfx::Painter&); - struct FrameColors { - Color title_color; - Color border_color; - Color border_color2; - Color title_stripes_color; - Color title_shadow_color; - }; - - FrameColors compute_frame_colors() const; + Gfx::WindowTheme::WindowState window_state_for_theme() const; Window& m_window; NonnullOwnPtrVector