From 0ce4b9d7dba09d3fe65e2f0ca0bdd275adf1c93b Mon Sep 17 00:00:00 2001 From: Tom Date: Mon, 8 Feb 2021 17:27:51 -0700 Subject: [PATCH] WindowServer: Implement simple window shadows This implements simple window shadows around most windows, including tooltips. Because this method uses a bitmap for the shadow bits, it is limited to rectangular window frames. For non-rectangular window frames we'll need to implement a more sophisticated algorithm. --- .../icons/themes/Default/window-shadow.png | Bin 0 -> 6136 bytes Base/res/themes/Default.ini | 4 + .../Libraries/LibGfx/ClassicWindowTheme.h | 1 + Userland/Libraries/LibGfx/Painter.cpp | 4 +- Userland/Libraries/LibGfx/Painter.h | 2 +- Userland/Libraries/LibGfx/Palette.h | 1 + Userland/Libraries/LibGfx/SystemTheme.cpp | 1 + Userland/Libraries/LibGfx/SystemTheme.h | 1 + Userland/Libraries/LibGfx/WindowTheme.h | 1 + Userland/Services/WindowServer/Compositor.cpp | 37 ++- Userland/Services/WindowServer/Window.cpp | 27 ++- .../Services/WindowServer/WindowFrame.cpp | 210 +++++++++++++++--- Userland/Services/WindowServer/WindowFrame.h | 12 +- .../Services/WindowServer/WindowManager.cpp | 3 +- 14 files changed, 234 insertions(+), 70 deletions(-) create mode 100644 Base/res/icons/themes/Default/window-shadow.png diff --git a/Base/res/icons/themes/Default/window-shadow.png b/Base/res/icons/themes/Default/window-shadow.png new file mode 100644 index 0000000000000000000000000000000000000000..87f71fb51df3f350098ecc6f4f2b8d63f7e3f480 GIT binary patch literal 6136 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 layout_buttons(WindowType, const IntRect& window_rect, const Palette&, size_t buttons) const override; + virtual bool is_simple_rect_frame() const override { return true; } private: struct FrameColors { diff --git a/Userland/Libraries/LibGfx/Painter.cpp b/Userland/Libraries/LibGfx/Painter.cpp index 5c67e6bac7..031d228826 100644 --- a/Userland/Libraries/LibGfx/Painter.cpp +++ b/Userland/Libraries/LibGfx/Painter.cpp @@ -735,13 +735,13 @@ void Painter::blit_with_alpha(const IntPoint& position, const Gfx::Bitmap& sourc } } -void Painter::blit(const IntPoint& position, const Gfx::Bitmap& source, const IntRect& a_src_rect, float opacity) +void Painter::blit(const IntPoint& position, const Gfx::Bitmap& source, const IntRect& a_src_rect, float opacity, bool apply_alpha) { ASSERT(scale() >= source.scale() && "painter doesn't support downsampling scale factors"); if (opacity < 1.0f) return blit_with_opacity(position, source, a_src_rect, opacity); - if (source.has_alpha_channel()) + if (source.has_alpha_channel() && apply_alpha) return blit_with_alpha(position, source, a_src_rect); auto safe_src_rect = a_src_rect.intersected(source.rect()); diff --git a/Userland/Libraries/LibGfx/Painter.h b/Userland/Libraries/LibGfx/Painter.h index 08cacd4965..12fa54457b 100644 --- a/Userland/Libraries/LibGfx/Painter.h +++ b/Userland/Libraries/LibGfx/Painter.h @@ -70,7 +70,7 @@ public: void draw_line(const IntPoint&, const IntPoint&, Color, int thickness = 1, LineStyle style = LineStyle::Solid); void draw_quadratic_bezier_curve(const IntPoint& control_point, const IntPoint&, const IntPoint&, Color, int thickness = 1, LineStyle style = LineStyle::Solid); void draw_elliptical_arc(const IntPoint& p1, const IntPoint& p2, const IntPoint& center, const FloatPoint& radii, float x_axis_rotation, float theta_1, float theta_delta, Color, int thickness = 1, LineStyle style = LineStyle::Solid); - void blit(const IntPoint&, const Gfx::Bitmap&, const IntRect& src_rect, float opacity = 1.0f); + void blit(const IntPoint&, const Gfx::Bitmap&, const IntRect& src_rect, float opacity = 1.0f, bool apply_alpha = true); void blit_dimmed(const IntPoint&, const Gfx::Bitmap&, const IntRect& src_rect); void blit_brightened(const IntPoint&, const Gfx::Bitmap&, const IntRect& src_rect); void blit_filtered(const IntPoint&, const Gfx::Bitmap&, const IntRect& src_rect, Function); diff --git a/Userland/Libraries/LibGfx/Palette.h b/Userland/Libraries/LibGfx/Palette.h index 7755263301..efe64baf29 100644 --- a/Userland/Libraries/LibGfx/Palette.h +++ b/Userland/Libraries/LibGfx/Palette.h @@ -140,6 +140,7 @@ public: int window_title_button_height() const { return metric(MetricRole::TitleButtonHeight); } String title_button_icons_path() const { return path(PathRole::TitleButtonIcons); } + String window_shadow_path() const { return path(PathRole::WindowShadow); } Color color(ColorRole role) const { return m_impl->color(role); } int metric(MetricRole role) const { return m_impl->metric(role); } diff --git a/Userland/Libraries/LibGfx/SystemTheme.cpp b/Userland/Libraries/LibGfx/SystemTheme.cpp index 2c4853766c..635bea11f4 100644 --- a/Userland/Libraries/LibGfx/SystemTheme.cpp +++ b/Userland/Libraries/LibGfx/SystemTheme.cpp @@ -119,6 +119,7 @@ Core::AnonymousBuffer load_system_theme(const String& path) } while (0) DO_PATH(TitleButtonIcons); + DO_PATH(WindowShadow); return buffer; } diff --git a/Userland/Libraries/LibGfx/SystemTheme.h b/Userland/Libraries/LibGfx/SystemTheme.h index 7c3fbbabb9..f12fc9b280 100644 --- a/Userland/Libraries/LibGfx/SystemTheme.h +++ b/Userland/Libraries/LibGfx/SystemTheme.h @@ -144,6 +144,7 @@ enum class MetricRole { enum class PathRole { NoRole, TitleButtonIcons, + WindowShadow, __Count, }; diff --git a/Userland/Libraries/LibGfx/WindowTheme.h b/Userland/Libraries/LibGfx/WindowTheme.h index 8ae0174723..73c0045458 100644 --- a/Userland/Libraries/LibGfx/WindowTheme.h +++ b/Userland/Libraries/LibGfx/WindowTheme.h @@ -61,6 +61,7 @@ public: virtual IntRect frame_rect_for_window(WindowType, const IntRect& window_rect, const Palette&) const = 0; virtual Vector layout_buttons(WindowType, const IntRect& window_rect, const Palette&, size_t buttons) const = 0; + virtual bool is_simple_rect_frame() const = 0; protected: WindowTheme() { } diff --git a/Userland/Services/WindowServer/Compositor.cpp b/Userland/Services/WindowServer/Compositor.cpp index 55e4f6a2ae..5564f25ef4 100644 --- a/Userland/Services/WindowServer/Compositor.cpp +++ b/Userland/Services/WindowServer/Compositor.cpp @@ -151,7 +151,7 @@ void Compositor::compose() // Mark window regions as dirty that need to be re-rendered wm.for_each_visible_window_from_back_to_front([&](Window& window) { - auto frame_rect = window.frame().rect(); + auto frame_rect = window.frame().render_rect(); for (auto& dirty_rect : dirty_screen_rects.rects()) { auto invalidate_rect = dirty_rect.intersected(frame_rect); if (!invalidate_rect.is_empty()) { @@ -173,12 +173,12 @@ void Compositor::compose() if (transparency_rects.is_empty()) return IterationDecision::Continue; - auto frame_rect = window.frame().rect(); + auto frame_rect = window.frame().render_rect(); auto& dirty_rects = window.dirty_rects(); wm.for_each_visible_window_from_back_to_front([&](Window& w) { if (&w == &window) return IterationDecision::Continue; - auto frame_rect2 = w.frame().rect(); + auto frame_rect2 = w.frame().render_rect(); if (!frame_rect2.intersects(frame_rect)) return IterationDecision::Continue; transparency_rects.for_each_intersected(w.dirty_rects(), [&](const Gfx::IntRect& intersected_dirty) { @@ -206,6 +206,9 @@ void Compositor::compose() auto cursor_rect = current_cursor_rect(); bool need_to_draw_cursor = false; + auto back_painter = *m_back_painter; + auto temp_painter = *m_temp_painter; + auto check_restore_cursor_back = [&](const Gfx::IntRect& rect) { if (!need_to_draw_cursor && rect.intersects(cursor_rect)) { // Restore what's behind the cursor if anything touches the area of the cursor @@ -225,26 +228,18 @@ void Compositor::compose() auto prepare_transparency_rect = [&](const Gfx::IntRect& rect) { dbgln_if(COMPOSE_DEBUG, " -> flush transparent: {}", rect); ASSERT(!flush_rects.intersects(rect)); - bool have_rect = false; for (auto& r : flush_transparent_rects.rects()) { - if (r == rect) { - have_rect = true; - break; - } + if (r == rect) + return; } - if (!have_rect) { - flush_transparent_rects.add(rect); - check_restore_cursor_back(rect); - } + flush_transparent_rects.add(rect); + check_restore_cursor_back(rect); }; if (!m_cursor_back_bitmap || m_invalidated_cursor) check_restore_cursor_back(cursor_rect); - auto back_painter = *m_back_painter; - auto temp_painter = *m_temp_painter; - auto paint_wallpaper = [&](Gfx::Painter& painter, const Gfx::IntRect& rect) { // FIXME: If the wallpaper is opaque and covers the whole rect, no need to fill with color! painter.fill_rect(rect, background_color); @@ -277,7 +272,7 @@ void Compositor::compose() }); auto compose_window = [&](Window& window) -> IterationDecision { - auto frame_rect = window.frame().rect(); + auto frame_rect = window.frame().render_rect(); if (!frame_rect.intersects(ws.rect())) return IterationDecision::Continue; auto frame_rects = frame_rect.shatter(window.rect()); @@ -849,7 +844,7 @@ bool Compositor::any_opaque_window_above_this_one_contains_rect(const Window& a_ return IterationDecision::Continue; if (!window.is_opaque()) return IterationDecision::Continue; - if (window.frame().rect().contains(rect)) { + if (window.frame().render_rect().contains(rect)) { found_containing_window = true; return IterationDecision::Break; } @@ -907,7 +902,7 @@ void Compositor::recompute_occlusions() Gfx::DisjointRectSet visible_rects(screen_rect); bool have_transparent = false; WindowManager::the().for_each_visible_window_from_front_to_back([&](Window& w) { - auto window_frame_rect = w.frame().rect().intersected(screen_rect); + auto window_frame_rect = w.frame().render_rect().intersected(screen_rect); w.transparency_wallpaper_rects().clear(); auto& visible_opaque = w.opaque_rects(); auto& transparency_rects = w.transparency_rects(); @@ -948,7 +943,7 @@ void Compositor::recompute_occlusions() if (w2.is_minimized()) return IterationDecision::Continue; - auto window_frame_rect2 = w2.frame().rect().intersected(screen_rect); + auto window_frame_rect2 = w2.frame().render_rect().intersected(screen_rect); auto covering_rect = window_frame_rect2.intersected(window_frame_rect); if (covering_rect.is_empty()) return IterationDecision::Continue; @@ -1026,7 +1021,7 @@ void Compositor::recompute_occlusions() // Determine what transparent window areas need to render the wallpaper first WindowManager::the().for_each_visible_window_from_back_to_front([&](Window& w) { auto& transparency_wallpaper_rects = w.transparency_wallpaper_rects(); - if ((w.is_opaque() && w.frame().is_opaque()) || w.is_minimized()) { + if (w.is_minimized()) { transparency_wallpaper_rects.clear(); return IterationDecision::Continue; } @@ -1053,7 +1048,7 @@ void Compositor::recompute_occlusions() } wm.for_each_visible_window_from_back_to_front([&](Window& w) { - auto window_frame_rect = w.frame().rect().intersected(screen_rect); + auto window_frame_rect = w.frame().render_rect().intersected(screen_rect); if (w.is_minimized() || window_frame_rect.is_empty()) return IterationDecision::Continue; diff --git a/Userland/Services/WindowServer/Window.cpp b/Userland/Services/WindowServer/Window.cpp index 50a37ddc4a..2d8889c7ea 100644 --- a/Userland/Services/WindowServer/Window.cpp +++ b/Userland/Services/WindowServer/Window.cpp @@ -268,7 +268,7 @@ void Window::set_minimized(bool minimized) m_minimized = minimized; update_menu_item_text(PopupMenuItem::Minimize); Compositor::the().invalidate_occlusions(); - Compositor::the().invalidate_screen(frame().rect()); + Compositor::the().invalidate_screen(frame().render_rect()); if (!blocking_modal_window()) start_minimize_animation(); if (!minimized) @@ -323,7 +323,7 @@ void Window::set_opacity(float opacity) m_opacity = opacity; if (was_opaque != is_opaque()) Compositor::the().invalidate_occlusions(); - Compositor::the().invalidate_screen(frame().rect()); + Compositor::the().invalidate_screen(frame().render_rect()); WindowManager::the().notify_opacity_changed(*this); } @@ -449,14 +449,17 @@ void Window::set_visible(bool b) if (m_visible) invalidate(true); else - Compositor::the().invalidate_screen(frame().rect()); + Compositor::the().invalidate_screen(frame().render_rect()); } void Window::invalidate(bool invalidate_frame) { m_invalidated = true; m_invalidated_all = true; - m_invalidated_frame |= invalidate_frame; + if (invalidate_frame && !m_invalidated_frame) { + m_invalidated_frame = true; + frame().set_dirty(); + } m_dirty_rects.clear(); Compositor::the().invalidate_window(); } @@ -477,11 +480,14 @@ bool Window::invalidate_no_notify(const Gfx::IntRect& rect, bool with_frame) if (rect.is_empty()) return false; if (m_invalidated_all) { - m_invalidated_frame |= with_frame; + if (with_frame && !m_invalidated_frame) { + m_invalidated_frame = true; + frame().set_dirty(); + } return false; } - auto outer_rect = frame().rect(); + auto outer_rect = frame().render_rect(); auto inner_rect = rect; inner_rect.move_by(position()); // FIXME: This seems slightly wrong; the inner rect shouldn't intersect the border part of the outer rect. @@ -490,7 +496,10 @@ bool Window::invalidate_no_notify(const Gfx::IntRect& rect, bool with_frame) return false; m_invalidated = true; - m_invalidated_frame |= with_frame; + if (with_frame && !m_invalidated_frame) { + m_invalidated_frame = true; + frame().set_dirty(); + } m_dirty_rects.add(inner_rect.translated(-outer_rect.location())); return true; } @@ -499,11 +508,11 @@ void Window::prepare_dirty_rects() { if (m_invalidated_all) { if (m_invalidated_frame) - m_dirty_rects = frame().rect(); + m_dirty_rects = frame().render_rect(); else m_dirty_rects = rect(); } else { - m_dirty_rects.move_by(frame().rect().location()); + m_dirty_rects.move_by(frame().render_rect().location()); } } diff --git a/Userland/Services/WindowServer/WindowFrame.cpp b/Userland/Services/WindowServer/WindowFrame.cpp index 77b1863ffc..fde785ee60 100644 --- a/Userland/Services/WindowServer/WindowFrame.cpp +++ b/Userland/Services/WindowServer/WindowFrame.cpp @@ -56,10 +56,20 @@ static Gfx::Bitmap* s_minimize_icon; static Gfx::Bitmap* s_maximize_icon; static Gfx::Bitmap* s_restore_icon; static Gfx::Bitmap* s_close_icon; +static Gfx::Bitmap* s_window_shadow; static String s_last_title_button_icons_path; static int s_last_title_button_icons_scale; +static String s_last_window_shadow_path; + +static Gfx::IntRect frame_rect_for_window(Window& window, const Gfx::IntRect& rect) +{ + if (window.is_frameless()) + return rect; + return Gfx::WindowTheme::current().frame_rect_for_window(to_theme_window_type(window.type()), rect, WindowManager::the().palette()); +} + WindowFrame::WindowFrame(Window& window) : m_window(window) { @@ -94,6 +104,7 @@ WindowFrame::~WindowFrame() void WindowFrame::set_button_icons() { + m_dirty = true; if (m_window.is_frameless()) return; @@ -104,6 +115,8 @@ void WindowFrame::set_button_icons() if (!s_minimize_icon || s_last_title_button_icons_path != icons_path || s_last_title_button_icons_scale != icons_scale) { full_path.append(icons_path); full_path.append("window-minimize.png"); + if (s_minimize_icon) + s_minimize_icon->unref(); if (!(s_minimize_icon = Gfx::Bitmap::load_from_file(full_path.to_string(), icons_scale).leak_ref())) s_minimize_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/downward-triangle.png", icons_scale).leak_ref(); full_path.clear(); @@ -111,6 +124,8 @@ void WindowFrame::set_button_icons() if (!s_maximize_icon || s_last_title_button_icons_path != icons_path || s_last_title_button_icons_scale != icons_scale) { full_path.append(icons_path); full_path.append("window-maximize.png"); + if (s_maximize_icon) + s_maximize_icon->unref(); if (!(s_maximize_icon = Gfx::Bitmap::load_from_file(full_path.to_string(), icons_scale).leak_ref())) s_maximize_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/upward-triangle.png", icons_scale).leak_ref(); full_path.clear(); @@ -118,6 +133,8 @@ void WindowFrame::set_button_icons() if (!s_restore_icon || s_last_title_button_icons_path != icons_path || s_last_title_button_icons_scale != icons_scale) { full_path.append(icons_path); full_path.append("window-restore.png"); + if (s_restore_icon) + s_restore_icon->unref(); if (!(s_restore_icon = Gfx::Bitmap::load_from_file(full_path.to_string(), icons_scale).leak_ref())) s_restore_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/window-restore.png", icons_scale).leak_ref(); full_path.clear(); @@ -125,6 +142,8 @@ void WindowFrame::set_button_icons() if (!s_close_icon || s_last_title_button_icons_path != icons_path || s_last_title_button_icons_scale != icons_scale) { full_path.append(icons_path); full_path.append("window-close.png"); + if (s_close_icon) + s_close_icon->unref(); if (!(s_close_icon = Gfx::Bitmap::load_from_file(full_path.to_string(), icons_scale).leak_ref())) s_close_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/window-close.png", icons_scale).leak_ref(); full_path.clear(); @@ -138,6 +157,26 @@ void WindowFrame::set_button_icons() s_last_title_button_icons_path = icons_path; s_last_title_button_icons_scale = icons_scale; + + String window_shadow_path = WindowManager::the().palette().window_shadow_path(); + if (!s_window_shadow || s_window_shadow->scale() != icons_scale || s_last_window_shadow_path != window_shadow_path) { + s_window_shadow = Gfx::Bitmap::load_from_file(window_shadow_path, icons_scale).leak_ref(); + } + s_last_window_shadow_path = window_shadow_path; +} + +Gfx::Bitmap* WindowFrame::window_shadow() const +{ + if (m_window.type() == WindowType::Desktop) + return nullptr; + return s_window_shadow; +} + +bool WindowFrame::frame_has_alpha() const +{ + if (auto* shadow_bitmap = window_shadow(); shadow_bitmap && shadow_bitmap->format() == Gfx::BitmapFormat::RGBA32) + return true; + return false; } void WindowFrame::did_set_maximized(Badge, bool maximized) @@ -205,7 +244,7 @@ void WindowFrame::paint(Gfx::Painter& painter, const Gfx::IntRect& rect) { render_to_cache(); - auto frame_rect = this->rect(); + auto frame_rect = render_rect(); auto window_rect = m_window.rect(); if (m_top_bottom) { @@ -265,48 +304,62 @@ void WindowFrame::render_to_cache() { if (!m_dirty) return; - m_dirty = true; + m_dirty = false; - static RefPtr s_tmp_bitmap; + static Gfx::Bitmap* s_tmp_bitmap; auto frame_rect = rect(); + auto total_frame_rect = frame_rect; + auto* shadow_bitmap = inflate_for_shadow(total_frame_rect, m_shadow_offset); auto window_rect = m_window.rect(); auto scale = Screen::the().scale_factor(); - if (!s_tmp_bitmap || !s_tmp_bitmap->size().contains(frame_rect.size()) || s_tmp_bitmap->scale() != scale) - s_tmp_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::RGBA32, frame_rect.size(), scale); + if (!s_tmp_bitmap || !s_tmp_bitmap->size().contains(total_frame_rect.size()) || s_tmp_bitmap->scale() != scale) { + if (s_tmp_bitmap) + s_tmp_bitmap->unref(); + s_tmp_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::RGBA32, total_frame_rect.size(), scale).leak_ref(); + } Gfx::Painter painter(*s_tmp_bitmap); - painter.fill_rect({ { 0, 0 }, frame_rect.size() }, Color::White); - render(painter); + for (auto& rect : total_frame_rect.shatter(window_rect)) + painter.clear_rect({ rect.location() - total_frame_rect.location(), rect.size() }, { 255, 255, 255, 0 }); - auto top_bottom_height = frame_rect.height() - window_rect.height(); + if (shadow_bitmap) + paint_simple_rect_shadow(painter, { { 0, 0 }, total_frame_rect.size() }, *shadow_bitmap); + + { + Gfx::PainterStateSaver save(painter); + painter.translate(m_shadow_offset); + render(painter); + } + + auto top_bottom_height = total_frame_rect.height() - window_rect.height(); if (top_bottom_height > 0) { - if (!m_top_bottom || m_top_bottom->width() != frame_rect.width() || m_top_bottom->height() != top_bottom_height || m_top_bottom->scale() != scale) - m_top_bottom = Gfx::Bitmap::create(Gfx::BitmapFormat::RGBA32, { frame_rect.width(), top_bottom_height }, scale); - m_bottom_y = window_rect.y() - frame_rect.y(); + if (!m_top_bottom || m_top_bottom->width() != total_frame_rect.width() || m_top_bottom->height() != top_bottom_height || m_top_bottom->scale() != scale) + m_top_bottom = Gfx::Bitmap::create(Gfx::BitmapFormat::RGBA32, { total_frame_rect.width(), top_bottom_height }, scale); + m_bottom_y = window_rect.y() - total_frame_rect.y(); ASSERT(m_bottom_y >= 0); Gfx::Painter top_bottom_painter(*m_top_bottom); if (m_bottom_y > 0) - top_bottom_painter.blit({ 0, 0 }, *s_tmp_bitmap, { 0, 0, frame_rect.width(), m_bottom_y }); + top_bottom_painter.blit({ 0, 0 }, *s_tmp_bitmap, { 0, 0, total_frame_rect.width(), m_bottom_y }, 1.0, false); if (m_bottom_y < top_bottom_height) - top_bottom_painter.blit({ 0, m_bottom_y }, *s_tmp_bitmap, { 0, frame_rect.height() - (frame_rect.bottom() - window_rect.bottom()), frame_rect.width(), top_bottom_height - m_bottom_y }); + top_bottom_painter.blit({ 0, m_bottom_y }, *s_tmp_bitmap, { 0, total_frame_rect.height() - (total_frame_rect.bottom() - window_rect.bottom()), total_frame_rect.width(), top_bottom_height - m_bottom_y }, 1.0, false); } else { m_top_bottom = nullptr; m_bottom_y = 0; } - auto left_right_width = frame_rect.width() - window_rect.width(); + auto left_right_width = total_frame_rect.width() - window_rect.width(); if (left_right_width > 0) { - if (!m_left_right || m_left_right->height() != frame_rect.height() || m_left_right->width() != left_right_width || m_left_right->scale() != scale) - m_left_right = Gfx::Bitmap::create(Gfx::BitmapFormat::RGBA32, { left_right_width, frame_rect.height() }, scale); - m_right_x = window_rect.x() - frame_rect.x(); + if (!m_left_right || m_left_right->height() != total_frame_rect.height() || m_left_right->width() != left_right_width || m_left_right->scale() != scale) + m_left_right = Gfx::Bitmap::create(Gfx::BitmapFormat::RGBA32, { left_right_width, total_frame_rect.height() }, scale); + m_right_x = window_rect.x() - total_frame_rect.x(); ASSERT(m_right_x >= 0); Gfx::Painter left_right_painter(*m_left_right); if (m_right_x > 0) - left_right_painter.blit({ 0, 0 }, *s_tmp_bitmap, { 0, m_bottom_y, m_right_x, window_rect.height() }); + left_right_painter.blit({ 0, 0 }, *s_tmp_bitmap, { 0, m_bottom_y, m_right_x, window_rect.height() }, 1.0, false); if (m_right_x < left_right_width) - left_right_painter.blit({ m_right_x, 0 }, *s_tmp_bitmap, { (window_rect.right() - frame_rect.x()) + 1, m_bottom_y, frame_rect.width() - (frame_rect.right() - window_rect.right()), window_rect.height() }); + left_right_painter.blit({ m_right_x, 0 }, *s_tmp_bitmap, { (window_rect.right() - total_frame_rect.x()) + 1, m_bottom_y, total_frame_rect.width() - (total_frame_rect.right() - window_rect.right()), window_rect.height() }, 1.0, false); } else { m_left_right = nullptr; m_right_x = 0; @@ -321,30 +374,45 @@ void WindowFrame::set_opacity(float opacity) m_opacity = opacity; if (was_opaque != is_opaque()) Compositor::the().invalidate_occlusions(); - Compositor::the().invalidate_screen(rect()); + Compositor::the().invalidate_screen(render_rect()); WindowManager::the().notify_opacity_changed(m_window); } -static Gfx::IntRect frame_rect_for_window(Window& window, const Gfx::IntRect& rect) +Gfx::IntRect WindowFrame::inflated_for_shadow(const Gfx::IntRect& frame_rect) const { - if (window.is_frameless()) - return rect; - return Gfx::WindowTheme::current().frame_rect_for_window(to_theme_window_type(window.type()), rect, WindowManager::the().palette()); + if (auto* shadow = window_shadow()) { + auto total_shadow_size = shadow->height(); + return frame_rect.inflated(total_shadow_size, total_shadow_size); + } + return frame_rect; } -static Gfx::IntRect frame_rect_for_window(Window& window) +Gfx::Bitmap* WindowFrame::inflate_for_shadow(Gfx::IntRect& frame_rect, Gfx::IntPoint& shadow_offset) const { - return frame_rect_for_window(window, window.rect()); + auto* shadow = window_shadow(); + if (shadow) { + auto total_shadow_size = shadow->height(); + frame_rect.inflate(total_shadow_size, total_shadow_size); + auto offset = total_shadow_size / 2; + shadow_offset = { offset, offset }; + } else { + shadow_offset = {}; + } + return shadow; } Gfx::IntRect WindowFrame::rect() const { - return frame_rect_for_window(m_window); + return frame_rect_for_window(m_window, m_window.rect()); +} + +Gfx::IntRect WindowFrame::render_rect() const +{ + return inflated_for_shadow(rect()); } void WindowFrame::invalidate_title_bar() { - m_dirty = true; invalidate(title_bar_rect()); } @@ -353,6 +421,7 @@ void WindowFrame::invalidate(Gfx::IntRect relative_rect) auto frame_rect = rect(); auto window_rect = m_window.rect(); relative_rect.move_by(frame_rect.x() - window_rect.x(), frame_rect.y() - window_rect.y()); + m_dirty = true; m_window.invalidate(relative_rect, true); } @@ -360,15 +429,15 @@ void WindowFrame::notify_window_rect_changed(const Gfx::IntRect& old_rect, const { layout_buttons(); - auto old_frame_rect = frame_rect_for_window(m_window, old_rect); - auto new_frame_rect = frame_rect_for_window(m_window, new_rect); - if (old_frame_rect.width() != new_frame_rect.width()) + auto old_frame_rect = inflated_for_shadow(frame_rect_for_window(m_window, old_rect)); + auto new_frame_rect = inflated_for_shadow(frame_rect_for_window(m_window, new_rect)); + if (old_frame_rect.size() != new_frame_rect.size()) m_dirty = true; auto& compositor = Compositor::the(); - for (auto& dirty : old_frame_rect.shatter(rect())) + for (auto& dirty : old_frame_rect.shatter(new_frame_rect)) compositor.invalidate_screen(dirty); if (!m_window.is_opaque()) - compositor.invalidate_screen(rect()); + compositor.invalidate_screen(new_frame_rect); compositor.invalidate_occlusions(); @@ -491,4 +560,79 @@ void WindowFrame::start_flash_animation() m_flash_timer->start(); } +void WindowFrame::paint_simple_rect_shadow(Gfx::Painter& painter, const Gfx::IntRect& containing_rect, const Gfx::Bitmap& shadow_bitmap) const +{ + // The layout of the shadow_bitmap is defined like this: + // +---------+----+---------+----+----+----+ + // | TL | T | TR | LT | L | LB | + // +---------+----+---------+----+----+----+ + // | BL | B | BR | RT | R | RB | + // +---------+----+---------+----+----+----+ + // Located strictly on the top or bottom of the rectangle, above or below of the content: + // TL = top-left T = top TR = top-right + // BL = bottom-left B = bottom BR = bottom-right + // Located on the left or right of the rectangle, but not above or below of the content: + // LT = left-top L = left LB = left-bottom + // RT = right-top R = right RB = right-bottom + // So, the bitmap has two rows and 6 column, two of which are twice as wide. + // The height divided by two defines a cell size, and width of each + // column must be the same as the height of the cell, except for the + // first an third column, which are twice as wide. + if (shadow_bitmap.height() % 2 != 0) { + dbgln("Can't paint simple rect shadow, shadow bitmap height {} is not even", shadow_bitmap.height()); + return; + } + auto base_size = shadow_bitmap.height() / 2; + if (shadow_bitmap.width() != base_size * (6 + 2)) { + if (shadow_bitmap.width() % base_size != 0) + dbgln("Can't paint simple rect shadow, shadow bitmap width {} is not a multiple of {}", shadow_bitmap.width(), base_size); + else + dbgln("Can't paint simple rect shadow, shadow bitmap width {} but expected {}", shadow_bitmap.width(), base_size * (6 + 2)); + return; + } + + // The containing_rect should have been inflated appropriately + ASSERT(containing_rect.size().contains(Gfx::IntSize { base_size, base_size })); + + auto half_width = containing_rect.width() / 2; + auto paint_horizontal = [&](int y, int src_row) { + Gfx::PainterStateSaver save(painter); + painter.add_clip_rect({ containing_rect.left(), y, containing_rect.width(), base_size }); + int corner_piece_width = base_size * 2; + int left_corners_right = min(half_width, corner_piece_width); + int right_corners_left = max(half_width, containing_rect.width() - corner_piece_width); + painter.blit({ containing_rect.left() + left_corners_right - corner_piece_width, y }, shadow_bitmap, { 0, src_row * base_size, corner_piece_width, base_size }); + painter.blit({ containing_rect.left() + right_corners_left, y }, shadow_bitmap, { corner_piece_width + base_size, src_row * base_size, corner_piece_width, base_size }); + for (int x = left_corners_right; x < right_corners_left; x += base_size) { + auto width = min(right_corners_left - x, base_size); + painter.blit({ containing_rect.left() + x, y }, shadow_bitmap, { corner_piece_width, src_row * base_size, width, base_size }); + } + }; + + paint_horizontal(containing_rect.top(), 0); + paint_horizontal(containing_rect.bottom() - base_size + 1, 1); + + auto sides_height = containing_rect.height() - 2 * base_size; + auto half_height = sides_height / 2; + auto paint_vertical = [&](int x, int src_row) { + Gfx::PainterStateSaver save(painter); + painter.add_clip_rect({ x, containing_rect.y() + base_size, base_size, containing_rect.height() - 2 * base_size }); + int top_corners_bottom = base_size + min(half_height, base_size); + int top_corner_height = top_corners_bottom - base_size; + int bottom_corners_top = base_size + max(half_height, sides_height - base_size); + int bottom_corner_height = sides_height + base_size - bottom_corners_top; + painter.blit({ x, containing_rect.top() + top_corners_bottom - top_corner_height }, shadow_bitmap, { base_size * 5, src_row * base_size, base_size, top_corner_height }); + painter.blit({ x, containing_rect.top() + bottom_corners_top }, shadow_bitmap, { base_size * 7, src_row * base_size + base_size - bottom_corner_height, base_size, bottom_corner_height }); + if (sides_height > 2 * base_size) { + for (int y = top_corners_bottom; y < bottom_corners_top; y += base_size) { + auto height = min(bottom_corners_top - y, base_size); + painter.blit({ x, containing_rect.top() + y }, shadow_bitmap, { base_size * 6, src_row * base_size, base_size, height }); + } + } + }; + + paint_vertical(containing_rect.left(), 0); + paint_vertical(containing_rect.right() - base_size + 1, 1); +} + } diff --git a/Userland/Services/WindowServer/WindowFrame.h b/Userland/Services/WindowServer/WindowFrame.h index 808bc1be82..6cd8917855 100644 --- a/Userland/Services/WindowServer/WindowFrame.h +++ b/Userland/Services/WindowServer/WindowFrame.h @@ -45,6 +45,7 @@ public: ~WindowFrame(); Gfx::IntRect rect() const; + Gfx::IntRect render_rect() const; void paint(Gfx::Painter&, const Gfx::IntRect&); void render(Gfx::Painter&); void render_to_cache(); @@ -64,7 +65,7 @@ public: void start_flash_animation(); - bool has_alpha_channel() const { return m_has_alpha_channel; } + bool has_alpha_channel() const { return m_has_alpha_channel || frame_has_alpha(); } void set_has_alpha_channel(bool value) { m_has_alpha_channel = value; } void set_opacity(float); @@ -79,14 +80,19 @@ public: return true; } - void scale_changed() + void set_dirty() { m_dirty = true; } private: + void paint_simple_rect_shadow(Gfx::Painter&, const Gfx::IntRect&, const Gfx::Bitmap&) const; void paint_notification_frame(Gfx::Painter&); void paint_normal_frame(Gfx::Painter&); + Gfx::Bitmap* window_shadow() const; + bool frame_has_alpha() const; + Gfx::IntRect inflated_for_shadow(const Gfx::IntRect&) const; + Gfx::Bitmap* inflate_for_shadow(Gfx::IntRect&, Gfx::IntPoint&) const; Gfx::WindowTheme::WindowState window_state_for_theme() const; @@ -96,6 +102,8 @@ private: Button* m_maximize_button { nullptr }; Button* m_minimize_button { nullptr }; + Gfx::IntPoint m_shadow_offset {}; + RefPtr m_top_bottom; RefPtr m_left_right; int m_bottom_y { 0 }; // y-offset in m_top_bottom for the bottom half diff --git a/Userland/Services/WindowServer/WindowManager.cpp b/Userland/Services/WindowServer/WindowManager.cpp index 2c52c695c0..9736908298 100644 --- a/Userland/Services/WindowServer/WindowManager.cpp +++ b/Userland/Services/WindowServer/WindowManager.cpp @@ -270,7 +270,7 @@ void WindowManager::remove_window(Window& window) if (active == &window || active_input == &window || (active && window.is_descendant_of(*active)) || (active_input && active_input != active && window.is_descendant_of(*active_input))) pick_new_active_window(&window); - Compositor::the().invalidate_screen(window.frame().rect()); + Compositor::the().invalidate_screen(window.frame().render_rect()); if (m_switcher.is_visible() && window.type() != WindowType::WindowSwitcher) m_switcher.refresh(); @@ -1517,7 +1517,6 @@ void WindowManager::reload_icon_bitmaps_after_scale_change(bool allow_hidpi_icon for_each_window([&](Window& window) { auto& window_frame = window.frame(); window_frame.set_button_icons(); - window_frame.scale_changed(); return IterationDecision::Continue; }); }