Pārlūkot izejas kodu

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.
Tom 4 gadi atpakaļ
vecāks
revīzija
0ce4b9d7db

BIN
Base/res/icons/themes/Default/window-shadow.png


+ 4 - 0
Base/res/themes/Default.ini

@@ -71,3 +71,7 @@ TooltipText=black
 TitleHeight=19
 TitleHeight=19
 TitleButtonWidth=15
 TitleButtonWidth=15
 TitleButtonHeight=15
 TitleButtonHeight=15
+
+[Paths]
+WindowShadow=/res/icons/themes/Default/window-shadow.png
+

+ 1 - 0
Userland/Libraries/LibGfx/ClassicWindowTheme.h

@@ -47,6 +47,7 @@ public:
     virtual IntRect frame_rect_for_window(WindowType, const IntRect& window_rect, const Palette&) const override;
     virtual IntRect frame_rect_for_window(WindowType, const IntRect& window_rect, const Palette&) const override;
 
 
     virtual Vector<IntRect> layout_buttons(WindowType, const IntRect& window_rect, const Palette&, size_t buttons) const override;
     virtual Vector<IntRect> 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:
 private:
     struct FrameColors {
     struct FrameColors {

+ 2 - 2
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");
     ASSERT(scale() >= source.scale() && "painter doesn't support downsampling scale factors");
 
 
     if (opacity < 1.0f)
     if (opacity < 1.0f)
         return blit_with_opacity(position, source, a_src_rect, opacity);
         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);
         return blit_with_alpha(position, source, a_src_rect);
 
 
     auto safe_src_rect = a_src_rect.intersected(source.rect());
     auto safe_src_rect = a_src_rect.intersected(source.rect());

+ 1 - 1
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_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_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 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_dimmed(const IntPoint&, const Gfx::Bitmap&, const IntRect& src_rect);
     void blit_brightened(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<Color(Color)>);
     void blit_filtered(const IntPoint&, const Gfx::Bitmap&, const IntRect& src_rect, Function<Color(Color)>);

+ 1 - 0
Userland/Libraries/LibGfx/Palette.h

@@ -140,6 +140,7 @@ public:
     int window_title_button_height() const { return metric(MetricRole::TitleButtonHeight); }
     int window_title_button_height() const { return metric(MetricRole::TitleButtonHeight); }
 
 
     String title_button_icons_path() const { return path(PathRole::TitleButtonIcons); }
     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); }
     Color color(ColorRole role) const { return m_impl->color(role); }
     int metric(MetricRole role) const { return m_impl->metric(role); }
     int metric(MetricRole role) const { return m_impl->metric(role); }

+ 1 - 0
Userland/Libraries/LibGfx/SystemTheme.cpp

@@ -119,6 +119,7 @@ Core::AnonymousBuffer load_system_theme(const String& path)
     } while (0)
     } while (0)
 
 
     DO_PATH(TitleButtonIcons);
     DO_PATH(TitleButtonIcons);
+    DO_PATH(WindowShadow);
 
 
     return buffer;
     return buffer;
 }
 }

+ 1 - 0
Userland/Libraries/LibGfx/SystemTheme.h

