WindowServer: Add basic virtual desktop support

This creates a 2-dimensional array of WindowStack instances, one for
each virtual desktop. The main desktop 0,0 is the main desktop, which
is the desktop used for all stationary windows (e.g. taskbar, desktop).
When adding windows to a desktop, stationary windows are always added
to the main desktop.

When composing the desktop, there are usually two WindowStacks
involved. For stationary windows, the main desktop will be traversed,
and for everything else the current virtual desktop will be iterated.
Iteration is interweaved to preserve the correct order. During the
transition animation, two WindowStacks will be iterated at the same
time.
This commit is contained in:
Tom 2021-06-29 19:51:26 -06:00 committed by Andreas Kling
parent 944e5cfb35
commit 584b144953
Notes: sideshowbarker 2024-07-18 11:04:37 +09:00
11 changed files with 900 additions and 213 deletions

View file

@ -129,6 +129,17 @@ public:
const Vector<IntRect, 32>& rects() const { return m_rects; }
Vector<IntRect, 32> take_rects() { return move(m_rects); }
void translate_by(int dx, int dy)
{
for (auto& rect : m_rects)
rect.translate_by(dx, dy);
}
void translate_by(Gfx::IntPoint const& delta)
{
for (auto& rect : m_rects)
rect.translate_by(delta);
}
private:
bool add_no_shatter(const IntRect&);
void shatter();

View file

@ -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();
}
}

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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;
};
}

View file

@ -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 };

View file

@ -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(&current_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)
return;
if (event.type() == Event::KeyDown && event.modifiers() == Mod_Super && m_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);
return;
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 (m_active_input_window->is_minimizable())
minimize_windows(*m_active_input_window, true);
};
if (handle_window_stack_switch_key()) {
Window* carry_window = nullptr;
auto& new_window_stack = m_window_stacks[row][column];
if (&new_window_stack != &current_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;
}
if (m_active_input_window->is_resizable()) {
}
auto* active_input_window = current_window_stack().active_input_window();
if (!active_input_window)
return;
if (event.type() == Event::KeyDown && event.modifiers() == Mod_Super && active_input_window->type() != WindowType::Desktop) {
if (event.key() == Key_Down) {
if (active_input_window->is_resizable() && active_input_window->is_maximized()) {
maximize_windows(*active_input_window, false);
return;
}
if (active_input_window->is_minimizable())
minimize_windows(*active_input_window, true);
return;
}
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);
}
}

View file

@ -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,8 +420,85 @@ 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)
{

View file

@ -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;
}

View file

@ -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,11 +27,8 @@ 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>
@ -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;
@ -120,55 +145,12 @@ 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;

View file

@ -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;