diff options
Diffstat (limited to 'Userland/Services')
-rw-r--r-- | Userland/Services/WindowServer/Compositor.cpp | 250 | ||||
-rw-r--r-- | Userland/Services/WindowServer/Compositor.h | 35 | ||||
-rw-r--r-- | Userland/Services/WindowServer/Overlays.cpp | 36 | ||||
-rw-r--r-- | Userland/Services/WindowServer/Overlays.h | 22 | ||||
-rw-r--r-- | Userland/Services/WindowServer/Window.h | 4 | ||||
-rw-r--r-- | Userland/Services/WindowServer/WindowManager.cpp | 417 | ||||
-rw-r--r-- | Userland/Services/WindowServer/WindowManager.h | 166 | ||||
-rw-r--r-- | Userland/Services/WindowServer/WindowStack.cpp | 33 | ||||
-rw-r--r-- | Userland/Services/WindowServer/WindowStack.h | 106 | ||||
-rw-r--r-- | Userland/Services/WindowServer/WindowSwitcher.cpp | 25 |
10 files changed, 885 insertions, 209 deletions
diff --git a/Userland/Services/WindowServer/Compositor.cpp b/Userland/Services/WindowServer/Compositor.cpp index f851d64a88..f6bbb4458f 100644 --- a/Userland/Services/WindowServer/Compositor.cpp +++ b/Userland/Services/WindowServer/Compositor.cpp @@ -123,6 +123,9 @@ void Compositor::init_bitmaps() void Compositor::did_construct_window_manager(Badge<WindowManager>) { auto& wm = WindowManager::the(); + + m_current_window_stack = &wm.current_window_stack(); + m_wallpaper_mode = mode_to_enum(wm.config()->read_entry("Background", "Mode", "center")); m_custom_background_color = Color::from_string(wm.config()->read_entry("Background", "Color", "")); @@ -131,6 +134,17 @@ void Compositor::did_construct_window_manager(Badge<WindowManager>) compose(); } +Gfx::IntPoint Compositor::window_transition_offset(Window& window) +{ + if (WindowManager::is_stationary_window_type(window.type())) + return {}; + + if (window.is_moving_to_another_stack()) + return {}; + + return window.outer_stack()->transition_offset(); +} + void Compositor::compose() { auto& wm = WindowManager::the(); @@ -158,33 +172,39 @@ void Compositor::compose() auto dirty_screen_rects = move(m_dirty_screen_rects); + bool window_stack_transition_in_progress = m_transitioning_to_window_stack != nullptr; + // Mark window regions as dirty that need to be re-rendered - wm.window_stack().for_each_visible_window_from_back_to_front([&](Window& window) { + wm.for_each_visible_window_from_back_to_front([&](Window& window) { + auto transition_offset = window_transition_offset(window); auto frame_rect = window.frame().render_rect(); + auto frame_rect_on_screen = frame_rect.translated(transition_offset); for (auto& dirty_rect : dirty_screen_rects.rects()) { - auto invalidate_rect = dirty_rect.intersected(frame_rect); + auto invalidate_rect = dirty_rect.intersected(frame_rect_on_screen); if (!invalidate_rect.is_empty()) { auto inner_rect_offset = window.rect().location() - frame_rect.location(); - invalidate_rect.translate_by(-(frame_rect.location() + inner_rect_offset)); + invalidate_rect.translate_by(-(frame_rect.location() + inner_rect_offset + transition_offset)); window.invalidate_no_notify(invalidate_rect); m_invalidated_window = true; } } window.prepare_dirty_rects(); + if (window_stack_transition_in_progress) + window.dirty_rects().translate_by(transition_offset); return IterationDecision::Continue; }); // Any windows above or below a given window that need to be re-rendered // also require us to re-render that window's intersecting area, regardless // of whether that window has any dirty rectangles - wm.window_stack().for_each_visible_window_from_back_to_front([&](Window& window) { + wm.for_each_visible_window_from_back_to_front([&](Window& window) { auto& transparency_rects = window.transparency_rects(); if (transparency_rects.is_empty()) return IterationDecision::Continue; auto frame_rect = window.frame().render_rect(); auto& dirty_rects = window.dirty_rects(); - wm.window_stack().for_each_visible_window_from_back_to_front([&](Window& w) { + wm.for_each_visible_window_from_back_to_front([&](Window& w) { if (&w == &window) return IterationDecision::Continue; auto frame_rect2 = w.frame().render_rect(); @@ -311,8 +331,9 @@ void Compositor::compose() // This window doesn't intersect with any screens, so there's nothing to render return IterationDecision::Continue; } - auto frame_rect = window.frame().render_rect(); - auto window_rect = window.rect(); + auto transition_offset = window_transition_offset(window); + auto frame_rect = window.frame().render_rect().translated(transition_offset); + auto window_rect = window.rect().translated(transition_offset); auto frame_rects = frame_rect.shatter(window_rect); dbgln_if(COMPOSE_DEBUG, " window {} frame rect: {}", window.title(), frame_rect); @@ -323,8 +344,9 @@ void Compositor::compose() rect.for_each_intersected(frame_rects, [&](const Gfx::IntRect& intersected_rect) { Gfx::PainterStateSaver saver(painter); painter.add_clip_rect(intersected_rect); + painter.translate(transition_offset); dbgln_if(COMPOSE_DEBUG, " render frame: {}", intersected_rect); - window.frame().paint(screen, painter, intersected_rect); + window.frame().paint(screen, painter, intersected_rect.translated(-transition_offset)); return IterationDecision::Continue; }); } @@ -481,7 +503,7 @@ void Compositor::compose() compose_window(*fullscreen_window); fullscreen_window->clear_dirty_rects(); } else { - wm.window_stack().for_each_visible_window_from_back_to_front([&](Window& window) { + wm.for_each_visible_window_from_back_to_front([&](Window& window) { compose_window(window); window.clear_dirty_rects(); return IterationDecision::Continue; @@ -899,11 +921,14 @@ void Compositor::decrement_show_screen_number(Badge<ClientConnection>) } } -bool Compositor::any_opaque_window_above_this_one_contains_rect(const Window& a_window, const Gfx::IntRect& rect) +bool Compositor::any_opaque_window_above_this_one_contains_rect(Window& a_window, const Gfx::IntRect& rect) { + auto* window_stack = a_window.outer_stack(); + if (!window_stack) + return false; bool found_containing_window = false; bool checking = false; - WindowManager::the().window_stack().for_each_visible_window_from_back_to_front([&](Window& window) { + WindowManager::the().for_each_visible_window_from_back_to_front([&](Window& window) { if (&window == &a_window) { checking = true; return IterationDecision::Continue; @@ -942,6 +967,7 @@ void Compositor::overlay_rects_changed() invalidate_occlusions(); for (auto& rect : m_overlay_rects.rects()) invalidate_screen(rect); + start_compose_async_timer(); } void Compositor::recompute_overlay_rects() @@ -973,17 +999,20 @@ void Compositor::recompute_overlay_rects() void Compositor::recompute_occlusions() { auto& wm = WindowManager::the(); - wm.window_stack().for_each_visible_window_from_back_to_front([&](Window& window) { - if (wm.m_switcher.is_visible()) { - window.set_occluded(false); - } else { + bool is_switcher_visible = wm.m_switcher.is_visible(); + wm.for_each_window_stack([&](WindowStack& window_stack) { + window_stack.set_all_occluded(!is_switcher_visible); + return IterationDecision::Continue; + }); + if (!is_switcher_visible) { + wm.for_each_visible_window_from_back_to_front([&](Window& window) { if (any_opaque_window_above_this_one_contains_rect(window, window.frame().rect())) window.set_occluded(true); else window.set_occluded(false); - } - return IterationDecision::Continue; - }); + return IterationDecision::Continue; + }); + } if (m_overlay_rects_changed) { m_overlay_rects_changed = false; @@ -996,11 +1025,12 @@ void Compositor::recompute_occlusions() dbgln(" overlay: {}", rect); } + bool window_stack_transition_in_progress = m_transitioning_to_window_stack != nullptr; auto& main_screen = Screen::main(); if (auto* fullscreen_window = wm.active_fullscreen_window()) { // TODO: support fullscreen windows on all screens auto screen_rect = main_screen.rect(); - WindowManager::the().window_stack().for_each_visible_window_from_front_to_back([&](Window& w) { + wm.for_each_visible_window_from_front_to_back([&](Window& w) { auto& visible_opaque = w.opaque_rects(); auto& transparency_rects = w.transparency_rects(); auto& transparency_wallpaper_rects = w.transparency_wallpaper_rects(); @@ -1029,7 +1059,7 @@ void Compositor::recompute_occlusions() Gfx::DisjointRectSet visible_rects; visible_rects.add_many(Screen::rects()); bool have_transparent = false; - WindowManager::the().window_stack().for_each_visible_window_from_front_to_back([&](Window& w) { + wm.for_each_visible_window_from_front_to_back([&](Window& w) { w.transparency_wallpaper_rects().clear(); auto& visible_opaque = w.opaque_rects(); visible_opaque.clear(); @@ -1039,8 +1069,13 @@ void Compositor::recompute_occlusions() if (w.is_minimized()) return IterationDecision::Continue; + auto transition_offset = window_transition_offset(w); auto transparent_frame_render_rects = w.frame().transparent_render_rects(); auto opaque_frame_render_rects = w.frame().opaque_render_rects(); + if (window_stack_transition_in_progress) { + transparent_frame_render_rects.translate_by(transition_offset); + opaque_frame_render_rects.translate_by(transition_offset); + } Gfx::DisjointRectSet visible_opaque_rects; Screen::for_each([&](auto& screen) { auto screen_rect = screen.rect(); @@ -1061,10 +1096,12 @@ void Compositor::recompute_occlusions() visible_opaque = visible_rects.intersected(visible_opaque_rects); auto render_rect = w.frame().render_rect(); - + auto render_rect_on_screen = render_rect; + if (window_stack_transition_in_progress) + render_rect_on_screen.translate_by(transition_offset); Gfx::DisjointRectSet opaque_covering; bool found_this_window = false; - WindowManager::the().window_stack().for_each_visible_window_from_back_to_front([&](Window& w2) { + wm.for_each_visible_window_from_back_to_front([&](Window& w2) { if (!found_this_window) { if (&w == &w2) found_this_window = true; @@ -1074,17 +1111,28 @@ void Compositor::recompute_occlusions() if (w2.is_minimized()) return IterationDecision::Continue; - if (!render_rect.intersects(w2.frame().render_rect())) + auto w2_render_rect = w2.frame().render_rect(); + auto w2_render_rect_on_screen = w2_render_rect; + auto w2_transition_offset = window_transition_offset(w2); + if (window_stack_transition_in_progress) + w2_render_rect_on_screen.translate_by(w2_transition_offset); + if (!render_rect_on_screen.intersects(w2_render_rect_on_screen)) return IterationDecision::Continue; - auto opaque_rects = w2.frame().opaque_render_rects().intersected(render_rect); - auto transparent_rects = w2.frame().transparent_render_rects().intersected(render_rect); + auto opaque_rects = w2.frame().opaque_render_rects(); + auto transparent_rects = w2.frame().transparent_render_rects(); + if (window_stack_transition_in_progress) { + auto transition_offset_2 = window_transition_offset(w2); + opaque_rects.translate_by(transition_offset_2); + transparent_rects.translate_by(transition_offset_2); + } + opaque_rects = opaque_rects.intersected(render_rect_on_screen); + transparent_rects = transparent_rects.intersected(render_rect_on_screen); if (opaque_rects.is_empty() && transparent_rects.is_empty()) return IterationDecision::Continue; - for (auto& covering : opaque_rects.rects()) { opaque_covering.add(covering); - if (opaque_covering.contains(render_rect)) { + if (opaque_covering.contains(render_rect_on_screen)) { // This window (including frame) is entirely covered by another opaque window visible_opaque.clear(); transparency_rects.clear(); @@ -1156,7 +1204,7 @@ void Compositor::recompute_occlusions() if (have_transparent) { // Determine what transparent window areas need to render the wallpaper first - WindowManager::the().window_stack().for_each_visible_window_from_back_to_front([&](Window& w) { + wm.for_each_visible_window_from_back_to_front([&](Window& w) { auto& transparency_wallpaper_rects = w.transparency_wallpaper_rects(); if (w.is_minimized()) { transparency_wallpaper_rects.clear(); @@ -1184,7 +1232,7 @@ void Compositor::recompute_occlusions() dbgln(" wallpaper opaque: {}", r); } - wm.window_stack().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().render_rect(); if (w.is_minimized() || window_frame_rect.is_empty() || w.screens().is_empty()) return IterationDecision::Continue; @@ -1237,4 +1285,148 @@ void Compositor::update_animations(Screen& screen, Gfx::DisjointRectSet& flush_r } } +void Compositor::create_window_stack_switch_overlay(WindowStack& target_stack) +{ + if (m_stack_switch_overlay_timer) { + // Cancel any timer, we're going to delete the overlay + m_stack_switch_overlay_timer->stop(); + m_stack_switch_overlay_timer = nullptr; + } + Screen::for_each([&](auto& screen) { + auto& screen_data = m_screen_data[screen.index()]; + screen_data.m_window_stack_switch_overlay = nullptr; // delete it first + screen_data.m_window_stack_switch_overlay = create_overlay<WindowStackSwitchOverlay>(screen, target_stack); + screen_data.m_window_stack_switch_overlay->set_enabled(true); + return IterationDecision::Continue; + }); +} + +void Compositor::start_window_stack_switch_overlay_timer() +{ + if (m_stack_switch_overlay_timer) { + m_stack_switch_overlay_timer->stop(); + m_stack_switch_overlay_timer = nullptr; + } + m_stack_switch_overlay_timer = Core::Timer::create_single_shot( + 500, + [this] { + Screen::for_each([&](auto& screen) { + auto& screen_data = m_screen_data[screen.index()]; + screen_data.m_window_stack_switch_overlay = nullptr; + return IterationDecision::Continue; + }); + }, + this); + m_stack_switch_overlay_timer->start(); +} + +void Compositor::finish_window_stack_switch() +{ + VERIFY(m_transitioning_to_window_stack); + VERIFY(m_current_window_stack); + VERIFY(m_transitioning_to_window_stack != m_current_window_stack); + + m_current_window_stack->set_transition_offset({}, {}); + m_transitioning_to_window_stack->set_transition_offset({}, {}); + + auto* previous_window_stack = m_current_window_stack; + m_current_window_stack = m_transitioning_to_window_stack; + m_transitioning_to_window_stack = nullptr; + + m_window_stack_transition_animation = nullptr; + + auto& wm = WindowManager::the(); + if (!wm.m_switcher.is_visible()) + previous_window_stack->set_all_occluded(true); + wm.did_switch_window_stack({}, *previous_window_stack, *m_current_window_stack); + + invalidate_occlusions(); + + // Rather than invalidating the entire we could invalidate all render rectangles + // that are affected by the transition offset before and after changing it. + invalidate_screen(); + + start_window_stack_switch_overlay_timer(); +} + +void Compositor::switch_to_window_stack(WindowStack& new_window_stack) +{ + if (m_transitioning_to_window_stack) { + if (m_transitioning_to_window_stack == &new_window_stack) + return; + // A switch is in progress, but the user is impatient. Finish the transition instantly + finish_window_stack_switch(); + VERIFY(!m_window_stack_transition_animation); + // Now switch to the next target as usual + } + VERIFY(m_current_window_stack); + + if (&new_window_stack == m_current_window_stack) { + // So that the user knows which stack they're on, show the overlay briefly + create_window_stack_switch_overlay(*m_current_window_stack); + start_window_stack_switch_overlay_timer(); + return; + } + VERIFY(!m_transitioning_to_window_stack); + m_transitioning_to_window_stack = &new_window_stack; + + auto window_stack_size = Screen::bounding_rect().size(); + + int delta_x = 0; + if (new_window_stack.column() < m_current_window_stack->column()) + delta_x = window_stack_size.width(); + else if (new_window_stack.column() > m_current_window_stack->column()) + delta_x = -window_stack_size.width(); + int delta_y = 0; + if (new_window_stack.row() < m_current_window_stack->row()) + delta_y = window_stack_size.height(); + else if (new_window_stack.row() > m_current_window_stack->row()) { + delta_y = -window_stack_size.height(); + } + + m_transitioning_to_window_stack->set_transition_offset({}, { -delta_x, -delta_y }); + m_current_window_stack->set_transition_offset({}, {}); + + create_window_stack_switch_overlay(*m_transitioning_to_window_stack); + // We start the timer when the animation ends! + + VERIFY(!m_window_stack_transition_animation); + m_window_stack_transition_animation = Animation::create(); + m_window_stack_transition_animation->set_duration(250); + m_window_stack_transition_animation->on_update = [this, delta_x, delta_y](float progress, Gfx::Painter&, Screen&, Gfx::DisjointRectSet&) { + VERIFY(m_transitioning_to_window_stack); + VERIFY(m_current_window_stack); + + // Set transition offset for the window stack we're transitioning out of + auto previous_transition_offset_from = m_current_window_stack->transition_offset(); + Gfx::IntPoint transition_offset_from { (float)delta_x * progress, (float)delta_y * progress }; + if (previous_transition_offset_from == transition_offset_from) + return; + + { + // we need to render both, the existing dirty rectangles as well as where we're shifting to + auto translated_dirty_rects = m_dirty_screen_rects.clone(); + auto transition_delta = transition_offset_from - previous_transition_offset_from; + translated_dirty_rects.translate_by(transition_delta); + m_dirty_screen_rects.add(translated_dirty_rects.intersected(Screen::bounding_rect())); + } + m_current_window_stack->set_transition_offset({}, transition_offset_from); + + // Set transition offset for the window stack we're transitioning to + Gfx::IntPoint transition_offset_to { (float)-delta_x * (1.0f - progress), (float)-delta_y * (1.0f - progress) }; + m_transitioning_to_window_stack->set_transition_offset({}, transition_offset_to); + + invalidate_occlusions(); + + // Rather than invalidating the entire we could invalidate all render rectangles + // that are affected by the transition offset before and after changing it. + invalidate_screen(); + }; + + m_window_stack_transition_animation->on_stop = [this] { + finish_window_stack_switch(); + }; + m_window_stack_transition_animation->start(); +} + } diff --git a/Userland/Services/WindowServer/Compositor.h b/Userland/Services/WindowServer/Compositor.h index 3d9e719cb6..f869d9d51f 100644 --- a/Userland/Services/WindowServer/Compositor.h +++ b/Userland/Services/WindowServer/Compositor.h @@ -22,6 +22,7 @@ class Cursor; class MultiScaleBitmaps; class Window; class WindowManager; +class WindowStack; enum class WallpaperMode { Tile, @@ -92,6 +93,27 @@ public: return IterationDecision::Continue; } + template<typename F> + IterationDecision for_each_rendering_window_stack(F f) + { + VERIFY(m_current_window_stack); + IterationDecision decision = f(*m_current_window_stack); + if (decision != IterationDecision::Continue) + return decision; + if (m_transitioning_to_window_stack) + decision = f(*m_transitioning_to_window_stack); + return decision; + } + + [[nodiscard]] WindowStack& get_rendering_window_stacks(WindowStack*& transitioning_window_stack) + { + transitioning_window_stack = m_transitioning_to_window_stack; + return *m_current_window_stack; + } + + bool is_switching_window_stacks() const { return m_transitioning_to_window_stack != nullptr; } + void switch_to_window_stack(WindowStack&); + void did_construct_window_manager(Badge<WindowManager>); const Gfx::Bitmap* cursor_bitmap_for_screenshot(Badge<ClientConnection>, Screen&) const; @@ -114,10 +136,14 @@ private: void start_compose_async_timer(); void recompute_overlay_rects(); void recompute_occlusions(); - bool any_opaque_window_above_this_one_contains_rect(const Window&, const Gfx::IntRect&); + bool any_opaque_window_above_this_one_contains_rect(Window&, const Gfx::IntRect&); void change_cursor(const Cursor*); void flush(Screen&); + Gfx::IntPoint window_transition_offset(Window&); void update_animations(Screen&, Gfx::DisjointRectSet& flush_rects); + void create_window_stack_switch_overlay(WindowStack&); + void start_window_stack_switch_overlay_timer(); + void finish_window_stack_switch(); RefPtr<Core::Timer> m_compose_timer; RefPtr<Core::Timer> m_immediate_compose_timer; @@ -139,6 +165,7 @@ private: OwnPtr<Gfx::Painter> m_cursor_back_painter; Gfx::IntRect m_last_cursor_rect; OwnPtr<ScreenNumberOverlay> m_screen_number_overlay; + OwnPtr<WindowStackSwitchOverlay> m_window_stack_switch_overlay; bool m_buffers_are_flipped { false }; bool m_screen_can_set_buffer { false }; bool m_cursor_back_is_valid { false }; @@ -197,6 +224,12 @@ private: RefPtr<Core::Timer> m_display_link_notify_timer; size_t m_display_link_count { 0 }; + WindowStack* m_current_window_stack { nullptr }; + WindowStack* m_transitioning_to_window_stack { nullptr }; + RefPtr<Animation> m_window_stack_transition_animation; + OwnPtr<WindowStackSwitchOverlay> m_stack_switch_overlay; + RefPtr<Core::Timer> m_stack_switch_overlay_timer; + size_t m_show_screen_number_count { 0 }; Optional<Gfx::Color> m_custom_background_color; diff --git a/Userland/Services/WindowServer/Overlays.cpp b/Userland/Services/WindowServer/Overlays.cpp index c5cedf2907..f183375537 100644 --- a/Userland/Services/WindowServer/Overlays.cpp +++ b/Userland/Services/WindowServer/Overlays.cpp @@ -311,4 +311,40 @@ RefPtr<Gfx::Bitmap> DndOverlay::create_bitmap(int scale_factor) return new_bitmap; } +void WindowStackSwitchOverlay::render_overlay_bitmap(Gfx::Painter& painter) +{ + // We should come up with a more elegant way to get the content rectangle + Gfx::IntRect content_rect({}, m_content_size); + content_rect.center_within({ {}, rect().size() }); + auto active_color = WindowManager::the().palette().active_window_border1(); + auto inactive_color = WindowManager::the().palette().inactive_window_border1(); + for (int y = 0; y < m_rows; y++) { + for (int x = 0; x < m_columns; x++) { + Gfx::IntRect rect { + content_rect.left() + x * (default_screen_rect_width + default_screen_rect_padding), + content_rect.top() + y * (default_screen_rect_height + default_screen_rect_padding), + default_screen_rect_width, + default_screen_rect_height + }; + bool is_target = y == m_target_row && x == m_target_column; + painter.fill_rect(rect, is_target ? active_color : inactive_color); + } + } +} + +WindowStackSwitchOverlay::WindowStackSwitchOverlay(Screen& screen, WindowStack& target_window_stack) + : m_rows((int)WindowManager::the().window_stack_rows()) + , m_columns((int)WindowManager::the().window_stack_columns()) + , m_target_row((int)target_window_stack.row()) + , m_target_column((int)target_window_stack.column()) +{ + m_content_size = { + m_columns * (default_screen_rect_width + default_screen_rect_padding) - default_screen_rect_padding, + m_rows * (default_screen_rect_height + default_screen_rect_padding) - default_screen_rect_padding, + }; + auto rect = calculate_frame_rect(Gfx::IntRect({}, m_content_size).inflated(2 * default_screen_rect_margin, 2 * default_screen_rect_margin)); + rect.center_within(screen.rect()); + set_rect(rect); +} + } diff --git a/Userland/Services/WindowServer/Overlays.h b/Userland/Services/WindowServer/Overlays.h index b675e9b0c9..f94a870bd0 100644 --- a/Userland/Services/WindowServer/Overlays.h +++ b/Userland/Services/WindowServer/Overlays.h @@ -17,6 +17,7 @@ namespace WindowServer { class Screen; class Window; +class WindowStack; class Overlay { friend class Compositor; @@ -27,6 +28,7 @@ public: enum class ZOrder { WindowGeometry, Dnd, + WindowStackSwitch, ScreenNumber, }; [[nodiscard]] virtual ZOrder zorder() const = 0; @@ -168,4 +170,24 @@ private: Gfx::IntRect m_label_rect; }; +class WindowStackSwitchOverlay : public RectangularOverlay { +public: + static constexpr int default_screen_rect_width = 40; + static constexpr int default_screen_rect_height = 30; + static constexpr int default_screen_rect_margin = 16; + static constexpr int default_screen_rect_padding = 8; + + WindowStackSwitchOverlay(Screen&, WindowStack&); + + virtual ZOrder zorder() const override { return ZOrder::WindowStackSwitch; } + virtual void render_overlay_bitmap(Gfx::Painter&) override; + +private: + Gfx::IntSize m_content_size; + const int m_rows; + const int m_columns; + const int m_target_row; + const int m_target_column; +}; + } diff --git a/Userland/Services/WindowServer/Window.h b/Userland/Services/WindowServer/Window.h index 09fc32431d..f6728da7f5 100644 --- a/Userland/Services/WindowServer/Window.h +++ b/Userland/Services/WindowServer/Window.h @@ -330,6 +330,9 @@ public: frame().window_was_constructed({}); } + void set_moving_to_another_stack(bool value) { m_moving_to_another_stack = value; } + bool is_moving_to_another_stack() const { return m_moving_to_another_stack; } + private: Window(ClientConnection&, WindowType, int window_id, bool modal, bool minimizable, bool frameless, bool resizable, bool fullscreen, bool accessory, Window* parent_window = nullptr); Window(Core::Object&, WindowType); @@ -382,6 +385,7 @@ private: bool m_invalidated_frame { true }; bool m_hit_testing_enabled { true }; bool m_modified { false }; + bool m_moving_to_another_stack { false }; WindowTileType m_tiled { WindowTileType::None }; Gfx::IntRect m_untiled_rect; bool m_occluded { false }; diff --git a/Userland/Services/WindowServer/WindowManager.cpp b/Userland/Services/WindowServer/WindowManager.cpp index a2912affb6..039efac1bc 100644 --- a/Userland/Services/WindowServer/WindowManager.cpp +++ b/Userland/Services/WindowServer/WindowManager.cpp @@ -39,6 +39,26 @@ WindowManager::WindowManager(Gfx::PaletteImpl const& palette) { s_the = this; + { + // Create the default window stacks + auto row_count = m_window_stacks.capacity(); + VERIFY(row_count > 0); + for (size_t row_index = 0; row_index < row_count; row_index++) { + auto row = adopt_own(*new RemoveReference<decltype(m_window_stacks[0])>()); + auto column_count = row->capacity(); + VERIFY(column_count > 0); + for (size_t column_index = 0; column_index < column_count; column_index++) + row->append(adopt_own(*new WindowStack(row_index, column_index))); + m_window_stacks.append(move(row)); + } + + m_current_window_stack = &m_window_stacks[0][0]; + for (auto& row : m_window_stacks) { + for (auto& stack : row) + stack.set_stationary_window_stack(*m_current_window_stack); + } + } + reload_config(); Compositor::the().did_construct_window_manager({}); @@ -123,9 +143,12 @@ bool WindowManager::set_screen_layout(ScreenLayout&& screen_layout, bool save, S client.notify_about_new_screen_rects(Screen::rects(), Screen::main().index()); }); - m_window_stack.for_each_window([](Window& window) { - window.screens().clear_with_capacity(); - window.recalculate_rect(); + for_each_window_stack([&](auto& window_stack) { + window_stack.for_each_window([](Window& window) { + window.screens().clear_with_capacity(); + window.recalculate_rect(); + return IterationDecision::Continue; + }); return IterationDecision::Continue; }); @@ -178,11 +201,23 @@ int WindowManager::double_click_speed() const return m_double_click_speed; } +WindowStack& WindowManager::window_stack_for_window(Window& window) +{ + if (is_stationary_window_type(window.type())) + return m_window_stacks[0][0]; + if (auto* parent = window.parent_window(); parent && !is_stationary_window_type(parent->type())) { + if (auto* parent_window_stack = parent->outer_stack()) + return *parent_window_stack; + } + return current_window_stack(); +} + void WindowManager::add_window(Window& window) { - bool is_first_window = m_window_stack.is_empty(); + auto& window_stack = window_stack_for_window(window); + bool is_first_window = window_stack.is_empty(); - m_window_stack.add(window); + window_stack.add(window); if (window.is_fullscreen()) { auto& screen = Screen::main(); // TODO: support fullscreen windows on other screens! @@ -235,7 +270,9 @@ void WindowManager::move_to_front_and_make_active(Window& window) void WindowManager::do_move_to_front(Window& window, bool make_active, bool make_input) { - m_window_stack.move_to_front(window); + auto* window_stack = window.outer_stack(); + VERIFY(window_stack); + window_stack->move_to_front(window); if (make_active) set_active_window(&window, make_input); @@ -256,11 +293,11 @@ void WindowManager::do_move_to_front(Window& window, bool make_active, bool make void WindowManager::remove_window(Window& window) { - check_hide_geometry_overlay(window); - m_window_stack.remove(window); auto* active = active_window(); auto* active_input = active_input_window(); + if (auto* window_stack = window.outer_stack()) + window_stack->remove(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); @@ -285,11 +322,14 @@ void WindowManager::greet_window_manager(WMClientConnection& conn) if (conn.window_id() < 0) return; - m_window_stack.for_each_window([&](Window& other_window) { - //if (conn.window_id() != other_window.window_id()) { - tell_wm_about_window(conn, other_window); - tell_wm_about_window_icon(conn, other_window); - //} + for_each_window_stack([&](auto& window_stack) { + window_stack.for_each_window([&](Window& other_window) { + //if (conn.window_id() != other_window.window_id()) { + tell_wm_about_window(conn, other_window); + tell_wm_about_window_icon(conn, other_window); + //} + return IterationDecision::Continue; + }); return IterationDecision::Continue; }); if (auto* applet_area_window = AppletManager::the().window()) @@ -473,7 +513,8 @@ bool WindowManager::pick_new_active_window(Window* previous_active) { bool new_window_picked = false; Window* first_candidate = nullptr; - m_window_stack.for_each_visible_window_from_front_to_back([&](Window& candidate) { + + for_each_visible_window_from_front_to_back([&](Window& candidate) { if (candidate.type() != WindowType::Normal && candidate.type() != WindowType::ToolWindow) return IterationDecision::Continue; if (candidate.is_destroyed()) @@ -555,7 +596,7 @@ void WindowManager::start_window_resize(Window& window, Gfx::IntPoint const& pos m_geometry_overlay = Compositor::the().create_overlay<WindowGeometryOverlay>(window); m_geometry_overlay->set_enabled(true); - m_active_input_tracking_window = nullptr; + current_window_stack().set_active_input_tracking_window(nullptr); window.invalidate(true, true); @@ -803,7 +844,7 @@ bool WindowManager::process_ongoing_drag(MouseEvent& event) m_dnd_overlay->cursor_moved(); // We didn't let go of the drag yet, see if we should send some drag move events.. - m_window_stack.for_each_visible_window_from_front_to_back([&](Window& window) { + for_each_visible_window_from_front_to_back([&](Window& window) { if (!window.rect().contains(event.position())) return IterationDecision::Continue; event.set_drag(true); @@ -816,7 +857,7 @@ bool WindowManager::process_ongoing_drag(MouseEvent& event) if (!(event.type() == Event::MouseUp && event.button() == MouseButton::Left)) return true; - if (auto* window = m_window_stack.window_at(event.position())) { + if (auto* window = current_window_stack().window_at(event.position())) { m_dnd_client->async_drag_accepted(); if (window->client()) { auto translated_event = event.translated(-window->position()); @@ -971,7 +1012,9 @@ void WindowManager::deliver_mouse_event(Window& window, MouseEvent const& event, bool WindowManager::process_ongoing_active_input_mouse_event(MouseEvent const& event) { - if (!m_active_input_tracking_window) + auto& window_stack = current_window_stack(); + auto* input_tracking_window = window_stack.active_input_tracking_window(); + if (!input_tracking_window) return false; // At this point, we have delivered the start of an input sequence to a @@ -980,11 +1023,10 @@ bool WindowManager::process_ongoing_active_input_mouse_event(MouseEvent const& e // // This prevents e.g. moving on one window out of the bounds starting // a move in that other unrelated window, and other silly shenanigans. - deliver_mouse_event(*m_active_input_tracking_window, event, true); + deliver_mouse_event(*input_tracking_window, event, true); - if (event.type() == Event::MouseUp && event.buttons() == 0) { - m_active_input_tracking_window = nullptr; - } + if (event.type() == Event::MouseUp && event.buttons() == 0) + window_stack.set_active_input_tracking_window(nullptr); return true; } @@ -1049,7 +1091,7 @@ void WindowManager::process_mouse_event_for_window(HitTestResult& result, MouseE } if (event.type() == Event::MouseDown) - m_active_input_tracking_window = window; + current_window_stack().set_active_input_tracking_window(&window); } void WindowManager::process_mouse_event(MouseEvent& event) @@ -1065,8 +1107,9 @@ void WindowManager::process_mouse_event(MouseEvent& event) // 2. Send the mouse event to all windows with global cursor tracking enabled. // The active input tracking window is excluded here because we're sending the event to it // in the next step. - m_window_stack.for_each_visible_window_from_front_to_back([&](Window& window) { - if (window.global_cursor_tracking() && &window != m_active_input_tracking_window) + auto& window_stack = current_window_stack(); + for_each_visible_window_from_front_to_back([&](Window& window) { + if (window.global_cursor_tracking() && &window != window_stack.active_input_tracking_window()) deliver_mouse_event(window, event, false); return IterationDecision::Continue; }); @@ -1112,7 +1155,7 @@ void WindowManager::process_mouse_event(MouseEvent& event) } // 8. Hit test the window stack to see what's under the cursor. - auto result = m_window_stack.hit_test(event.position()); + auto result = current_window_stack().hit_test(event.position()); if (!result.has_value()) { // No window is under the cursor. @@ -1144,7 +1187,7 @@ void WindowManager::reevaluate_hovered_window(Window* updated_window) if (fullscreen_window->hit_test(cursor_location).has_value()) hovered_window = fullscreen_window; } else { - hovered_window = m_window_stack.window_at(cursor_location); + hovered_window = current_window_stack().window_at(cursor_location); } if (set_hovered_window(hovered_window)) { @@ -1213,7 +1256,8 @@ void WindowManager::event(Core::Event& event) m_previous_event_was_super_keydown = false; process_mouse_event(mouse_event); - set_hovered_window(m_window_stack.window_at(mouse_event.position(), WindowStack::IncludeWindowFrame::No)); + // TODO: handle transitioning between two stacks + set_hovered_window(current_window_stack().window_at(mouse_event.position(), WindowStack::IncludeWindowFrame::No)); return; } @@ -1225,6 +1269,117 @@ void WindowManager::event(Core::Event& event) Core::Object::event(event); } +bool WindowManager::is_window_in_modal_stack(Window& window_in_modal_stack, Window& other_window) +{ + auto result = for_each_window_in_modal_stack(window_in_modal_stack, [&](auto& window, auto) { + if (&other_window == &window) + return IterationDecision::Break; + for (auto& accessory : window.accessory_windows()) { + if (accessory.ptr() == &other_window) + return IterationDecision::Break; + } + return IterationDecision::Continue; + }); + return result == IterationDecision::Break; +} + +void WindowManager::switch_to_window_stack(WindowStack& window_stack, Window* carry_window) +{ + m_carry_window_to_new_stack.clear(); + m_switching_to_window_stack = &window_stack; + if (carry_window && !is_stationary_window_type(carry_window->type()) && carry_window->outer_stack() != &window_stack) { + auto& from_stack = *carry_window->outer_stack(); + + auto* blocking_modal = carry_window->blocking_modal_window(); + for_each_visible_window_from_back_to_front([&](Window& window) { + if (is_stationary_window_type(window.type())) + return IterationDecision::Continue; + + if (window.outer_stack() != carry_window->outer_stack()) + return IterationDecision::Continue; + if (&window == carry_window || ((carry_window->is_modal() || blocking_modal) && is_window_in_modal_stack(*carry_window, window))) + m_carry_window_to_new_stack.append(window); + return IterationDecision::Continue; + }, + &from_stack); + + auto* from_active_window = from_stack.active_window(); + auto* from_active_input_window = from_stack.active_input_window(); + bool did_carry_active_window = false; + bool did_carry_active_input_window = false; + for (auto& window : m_carry_window_to_new_stack) { + if (window == from_active_window) + did_carry_active_window = true; + if (window == from_active_input_window) + did_carry_active_input_window = true; + window->set_moving_to_another_stack(true); + VERIFY(window->outer_stack() == &from_stack); + from_stack.remove(*window); + window_stack.add(*window); + } + // Before we change to the new stack, find a new active window on the stack we're switching from + if (did_carry_active_window || did_carry_active_input_window) + pick_new_active_window(from_active_window); + + // Now switch to the new stack + m_current_window_stack = &window_stack; + if (did_carry_active_window && from_active_window) + set_active_window(from_active_window, from_active_input_window == from_active_window); + if (did_carry_active_input_window && from_active_input_window && from_active_input_window != from_active_window) + set_active_input_window(from_active_input_window); + + // Because we moved windows between stacks we need to invalidate occlusions + Compositor::the().invalidate_occlusions(); + } else { + m_current_window_stack = &window_stack; + } + + Compositor::the().switch_to_window_stack(window_stack); +} + +void WindowManager::did_switch_window_stack(Badge<Compositor>, WindowStack& previous_stack, WindowStack& new_stack) +{ + VERIFY(&previous_stack != &new_stack); + + // We are being notified by the compositor, it should not be switching right now! + VERIFY(!Compositor::the().is_switching_window_stacks()); + VERIFY(¤t_window_stack() == &new_stack); + + if (m_switching_to_window_stack == &new_stack) { + m_switching_to_window_stack = nullptr; + if (!m_carry_window_to_new_stack.is_empty()) { + // switched_to_stack may be different from the stack where the windows were + // carried to when the user rapidly tries to switch stacks, so make sure to + // only reset the moving flag if we arrived at our final destination + for (auto& window_ref : m_carry_window_to_new_stack) { + if (auto* window = window_ref.ptr()) + window->set_moving_to_another_stack(false); + } + m_carry_window_to_new_stack.clear(); + } + } + + auto* previous_stack_active_window = previous_stack.active_window(); + auto* previous_stack_active_input_window = previous_stack.active_input_window(); + auto* new_stack_active_window = new_stack.active_window(); + auto* new_stack_active_input_window = new_stack.active_input_window(); + if (previous_stack_active_input_window && previous_stack_active_input_window != new_stack_active_input_window) + notify_previous_active_input_window(*previous_stack_active_input_window); + if (new_stack_active_input_window && previous_stack_active_input_window != new_stack_active_input_window) + notify_new_active_input_window(*new_stack_active_input_window); + if (previous_stack_active_window != new_stack_active_window) { + if (previous_stack_active_window && is_stationary_window_type(previous_stack_active_window->type())) + notify_previous_active_window(*previous_stack_active_window); + if (new_stack_active_window && is_stationary_window_type(new_stack_active_window->type())) + notify_new_active_window(*new_stack_active_window); + } + + if (!new_stack_active_input_window) + pick_new_active_window(nullptr); + + reevaluate_hovered_window(); +} + void WindowManager::process_key_event(KeyEvent& event) { m_keyboard_modifiers = event.modifiers(); @@ -1258,7 +1413,7 @@ void WindowManager::process_key_event(KeyEvent& event) m_previous_event_was_super_keydown = true; } else if (m_previous_event_was_super_keydown) { m_previous_event_was_super_keydown = false; - if (!m_dnd_client && !m_active_input_tracking_window && event.type() == Event::KeyUp && event.key() == Key_Super) { + if (!m_dnd_client && !current_window_stack().active_input_tracking_window() && event.type() == Event::KeyUp && event.key() == Key_Super) { tell_wms_super_key_pressed(); return; } @@ -1281,59 +1436,110 @@ void WindowManager::process_key_event(KeyEvent& event) return; } - if (!m_active_input_window) + if (event.type() == Event::KeyDown && (event.modifiers() == (Mod_Ctrl | Mod_Alt) || event.modifiers() == (Mod_Ctrl | Mod_Shift | Mod_Alt)) && (window_stack_columns() > 1 || window_stack_rows() > 1)) { + auto& current_stack = current_window_stack(); + auto row = current_stack.row(); + auto column = current_stack.column(); + auto handle_window_stack_switch_key = [&]() { + switch (event.key()) { + case Key_Left: + if (column == 0) + return true; + column--; + return true; + case Key_Right: + if (column + 1 >= m_window_stacks[0].size()) + return true; + column++; + return true; + case Key_Up: + if (row == 0) + return true; + row--; + return true; + case Key_Down: + if (row + 1 >= m_window_stacks.size()) + return true; + row++; + return true; + default: + return false; + } + }; + if (handle_window_stack_switch_key()) { + Window* carry_window = nullptr; + auto& new_window_stack = m_window_stacks[row][column]; + if (&new_window_stack != ¤t_stack) { + if (event.modifiers() == (Mod_Ctrl | Mod_Shift | Mod_Alt)) + carry_window = this->active_window(); + } + // Call switch_to_window_stack even if we're not going to switch to another stack. + // We'll show the window stack switch overlay briefly! + switch_to_window_stack(new_window_stack, carry_window); + return; + } + } + + auto* active_input_window = current_window_stack().active_input_window(); + if (!active_input_window) return; - if (event.type() == Event::KeyDown && event.modifiers() == Mod_Super && m_active_input_window->type() != WindowType::Desktop) { + if (event.type() == Event::KeyDown && event.modifiers() == Mod_Super && active_input_window->type() != WindowType::Desktop) { if (event.key() == Key_Down) { - if (m_active_input_window->is_resizable() && m_active_input_window->is_maximized()) { - maximize_windows(*m_active_input_window, false); + if (active_input_window->is_resizable() && active_input_window->is_maximized()) { + maximize_windows(*active_input_window, false); return; } - if (m_active_input_window->is_minimizable()) - minimize_windows(*m_active_input_window, true); + if (active_input_window->is_minimizable()) + minimize_windows(*active_input_window, true); return; } - if (m_active_input_window->is_resizable()) { + if (active_input_window->is_resizable()) { if (event.key() == Key_Up) { - maximize_windows(*m_active_input_window, !m_active_input_window->is_maximized()); + maximize_windows(*active_input_window, !active_input_window->is_maximized()); return; } if (event.key() == Key_Left) { - if (m_active_input_window->tiled() == WindowTileType::Left) + if (active_input_window->tiled() == WindowTileType::Left) return; - if (m_active_input_window->tiled() != WindowTileType::None) { - m_active_input_window->set_untiled(); + if (active_input_window->tiled() != WindowTileType::None) { + active_input_window->set_untiled(); return; } - if (m_active_input_window->is_maximized()) - maximize_windows(*m_active_input_window, false); - m_active_input_window->set_tiled(nullptr, WindowTileType::Left); + if (active_input_window->is_maximized()) + maximize_windows(*active_input_window, false); + active_input_window->set_tiled(nullptr, WindowTileType::Left); return; } if (event.key() == Key_Right) { - if (m_active_input_window->tiled() == WindowTileType::Right) + if (active_input_window->tiled() == WindowTileType::Right) return; - if (m_active_input_window->tiled() != WindowTileType::None) { - m_active_input_window->set_untiled(); + if (active_input_window->tiled() != WindowTileType::None) { + active_input_window->set_untiled(); return; } - if (m_active_input_window->is_maximized()) - maximize_windows(*m_active_input_window, false); - m_active_input_window->set_tiled(nullptr, WindowTileType::Right); + if (active_input_window->is_maximized()) + maximize_windows(*active_input_window, false); + active_input_window->set_tiled(nullptr, WindowTileType::Right); return; } } } - m_active_input_window->dispatch_event(event); + active_input_window->dispatch_event(event); } void WindowManager::set_highlight_window(Window* new_highlight_window) { - if (new_highlight_window == m_window_stack.highlight_window()) + // NOTE: The highlight window is global across all stacks. That's because we + // can only have one and we want to be able to highlight it during transitions + auto* previous_highlight_window = highlight_window(); + if (new_highlight_window == previous_highlight_window) return; - auto* previous_highlight_window = m_window_stack.highlight_window(); - m_window_stack.set_highlight_window(new_highlight_window); + if (!new_highlight_window) + m_highlight_window = nullptr; + else + m_highlight_window = new_highlight_window->make_weak_ptr<Window>(); + if (previous_highlight_window) { previous_highlight_window->invalidate(true, true); Compositor::the().invalidate_screen(previous_highlight_window->frame().render_rect()); @@ -1377,23 +1583,31 @@ void WindowManager::restore_active_input_window(Window* window) Window* WindowManager::set_active_input_window(Window* window) { - if (window == m_active_input_window) + auto& window_stack = current_window_stack(); + auto* previous_input_window = window_stack.active_input_window(); + if (window == previous_input_window) return window; - Window* previous_input_window = m_active_input_window; if (previous_input_window) - Core::EventLoop::current().post_event(*previous_input_window, make<Event>(Event::WindowInputLeft)); + notify_previous_active_input_window(*previous_input_window); - if (window) { - m_active_input_window = *window; - Core::EventLoop::current().post_event(*window, make<Event>(Event::WindowInputEntered)); - } else { - m_active_input_window = nullptr; - } + window_stack.set_active_input_window(window); + if (window) + notify_new_active_input_window(*window); return previous_input_window; } +void WindowManager::notify_new_active_input_window(Window& new_input_window) +{ + Core::EventLoop::current().post_event(new_input_window, make<Event>(Event::WindowInputEntered)); +} + +void WindowManager::notify_previous_active_input_window(Window& previous_input_window) +{ + Core::EventLoop::current().post_event(previous_input_window, make<Event>(Event::WindowInputLeft)); +} + void WindowManager::set_active_window(Window* new_active_window, bool make_input) { if (new_active_window) { @@ -1418,32 +1632,44 @@ void WindowManager::set_active_window(Window* new_active_window, bool make_input if (make_input) set_active_input_window(new_active_input_window); - if (new_active_window == m_window_stack.active_window()) + auto& window_stack = current_window_stack(); + if (new_active_window == window_stack.active_window()) return; - if (auto* previously_active_window = m_window_stack.active_window()) { - for (auto& child_window : previously_active_window->child_windows()) { - if (child_window && child_window->type() == WindowType::Tooltip) - child_window->request_close(); - } - Core::EventLoop::current().post_event(*previously_active_window, make<Event>(Event::WindowDeactivated)); - previously_active_window->invalidate(true, true); - m_window_stack.set_active_window(nullptr); - m_active_input_tracking_window = nullptr; - tell_wms_window_state_changed(*previously_active_window); + if (auto* previously_active_window = window_stack.active_window()) { + window_stack.set_active_window(nullptr); + window_stack.set_active_input_tracking_window(nullptr); + notify_previous_active_window(*previously_active_window); } if (new_active_window) { - m_window_stack.set_active_window(new_active_window); - Core::EventLoop::current().post_event(*new_active_window, make<Event>(Event::WindowActivated)); - new_active_window->invalidate(true, true); - tell_wms_window_state_changed(*new_active_window); + window_stack.set_active_window(new_active_window); + notify_new_active_window(*new_active_window); } // Window shapes may have changed (e.g. shadows for inactive/active windows) Compositor::the().invalidate_occlusions(); } +void WindowManager::notify_new_active_window(Window& new_active_window) +{ + Core::EventLoop::current().post_event(new_active_window, make<Event>(Event::WindowActivated)); + new_active_window.invalidate(true, true); + tell_wms_window_state_changed(new_active_window); +} + +void WindowManager::notify_previous_active_window(Window& previously_active_window) +{ + for (auto& child_window : previously_active_window.child_windows()) { + if (child_window && child_window->type() == WindowType::Tooltip) + child_window->request_close(); + } + Core::EventLoop::current().post_event(previously_active_window, make<Event>(Event::WindowDeactivated)); + previously_active_window.invalidate(true, true); + + tell_wms_window_state_changed(previously_active_window); +} + bool WindowManager::set_hovered_window(Window* window) { if (m_hovered_window == window) @@ -1461,7 +1687,7 @@ bool WindowManager::set_hovered_window(Window* window) ClientConnection const* WindowManager::active_client() const { - if (auto* window = m_window_stack.active_window()) + if (auto* window = const_cast<WindowManager*>(this)->current_window_stack().active_window()) return window->client(); return nullptr; } @@ -1534,7 +1760,7 @@ Gfx::IntRect WindowManager::maximized_window_rect(Window const& window, bool rel if (screen.is_main_screen()) { // Subtract taskbar window height if present - const_cast<WindowManager*>(this)->m_window_stack.for_each_visible_window_of_type_from_back_to_front(WindowType::Taskbar, [&rect](Window& taskbar_window) { + const_cast<WindowManager*>(this)->current_window_stack().for_each_visible_window_of_type_from_back_to_front(WindowType::Taskbar, [&rect](Window& taskbar_window) { rect.set_height(rect.height() - taskbar_window.height()); return IterationDecision::Break; }); @@ -1558,7 +1784,7 @@ void WindowManager::start_dnd_drag(ClientConnection& client, String const& text, m_dnd_overlay->set_enabled(true); m_dnd_mime_data = mime_data; Compositor::the().invalidate_cursor(); - m_active_input_tracking_window = nullptr; + current_window_stack().set_active_input_tracking_window(nullptr); } void WindowManager::end_dnd_drag() @@ -1574,8 +1800,11 @@ void WindowManager::invalidate_after_theme_or_font_change() { Compositor::the().set_background_color(m_config->read_entry("Background", "Color", palette().desktop_background().to_string())); WindowFrame::reload_config(); - m_window_stack.for_each_window([&](Window& window) { - window.frame().theme_changed(); + for_each_window_stack([&](auto& window_stack) { + window_stack.for_each_window([&](Window& window) { + window.frame().theme_changed(); + return IterationDecision::Continue; + }); return IterationDecision::Continue; }); ClientConnection::for_each_client([&](ClientConnection& client) { @@ -1604,10 +1833,11 @@ bool WindowManager::update_theme(String theme_path, String theme_name) void WindowManager::did_popup_a_menu(Badge<Menu>) { // Clear any ongoing input gesture - if (!m_active_input_tracking_window) + auto* active_input_tracking_window = current_window_stack().active_input_tracking_window(); + if (!active_input_tracking_window) return; - m_active_input_tracking_window->set_automatic_cursor_tracking_enabled(false); - m_active_input_tracking_window = nullptr; + active_input_tracking_window->set_automatic_cursor_tracking_enabled(false); + active_input_tracking_window = nullptr; } void WindowManager::minimize_windows(Window& window, bool minimized) @@ -1638,7 +1868,7 @@ Gfx::IntPoint WindowManager::get_recommended_window_position(Gfx::IntPoint const int taskbar_height = 28; Window const* overlap_window = nullptr; - m_window_stack.for_each_visible_window_of_type_from_front_to_back(WindowType::Normal, [&](Window& window) { + current_window_stack().for_each_visible_window_of_type_from_front_to_back(WindowType::Normal, [&](Window& window) { if (window.default_positioned() && (!overlap_window || overlap_window->window_id() < window.window_id())) { overlap_window = &window; } @@ -1663,9 +1893,12 @@ Gfx::IntPoint WindowManager::get_recommended_window_position(Gfx::IntPoint const void WindowManager::reload_icon_bitmaps_after_scale_change() { reload_config(); - m_window_stack.for_each_window([&](Window& window) { - auto& window_frame = window.frame(); - window_frame.theme_changed(); + for_each_window_stack([&](auto& window_stack) { + window_stack.for_each_window([&](Window& window) { + auto& window_frame = window.frame(); + window_frame.theme_changed(); + return IterationDecision::Continue; + }); return IterationDecision::Continue; }); } @@ -1679,4 +1912,10 @@ void WindowManager::set_window_with_active_menu(Window* window) else m_window_with_active_menu = nullptr; } + +WindowStack& WindowManager::get_rendering_window_stacks(WindowStack*& transitioning_window_stack) +{ + return Compositor::the().get_rendering_window_stacks(transitioning_window_stack); +} + } diff --git a/Userland/Services/WindowServer/WindowManager.h b/Userland/Services/WindowServer/WindowManager.h index 3dfa0b6ab6..6d1b64e572 100644 --- a/Userland/Services/WindowServer/WindowManager.h +++ b/Userland/Services/WindowServer/WindowManager.h @@ -90,17 +90,36 @@ public: void start_dnd_drag(ClientConnection&, String const& text, Gfx::Bitmap const*, Core::MimeData const&); void end_dnd_drag(); - Window* active_window() { return m_window_stack.active_window(); } - Window const* active_window() const { return m_window_stack.active_window(); } - Window* active_input_window() { return m_active_input_window.ptr(); } - Window const* active_input_window() const { return m_active_input_window.ptr(); } + Window* active_window() + { + VERIFY(m_current_window_stack); + return m_current_window_stack->active_window(); + } + Window const* active_window() const + { + VERIFY(m_current_window_stack); + return m_current_window_stack->active_window(); + } + + Window* active_input_window() + { + VERIFY(m_current_window_stack); + return m_current_window_stack->active_input_window(); + } + Window const* active_input_window() const + { + VERIFY(m_current_window_stack); + return m_current_window_stack->active_input_window(); + } + ClientConnection const* active_client() const; Window* window_with_active_menu() { return m_window_with_active_menu; } Window const* window_with_active_menu() const { return m_window_with_active_menu; } void set_window_with_active_menu(Window*); - Window const* highlight_window() const { return m_window_stack.highlight_window(); } + Window* highlight_window() { return m_highlight_window; } + Window const* highlight_window() const { return m_highlight_window; } void set_highlight_window(Window*); void move_to_front_and_make_active(Window&); @@ -223,6 +242,7 @@ public: return f(window, true); } } + bool is_window_in_modal_stack(Window& window_in_modal_stack, Window& other_window); Gfx::IntPoint get_recommended_window_position(Gfx::IntPoint const& desired); @@ -231,13 +251,61 @@ public: void reevaluate_hovered_window(Window* = nullptr); Window* hovered_window() const { return m_hovered_window.ptr(); } - WindowStack& window_stack() { return m_window_stack; } + void switch_to_window_stack(WindowStack&, Window* = nullptr); + + size_t window_stack_rows() const { return m_window_stacks.size(); } + size_t window_stack_columns() const { return m_window_stacks[0].size(); } + + WindowStack& current_window_stack() + { + VERIFY(m_current_window_stack); + return *m_current_window_stack; + } + + template<typename F> + IterationDecision for_each_window_stack(F f) + { + for (auto& row : m_window_stacks) { + for (auto& stack : row) { + IterationDecision decision = f(stack); + if (decision != IterationDecision::Continue) + return decision; + } + } + return IterationDecision::Continue; + } + + WindowStack& window_stack_for_window(Window&); + + static constexpr bool is_stationary_window_type(WindowType window_type) + { + switch (window_type) { + case WindowType::Normal: + case WindowType::ToolWindow: + case WindowType::Tooltip: + return false; + default: + return true; + } + } + + void did_switch_window_stack(Badge<Compositor>, WindowStack&, WindowStack&); + + template<typename Callback> + IterationDecision for_each_visible_window_from_back_to_front(Callback, WindowStack* = nullptr); + template<typename Callback> + IterationDecision for_each_visible_window_from_front_to_back(Callback, WindowStack* = nullptr); MultiScaleBitmaps const* overlay_rect_shadow() const { return m_overlay_rect_shadow.ptr(); } private: RefPtr<Cursor> get_cursor(String const& name); + void notify_new_active_window(Window&); + void notify_new_active_input_window(Window&); + void notify_previous_active_window(Window&); + void notify_previous_active_input_window(Window&); + void process_mouse_event(MouseEvent&); void process_event_for_doubleclick(Window& window, MouseEvent& event); bool process_ongoing_window_resize(MouseEvent const&); @@ -260,6 +328,8 @@ private: void do_move_to_front(Window&, bool, bool); + [[nodiscard]] static WindowStack& get_rendering_window_stacks(WindowStack*&); + RefPtr<Cursor> m_hidden_cursor; RefPtr<Cursor> m_arrow_cursor; RefPtr<Cursor> m_hand_cursor; @@ -279,7 +349,9 @@ private: RefPtr<MultiScaleBitmaps> m_overlay_rect_shadow; - WindowStack m_window_stack; + // Setup 2 rows 1 column by default + NonnullOwnPtrVector<NonnullOwnPtrVector<WindowStack, 3>, 2> m_window_stacks; + WindowStack* m_current_window_stack { nullptr }; struct DoubleClickInfo { struct ClickMetadata { @@ -317,8 +389,7 @@ private: bool m_previous_event_was_super_keydown { false }; WeakPtr<Window> m_hovered_window; - WeakPtr<Window> m_active_input_window; - WeakPtr<Window> m_active_input_tracking_window; + WeakPtr<Window> m_highlight_window; WeakPtr<Window> m_window_with_active_menu; OwnPtr<WindowGeometryOverlay> m_geometry_overlay; @@ -349,9 +420,86 @@ private: String m_dnd_text; RefPtr<Core::MimeData> m_dnd_mime_data; + + WindowStack* m_switching_to_window_stack { nullptr }; + Vector<WeakPtr<Window>, 4> m_carry_window_to_new_stack; }; template<typename Callback> +inline IterationDecision WindowManager::for_each_visible_window_from_back_to_front(Callback callback, WindowStack* specific_stack) +{ + auto* window_stack = specific_stack; + WindowStack* transitioning_to_window_stack = nullptr; + if (!window_stack) + window_stack = &get_rendering_window_stacks(transitioning_to_window_stack); + auto for_each_window = [&]<WindowType window_type>() { + if constexpr (is_stationary_window_type(window_type)) { + auto& stationary_stack = window_stack->stationary_window_stack(); + return stationary_stack.for_each_visible_window_of_type_from_back_to_front(window_type, callback); + } else { + auto decision = window_stack->for_each_visible_window_of_type_from_back_to_front(window_type, callback); + if (decision == IterationDecision::Continue && transitioning_to_window_stack) + decision = transitioning_to_window_stack->for_each_visible_window_of_type_from_back_to_front(window_type, callback); + return decision; + } + }; + if (for_each_window.template operator()<WindowType::Desktop>() == IterationDecision::Break) + return IterationDecision::Break; + if (for_each_window.template operator()<WindowType::Normal>() == IterationDecision::Break) + return IterationDecision::Break; + if (for_each_window.template operator()<WindowType::ToolWindow>() == IterationDecision::Break) + return IterationDecision::Break; + if (for_each_window.template operator()<WindowType::Taskbar>() == IterationDecision::Break) + return IterationDecision::Break; + if (for_each_window.template operator()<WindowType::AppletArea>() == IterationDecision::Break) + return IterationDecision::Break; + if (for_each_window.template operator()<WindowType::Notification>() == IterationDecision::Break) + return IterationDecision::Break; + if (for_each_window.template operator()<WindowType::Tooltip>() == IterationDecision::Break) + return IterationDecision::Break; + if (for_each_window.template operator()<WindowType::Menu>() == IterationDecision::Break) + return IterationDecision::Break; + return for_each_window.template operator()<WindowType::WindowSwitcher>(); +} + +template<typename Callback> +inline IterationDecision WindowManager::for_each_visible_window_from_front_to_back(Callback callback, WindowStack* specific_stack) +{ + auto* window_stack = specific_stack; + WindowStack* transitioning_to_window_stack = nullptr; + if (!window_stack) + window_stack = &get_rendering_window_stacks(transitioning_to_window_stack); + auto for_each_window = [&]<WindowType window_type>() { + if constexpr (is_stationary_window_type(window_type)) { + auto& stationary_stack = window_stack->stationary_window_stack(); + return stationary_stack.for_each_visible_window_of_type_from_front_to_back(window_type, callback); + } else { + auto decision = window_stack->for_each_visible_window_of_type_from_front_to_back(window_type, callback); + if (decision == IterationDecision::Continue && transitioning_to_window_stack) + decision = transitioning_to_window_stack->for_each_visible_window_of_type_from_front_to_back(window_type, callback); + return decision; + } + }; + if (for_each_window.template operator()<WindowType::WindowSwitcher>() == IterationDecision::Break) + return IterationDecision::Break; + if (for_each_window.template operator()<WindowType::Menu>() == IterationDecision::Break) + return IterationDecision::Break; + if (for_each_window.template operator()<WindowType::Tooltip>() == IterationDecision::Break) + return IterationDecision::Break; + if (for_each_window.template operator()<WindowType::Notification>() == IterationDecision::Break) + return IterationDecision::Break; + if (for_each_window.template operator()<WindowType::AppletArea>() == IterationDecision::Break) + return IterationDecision::Break; + if (for_each_window.template operator()<WindowType::Taskbar>() == IterationDecision::Break) + return IterationDecision::Break; + if (for_each_window.template operator()<WindowType::ToolWindow>() == IterationDecision::Break) + return IterationDecision::Break; + if (for_each_window.template operator()<WindowType::Normal>() == IterationDecision::Break) + return IterationDecision::Break; + return for_each_window.template operator()<WindowType::Desktop>(); +} + +template<typename Callback> void WindowManager::for_each_window_manager(Callback callback) { auto& connections = WMClientConnection::s_connections; diff --git a/Userland/Services/WindowServer/WindowStack.cpp b/Userland/Services/WindowServer/WindowStack.cpp index 3b914f2b8b..f10a9641b2 100644 --- a/Userland/Services/WindowServer/WindowStack.cpp +++ b/Userland/Services/WindowServer/WindowStack.cpp @@ -5,10 +5,13 @@ */ #include "WindowStack.h" +#include "WindowManager.h" namespace WindowServer { -WindowStack::WindowStack() +WindowStack::WindowStack(unsigned row, unsigned column) + : m_row(row) + , m_column(column) { } @@ -28,6 +31,12 @@ void WindowStack::remove(Window& window) VERIFY(window.outer_stack() == this); m_windows.remove(window); window.set_outer_stack({}, nullptr); + if (m_active_window == &window) + m_active_window = nullptr; + if (m_active_input_window == &window) + m_active_input_window = nullptr; + if (m_active_input_tracking_window == &window) + m_active_input_tracking_window = nullptr; } void WindowStack::move_to_front(Window& window) @@ -48,12 +57,11 @@ Window* WindowStack::window_at(Gfx::IntPoint const& position, IncludeWindowFrame return result->window; } -void WindowStack::set_highlight_window(Window* window) +Window* WindowStack::highlight_window() const { - if (!window) - m_highlight_window = nullptr; - else - m_highlight_window = window->make_weak_ptr<Window>(); + if (auto* window = WindowManager::the().highlight_window(); window && window->outer_stack() == this) + return window; + return nullptr; } void WindowStack::set_active_window(Window* window) @@ -64,15 +72,24 @@ void WindowStack::set_active_window(Window* window) m_active_window = window->make_weak_ptr<Window>(); } +void WindowStack::set_all_occluded(bool occluded) +{ + for (auto& window : m_windows) { + if (!WindowManager::is_stationary_window_type(window.type())) + window.set_occluded(occluded); + } +} + Optional<HitTestResult> WindowStack::hit_test(Gfx::IntPoint const& position) const { Optional<HitTestResult> result; - const_cast<WindowStack*>(this)->for_each_visible_window_from_front_to_back([&](Window& window) { + WindowManager::the().for_each_visible_window_from_front_to_back([&](Window& window) { result = window.hit_test(position); if (result.has_value()) return IterationDecision::Break; return IterationDecision::Continue; - }); + }, + const_cast<WindowStack*>(this)); return result; } diff --git a/Userland/Services/WindowServer/WindowStack.h b/Userland/Services/WindowServer/WindowStack.h index ccabb8bb6a..a4aaec944e 100644 --- a/Userland/Services/WindowServer/WindowStack.h +++ b/Userland/Services/WindowServer/WindowStack.h @@ -10,9 +10,11 @@ namespace WindowServer { +class Compositor; + class WindowStack { public: - WindowStack(); + WindowStack(unsigned row, unsigned column); ~WindowStack(); bool is_empty() const { return m_windows.is_empty(); } @@ -25,12 +27,9 @@ public: No, }; Window* window_at(Gfx::IntPoint const&, IncludeWindowFrame = IncludeWindowFrame::Yes) const; + Window* highlight_window() const; template<typename Callback> - IterationDecision for_each_visible_window_from_back_to_front(Callback); - template<typename Callback> - IterationDecision for_each_visible_window_from_front_to_back(Callback); - template<typename Callback> IterationDecision for_each_visible_window_of_type_from_front_to_back(WindowType, Callback, bool ignore_highlight = false); template<typename Callback> IterationDecision for_each_visible_window_of_type_from_back_to_front(WindowType, Callback, bool ignore_highlight = false); @@ -42,26 +41,51 @@ public: Window::List& windows() { return m_windows; } - Window* highlight_window() { return m_highlight_window; } - Window const* highlight_window() const { return m_highlight_window; } - void set_highlight_window(Window*); - Window* active_window() { return m_active_window; } Window const* active_window() const { return m_active_window; } void set_active_window(Window*); + Window* active_input_window() { return m_active_input_window; } + Window const* active_input_window() const { return m_active_input_window; } + void set_active_input_window(Window* window) { m_active_input_window = window; } + + Window* active_input_tracking_window() { return m_active_input_tracking_window; } + Window const* active_input_tracking_window() const { return m_active_input_tracking_window; } + void set_active_input_tracking_window(Window* window) { m_active_input_tracking_window = window; } + Optional<HitTestResult> hit_test(Gfx::IntPoint const&) const; + unsigned row() const { return m_row; } + unsigned column() const { return m_column; } + + void set_transition_offset(Badge<Compositor>, Gfx::IntPoint const& transition_offset) { m_transition_offset = transition_offset; } + Gfx::IntPoint const& transition_offset() const { return m_transition_offset; } + + void set_stationary_window_stack(WindowStack& window_stack) { m_stationary_window_stack = &window_stack; } + WindowStack& stationary_window_stack() + { + VERIFY(m_stationary_window_stack); + return *m_stationary_window_stack; + } + + void set_all_occluded(bool); + private: - WeakPtr<Window> m_highlight_window; WeakPtr<Window> m_active_window; + WeakPtr<Window> m_active_input_window; + WeakPtr<Window> m_active_input_tracking_window; Window::List m_windows; + unsigned m_row { 0 }; + unsigned m_column { 0 }; + Gfx::IntPoint m_transition_offset; + WindowStack* m_stationary_window_stack { nullptr }; }; template<typename Callback> inline IterationDecision WindowStack::for_each_visible_window_of_type_from_back_to_front(WindowType type, Callback callback, bool ignore_highlight) { + auto* highlight_window = this->highlight_window(); bool do_highlight_window_at_end = false; for (auto& window : m_windows) { if (!window.is_visible()) @@ -70,7 +94,7 @@ inline IterationDecision WindowStack::for_each_visible_window_of_type_from_back_ continue; if (window.type() != type) continue; - if (!ignore_highlight && m_highlight_window == &window) { + if (!ignore_highlight && highlight_window == &window) { do_highlight_window_at_end = true; continue; } @@ -78,7 +102,7 @@ inline IterationDecision WindowStack::for_each_visible_window_of_type_from_back_ return IterationDecision::Break; } if (do_highlight_window_at_end) { - if (callback(*m_highlight_window) == IterationDecision::Break) + if (callback(*highlight_window) == IterationDecision::Break) return IterationDecision::Break; } return IterationDecision::Continue; @@ -87,8 +111,9 @@ inline IterationDecision WindowStack::for_each_visible_window_of_type_from_back_ template<typename Callback> inline IterationDecision WindowStack::for_each_visible_window_of_type_from_front_to_back(WindowType type, Callback callback, bool ignore_highlight) { - if (!ignore_highlight && m_highlight_window && m_highlight_window->type() == type && m_highlight_window->is_visible()) { - if (callback(*m_highlight_window) == IterationDecision::Break) + auto* highlight_window = this->highlight_window(); + if (!ignore_highlight && highlight_window && highlight_window->type() == type && highlight_window->is_visible()) { + if (callback(*highlight_window) == IterationDecision::Break) return IterationDecision::Break; } @@ -101,7 +126,7 @@ inline IterationDecision WindowStack::for_each_visible_window_of_type_from_front continue; if (window.type() != type) continue; - if (!ignore_highlight && &window == m_highlight_window) + if (!ignore_highlight && &window == highlight_window) continue; if (callback(window) == IterationDecision::Break) return IterationDecision::Break; @@ -121,54 +146,11 @@ inline void WindowStack::for_each_window(Callback callback) } template<typename Callback> -inline IterationDecision WindowStack::for_each_visible_window_from_back_to_front(Callback callback) -{ - if (for_each_visible_window_of_type_from_back_to_front(WindowType::Desktop, callback) == IterationDecision::Break) - return IterationDecision::Break; - if (for_each_visible_window_of_type_from_back_to_front(WindowType::Normal, callback) == IterationDecision::Break) - return IterationDecision::Break; - if (for_each_visible_window_of_type_from_back_to_front(WindowType::ToolWindow, callback) == IterationDecision::Break) - return IterationDecision::Break; - if (for_each_visible_window_of_type_from_back_to_front(WindowType::Taskbar, callback) == IterationDecision::Break) - return IterationDecision::Break; - if (for_each_visible_window_of_type_from_back_to_front(WindowType::AppletArea, callback) == IterationDecision::Break) - return IterationDecision::Break; - if (for_each_visible_window_of_type_from_back_to_front(WindowType::Notification, callback) == IterationDecision::Break) - return IterationDecision::Break; - if (for_each_visible_window_of_type_from_back_to_front(WindowType::Tooltip, callback) == IterationDecision::Break) - return IterationDecision::Break; - if (for_each_visible_window_of_type_from_back_to_front(WindowType::Menu, callback) == IterationDecision::Break) - return IterationDecision::Break; - return for_each_visible_window_of_type_from_back_to_front(WindowType::WindowSwitcher, callback); -} - -template<typename Callback> -inline IterationDecision WindowStack::for_each_visible_window_from_front_to_back(Callback callback) -{ - if (for_each_visible_window_of_type_from_front_to_back(WindowType::WindowSwitcher, callback) == IterationDecision::Break) - return IterationDecision::Break; - if (for_each_visible_window_of_type_from_front_to_back(WindowType::Menu, callback) == IterationDecision::Break) - return IterationDecision::Break; - if (for_each_visible_window_of_type_from_front_to_back(WindowType::Tooltip, callback) == IterationDecision::Break) - return IterationDecision::Break; - if (for_each_visible_window_of_type_from_front_to_back(WindowType::Notification, callback) == IterationDecision::Break) - return IterationDecision::Break; - if (for_each_visible_window_of_type_from_front_to_back(WindowType::AppletArea, callback) == IterationDecision::Break) - return IterationDecision::Break; - if (for_each_visible_window_of_type_from_front_to_back(WindowType::Taskbar, callback) == IterationDecision::Break) - return IterationDecision::Break; - if (for_each_visible_window_of_type_from_front_to_back(WindowType::ToolWindow, callback) == IterationDecision::Break) - return IterationDecision::Break; - if (for_each_visible_window_of_type_from_front_to_back(WindowType::Normal, callback) == IterationDecision::Break) - return IterationDecision::Break; - return for_each_visible_window_of_type_from_front_to_back(WindowType::Desktop, callback); -} - -template<typename Callback> inline IterationDecision WindowStack::for_each_window_of_type_from_front_to_back(WindowType type, Callback callback, bool ignore_highlight) { - if (!ignore_highlight && m_highlight_window && m_highlight_window->type() == type && m_highlight_window->is_visible()) { - if (callback(*m_highlight_window) == IterationDecision::Break) + auto* highlight_window = this->highlight_window(); + if (!ignore_highlight && highlight_window && highlight_window->type() == type && highlight_window->is_visible()) { + if (callback(*highlight_window) == IterationDecision::Break) return IterationDecision::Break; } @@ -177,7 +159,7 @@ inline IterationDecision WindowStack::for_each_window_of_type_from_front_to_back auto& window = *reverse_iterator; if (window.type() != type) continue; - if (!ignore_highlight && &window == m_highlight_window) + if (!ignore_highlight && &window == highlight_window) continue; if (callback(window) == IterationDecision::Break) return IterationDecision::Break; diff --git a/Userland/Services/WindowServer/WindowSwitcher.cpp b/Userland/Services/WindowServer/WindowSwitcher.cpp index ecc75a9bce..2ba8aafcbd 100644 --- a/Userland/Services/WindowServer/WindowSwitcher.cpp +++ b/Userland/Services/WindowServer/WindowSwitcher.cpp @@ -202,18 +202,21 @@ void WindowSwitcher::refresh() m_selected_index = 0; int window_count = 0; int longest_title_width = 0; - wm.window_stack().for_each_window_of_type_from_front_to_back( - WindowType::Normal, [&](Window& window) { - if (window.is_frameless()) + wm.for_each_window_stack([&](auto& window_stack) { + window_stack.for_each_window_of_type_from_front_to_back( + WindowType::Normal, [&](Window& window) { + if (window.is_frameless()) + return IterationDecision::Continue; + ++window_count; + longest_title_width = max(longest_title_width, wm.font().width(window.computed_title())); + if (selected_window == &window) + m_selected_index = m_windows.size(); + m_windows.append(window); return IterationDecision::Continue; - ++window_count; - longest_title_width = max(longest_title_width, wm.font().width(window.computed_title())); - if (selected_window == &window) - m_selected_index = m_windows.size(); - m_windows.append(window); - return IterationDecision::Continue; - }, - true); + }, + true); + return IterationDecision::Continue; + }); if (m_windows.is_empty()) { hide(); return; |