@@ -144,6 +144,7 @@ enum class MetricRole {
 enum class PathRole {
 enum class PathRole {
     NoRole,
     NoRole,
     TitleButtonIcons,
     TitleButtonIcons,
+    WindowShadow,
     __Count,
     __Count,
 };
 };
 
 

+ 1 - 0
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 IntRect frame_rect_for_window(WindowType, const IntRect& window_rect, const Palette&) const = 0;
 
 
     virtual Vector<IntRect> layout_buttons(WindowType, const IntRect& window_rect, const Palette&, size_t buttons) const = 0;
     virtual Vector<IntRect> layout_buttons(WindowType, const IntRect& window_rect, const Palette&, size_t buttons) const = 0;
+    virtual bool is_simple_rect_frame() const = 0;
 
 
 protected:
 protected:
     WindowTheme() { }
     WindowTheme() { }

+ 16 - 21
Userland/Services/WindowServer/Compositor.cpp

@@ -151,7 +151,7 @@ void Compositor::compose()
 
 
     // Mark window regions as dirty that need to be re-rendered
     // Mark window regions as dirty that need to be re-rendered
     wm.for_each_visible_window_from_back_to_front([&](Window& window) {
     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()) {
         for (auto& dirty_rect : dirty_screen_rects.rects()) {
             auto invalidate_rect = dirty_rect.intersected(frame_rect);
             auto invalidate_rect = dirty_rect.intersected(frame_rect);
             if (!invalidate_rect.is_empty()) {
             if (!invalidate_rect.is_empty()) {
@@ -173,12 +173,12 @@ void Compositor::compose()
         if (transparency_rects.is_empty())
         if (transparency_rects.is_empty())
             return IterationDecision::Continue;
             return IterationDecision::Continue;
 
 
-        auto frame_rect = window.frame().rect();
+        auto frame_rect = window.frame().render_rect();
         auto& dirty_rects = window.dirty_rects();
         auto& dirty_rects = window.dirty_rects();
         wm.for_each_visible_window_from_back_to_front([&](Window& w) {
         wm.for_each_visible_window_from_back_to_front([&](Window& w) {
             if (&w == &window)
             if (&w == &window)
                 return IterationDecision::Continue;
                 return IterationDecision::Continue;
-            auto frame_rect2 = w.frame().rect();
+            auto frame_rect2 = w.frame().render_rect();
             if (!frame_rect2.intersects(frame_rect))
             if (!frame_rect2.intersects(frame_rect))
                 return IterationDecision::Continue;
                 return IterationDecision::Continue;
             transparency_rects.for_each_intersected(w.dirty_rects(), [&](const Gfx::IntRect& intersected_dirty) {
             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();
     auto cursor_rect = current_cursor_rect();
     bool need_to_draw_cursor = false;
     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) {
     auto check_restore_cursor_back = [&](const Gfx::IntRect& rect) {
         if (!need_to_draw_cursor && rect.intersects(cursor_rect)) {
         if (!need_to_draw_cursor && rect.intersects(cursor_rect)) {
             // Restore what's behind the cursor if anything touches the area of the cursor
             // 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) {
     auto prepare_transparency_rect = [&](const Gfx::IntRect& rect) {
         dbgln_if(COMPOSE_DEBUG, "   -> flush transparent: {}", rect);
         dbgln_if(COMPOSE_DEBUG, "   -> flush transparent: {}", rect);
         ASSERT(!flush_rects.intersects(rect));
         ASSERT(!flush_rects.intersects(rect));
-        bool have_rect = false;
         for (auto& r : flush_transparent_rects.rects()) {
         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)
     if (!m_cursor_back_bitmap || m_invalidated_cursor)
         check_restore_cursor_back(cursor_rect);
         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) {
     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!
         // FIXME: If the wallpaper is opaque and covers the whole rect, no need to fill with color!
         painter.fill_rect(rect, background_color);
         painter.fill_rect(rect, background_color);
@@ -277,7 +272,7 @@ void Compositor::compose()
     });
     });
 
 
     auto compose_window = [&](Window& window) -> IterationDecision {
     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()))
         if (!frame_rect.intersects(ws.rect()))
             return IterationDecision::Continue;
             return IterationDecision::Continue;
         auto frame_rects = frame_rect.shatter(window.rect());
         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;
             return IterationDecision::Continue;
         if (!window.is_opaque())
         if (!window.is_opaque())
             return IterationDecision::Continue;
             return IterationDecision::Continue;
-        if (window.frame().rect().contains(rect)) {
+        if (window.frame().render_rect().contains(rect)) {
             found_containing_window = true;
             found_containing_window = true;
             return IterationDecision::Break;
             return IterationDecision::Break;
         }
         }
@@ -907,7 +902,7 @@ void Compositor::recompute_occlusions()
         Gfx::DisjointRectSet visible_rects(screen_rect);
         Gfx::DisjointRectSet visible_rects(screen_rect);
         bool have_transparent = false;
         bool have_transparent = false;
         WindowManager::the().for_each_visible_window_from_front_to_back([&](Window& w) {
         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();
             w.transparency_wallpaper_rects().clear();
             auto& visible_opaque = w.opaque_rects();
             auto& visible_opaque = w.opaque_rects();
             auto& transparency_rects = w.transparency_rects();
             auto& transparency_rects = w.transparency_rects();
@@ -948,7 +943,7 @@ void Compositor::recompute_occlusions()
 
 
                 if (w2.is_minimized())
                 if (w2.is_minimized())
                     return IterationDecision::Continue;
                     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);
                 auto covering_rect = window_frame_rect2.intersected(window_frame_rect);
                 if (covering_rect.is_empty())
                 if (covering_rect.is_empty())
                     return IterationDecision::Continue;
                     return IterationDecision::Continue;
@@ -1026,7 +1021,7 @@ void Compositor::recompute_occlusions()
             // Determine what transparent window areas need to render the wallpaper first
             // Determine what transparent window areas need to render the wallpaper first
             WindowManager::the().for_each_visible_window_from_back_to_front([&](Window& w) {
             WindowManager::the().for_each_visible_window_from_back_to_front([&](Window& w) {
                 auto& transparency_wallpaper_rects = w.transparency_wallpaper_rects();
                 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();
                     transparency_wallpaper_rects.clear();
                     return IterationDecision::Continue;
                     return IterationDecision::Continue;
                 }
                 }
@@ -1053,7 +1048,7 @@ void Compositor::recompute_occlusions()
     }
     }
 
 
     wm.for_each_visible_window_from_back_to_front([&](Window& w) {
     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())
         if (w.is_minimized() || window_frame_rect.is_empty())
             return IterationDecision::Continue;
             return IterationDecision::Continue;
 
 

+ 18 - 9
Userland/Services/WindowServer/Window.cpp

@@ -268,7 +268,7 @@ void Window::set_minimized(bool minimized)
     m_minimized = minimized;
     m_minimized = minimized;
     update_menu_item_text(PopupMenuItem::Minimize);
     update_menu_item_text(PopupMenuItem::Minimize);
     Compositor::the().invalidate_occlusions();
     Compositor::the().invalidate_occlusions();
-    Compositor::the().invalidate_screen(frame().rect());
+    Compositor::the().invalidate_screen(frame().render_rect());
     if (!blocking_modal_window())
     if (!blocking_modal_window())
         start_minimize_animation();
         start_minimize_animation();
     if (!minimized)
     if (!minimized)
@@ -323,7 +323,7 @@ void Window::set_opacity(float opacity)
     m_opacity = opacity;
     m_opacity = opacity;
     if (was_opaque != is_opaque())
     if (was_opaque != is_opaque())
         Compositor::the().invalidate_occlusions();
         Compositor::the().invalidate_occlusions();
-    Compositor::the().invalidate_screen(frame().rect());
+    Compositor::the().invalidate_screen(frame().render_rect());
     WindowManager::the().notify_opacity_changed(*this);
     WindowManager::the().notify_opacity_changed(*this);
 }
 }
 
 
@@ -449,14 +449,17 @@ void Window::set_visible(bool b)
     if (m_visible)
     if (m_visible)
         invalidate(true);
         invalidate(true);
     else
     else
-        Compositor::the().invalidate_screen(frame().rect());
+        Compositor::the().invalidate_screen(frame().render_rect());
 }
 }
 
 
 void Window::invalidate(bool invalidate_frame)
 void Window::invalidate(bool invalidate_frame)
 {
 {
     m_invalidated = true;
     m_invalidated = true;
     m_invalidated_all = 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();
     m_dirty_rects.clear();
     Compositor::the().invalidate_window();
     Compositor::the().invalidate_window();
 }
 }
@@ -477,11 +480,14 @@ bool Window::invalidate_no_notify(const Gfx::IntRect& rect, bool with_frame)
     if (rect.is_empty())
     if (rect.is_empty())
         return false;
         return false;
     if (m_invalidated_all) {
     if (m_invalidated_all) {
-        m_invalidated_frame |= with_frame;
+        if (with_frame && !m_invalidated_frame) {
+            m_invalidated_frame = true;
+            frame().set_dirty();
+        }
         return false;
         return false;
     }
     }
 
 
-    auto outer_rect = frame().rect();
+    auto outer_rect = frame().render_rect();
     auto inner_rect = rect;
     auto inner_rect = rect;
     inner_rect.move_by(position());
     inner_rect.move_by(position());
     // FIXME: This seems slightly wrong; the inner rect shouldn't intersect the border part of the outer rect.
     // 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;
         return false;
 
 
     m_invalidated = true;
     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()));
     m_dirty_rects.add(inner_rect.translated(-outer_rect.location()));
     return true;
     return true;
 }
 }
@@ -499,11 +508,11 @@ void Window::prepare_dirty_rects()
 {
 {
     if (m_invalidated_all) {
     if (m_invalidated_all) {
         if (m_invalidated_frame)
         if (m_invalidated_frame)
-            m_dirty_rects = frame().rect();
+            m_dirty_rects = frame().render_rect();
         else
         else
             m_dirty_rects = rect();
             m_dirty_rects = rect();
     } else {
     } else {
-        m_dirty_rects.move_by(frame().rect().location());
+        m_dirty_rects.move_by(frame().render_rect().location());
     }
     }
 }
 }
 
 

+ 177 - 33
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_maximize_icon;
 static Gfx::Bitmap* s_restore_icon;
 static Gfx::Bitmap* s_restore_icon;
 static Gfx::Bitmap* s_close_icon;
 static Gfx::Bitmap* s_close_icon;
+static Gfx::Bitmap* s_window_shadow;
 
 
 static String s_last_title_button_icons_path;
 static String s_last_title_button_icons_path;
 static int s_last_title_button_icons_scale;
 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)
 WindowFrame::WindowFrame(Window& window)
     : m_window(window)
     : m_window(window)
 {
 {
@@ -94,6 +104,7 @@ WindowFrame::~WindowFrame()
 
 
 void WindowFrame::set_button_icons()
 void WindowFrame::set_button_icons()
 {
 {
+    m_dirty = true;
     if (m_window.is_frameless())
     if (m_window.is_frameless())
         return;
         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) {
     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(icons_path);
         full_path.append("window-minimize.png");
         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()))
         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();
             s_minimize_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/downward-triangle.png", icons_scale).leak_ref();
         full_path.clear();
         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) {
     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(icons_path);
         full_path.append("window-maximize.png");
         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()))
         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();
             s_maximize_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/upward-triangle.png", icons_scale).leak_ref();
         full_path.clear();
         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) {
     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(icons_path);
         full_path.append("window-restore.png");
         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()))
         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();
             s_restore_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/window-restore.png", icons_scale).leak_ref();
         full_path.clear();
         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) {
     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(icons_path);
         full_path.append("window-close.png");
         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()))
         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();
             s_close_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/window-close.png", icons_scale).leak_ref();
         full_path.clear();
         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_path = icons_path;
     s_last_title_button_icons_scale = icons_scale;
     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<Window>, bool maximized)
 void WindowFrame::did_set_maximized(Badge<Window>, bool maximized)
@@ -205,7 +244,7 @@ void WindowFrame::paint(Gfx::Painter& painter, const Gfx::IntRect& rect)
 {
 {
     render_to_cache();
     render_to_cache();
 
 
-    auto frame_rect = this->rect();
+    auto frame_rect = render_rect();
     auto window_rect = m_window.rect();
     auto window_rect = m_window.rect();
 
 
     if (m_top_bottom) {
     if (m_top_bottom) {
@@ -265,48 +304,62 @@ void WindowFrame::render_to_cache()
 {
 {
     if (!m_dirty)
     if (!m_dirty)
         return;
         return;
-    m_dirty = true;
+    m_dirty = false;
 
 
-    static RefPtr<Gfx::Bitmap> s_tmp_bitmap;
+    static Gfx::Bitmap* s_tmp_bitmap;
     auto frame_rect = rect();
     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 window_rect = m_window.rect();
     auto scale = Screen::the().scale_factor();
     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);
     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 });
+
+    if (shadow_bitmap)
+        paint_simple_rect_shadow(painter, { { 0, 0 }, total_frame_rect.size() }, *shadow_bitmap);
 
 
-    auto top_bottom_height = frame_rect.height() - window_rect.height();
+    {
+        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 (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);
         ASSERT(m_bottom_y >= 0);
 
 
         Gfx::Painter top_bottom_painter(*m_top_bottom);
         Gfx::Painter top_bottom_painter(*m_top_bottom);
         if (m_bottom_y > 0)
         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)
         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 {
     } else {
         m_top_bottom = nullptr;
         m_top_bottom = nullptr;
         m_bottom_y = 0;
         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 (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);
         ASSERT(m_right_x >= 0);
 
 
         Gfx::Painter left_right_painter(*m_left_right);
         Gfx::Painter left_right_painter(*m_left_right);
         if (m_right_x > 0)
         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)
         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 {
     } else {
         m_left_right = nullptr;
         m_left_right = nullptr;
         m_right_x = 0;
         m_right_x = 0;
@@ -321,30 +374,45 @@ void WindowFrame::set_opacity(float opacity)
     m_opacity = opacity;
     m_opacity = opacity;
     if (was_opaque != is_opaque())
     if (was_opaque != is_opaque())
         Compositor::the().invalidate_occlusions();
         Compositor::the().invalidate_occlusions();
-    Compositor::the().invalidate_screen(rect());
+    Compositor::the().invalidate_screen(render_rect());
     WindowManager::the().notify_opacity_changed(m_window);
     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
 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()
 void WindowFrame::invalidate_title_bar()
 {
 {
-    m_dirty = true;
     invalidate(title_bar_rect());
     invalidate(title_bar_rect());
 }
 }
 
 
@@ -353,6 +421,7 @@ void WindowFrame::invalidate(Gfx::IntRect relative_rect)
     auto frame_rect = rect();
     auto frame_rect = rect();
     auto window_rect = m_window.rect();
     auto window_rect = m_window.rect();
     relative_rect.move_by(frame_rect.x() - window_rect.x(), frame_rect.y() - window_rect.y());
     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);
     m_window.invalidate(relative_rect, true);
 }
 }
 
 
@@ -360,15 +429,15 @@ void WindowFrame::notify_window_rect_changed(const Gfx::IntRect& old_rect, const
 {
 {
     layout_buttons();
     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;
         m_dirty = true;
     auto& compositor = Compositor::the();
     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);
         compositor.invalidate_screen(dirty);
     if (!m_window.is_opaque())
     if (!m_window.is_opaque())
-        compositor.invalidate_screen(rect());
+        compositor.invalidate_screen(new_frame_rect);
 
 
     compositor.invalidate_occlusions();
     compositor.invalidate_occlusions();
 
 
@@ -491,4 +560,79 @@ void WindowFrame::start_flash_animation()
     m_flash_timer->start();
     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);
+}
+
 }
 }

+ 10 - 2
Userland/Services/WindowServer/WindowFrame.h

@@ -45,6 +45,7 @@ public:
     ~WindowFrame();
     ~WindowFrame();
 
 
     Gfx::IntRect rect() const;
     Gfx::IntRect rect() const;
+    Gfx::IntRect render_rect() const;
     void paint(Gfx::Painter&, const Gfx::IntRect&);
     void paint(Gfx::Painter&, const Gfx::IntRect&);
     void render(Gfx::Painter&);
     void render(Gfx::Painter&);
     void render_to_cache();
     void render_to_cache();
@@ -64,7 +65,7 @@ public:
 
 
     void start_flash_animation();
     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_has_alpha_channel(bool value) { m_has_alpha_channel = value; }
 
 
     void set_opacity(float);
     void set_opacity(float);
@@ -79,14 +80,19 @@ public:
         return true;
         return true;
     }
     }
 
 
-    void scale_changed()
+    void set_dirty()
     {
     {
         m_dirty = true;
         m_dirty = true;
     }
     }
 
 
 private:
 private:
+    void paint_simple_rect_shadow(Gfx::Painter&, const Gfx::IntRect&, const Gfx::Bitmap&) const;
     void paint_notification_frame(Gfx::Painter&);
     void paint_notification_frame(Gfx::Painter&);
     void paint_normal_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;
     Gfx::WindowTheme::WindowState window_state_for_theme() const;
 
 
@@ -96,6 +102,8 @@ private:
     Button* m_maximize_button { nullptr };
     Button* m_maximize_button { nullptr };
     Button* m_minimize_button { nullptr };
     Button* m_minimize_button { nullptr };
 
 
+    Gfx::IntPoint m_shadow_offset {};
+
     RefPtr<Gfx::Bitmap> m_top_bottom;
     RefPtr<Gfx::Bitmap> m_top_bottom;
     RefPtr<Gfx::Bitmap> m_left_right;
     RefPtr<Gfx::Bitmap> m_left_right;
     int m_bottom_y { 0 }; // y-offset in m_top_bottom for the bottom half
     int m_bottom_y { 0 }; // y-offset in m_top_bottom for the bottom half

+ 1 - 2
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)))
     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);
         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)
     if (m_switcher.is_visible() && window.type() != WindowType::WindowSwitcher)
         m_switcher.refresh();
         m_switcher.refresh();
@@ -1517,7 +1517,6 @@ void WindowManager::reload_icon_bitmaps_after_scale_change(bool allow_hidpi_icon
     for_each_window([&](Window& window) {
     for_each_window([&](Window& window) {
         auto& window_frame = window.frame();
         auto& window_frame = window.frame();
         window_frame.set_button_icons();
         window_frame.set_button_icons();
-        window_frame.scale_changed();
         return IterationDecision::Continue;
         return IterationDecision::Continue;
     });
     });
 }
 }