mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-26 09:30:24 +00:00
WindowServer: Add a window switcher.
This needs some work on the window ordering and things like that, but it works quite nicely as a starting point. The keyboard shortcut is Logo+Tab. :^)
This commit is contained in:
parent
483e0a5526
commit
5c27e74fac
Notes:
sideshowbarker
2024-07-19 15:33:33 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/5c27e74fac5
7 changed files with 277 additions and 50 deletions
|
@ -17,6 +17,7 @@ WINDOWSERVER_OBJS = \
|
|||
WSMenu.o \
|
||||
WSMenuItem.o \
|
||||
WSClientConnection.o \
|
||||
WSWindowSwitcher.o \
|
||||
main.o
|
||||
|
||||
APP = WindowServer
|
||||
|
|
|
@ -148,3 +148,8 @@ void WSWindow::invalidate()
|
|||
{
|
||||
WSWindowManager::the().invalidate(*this);
|
||||
}
|
||||
|
||||
bool WSWindow::is_active() const
|
||||
{
|
||||
return WSWindowManager::the().active_window() == this;
|
||||
}
|
||||
|
|
|
@ -33,6 +33,8 @@ public:
|
|||
int width() const { return m_rect.width(); }
|
||||
int height() const { return m_rect.height(); }
|
||||
|
||||
bool is_active() const;
|
||||
|
||||
bool is_visible() const { return m_visible; }
|
||||
void set_visible(bool);
|
||||
|
||||
|
|
|
@ -172,6 +172,9 @@ WSWindowManager::WSWindowManager()
|
|||
m_dragging_window_border_color = Color(161, 50, 13);
|
||||
m_dragging_window_border_color2 = Color(250, 220, 187);
|
||||
m_dragging_window_title_color = Color::White;
|
||||
m_highlight_window_border_color = Color::from_rgb(0xa10d0d);
|
||||
m_highlight_window_border_color2 = Color::from_rgb(0xfabbbb);
|
||||
m_highlight_window_title_color = Color::White;
|
||||
|
||||
m_cursor_bitmap_inner = CharacterBitmap::create_from_ascii(cursor_bitmap_inner_ascii, 12, 17);
|
||||
m_cursor_bitmap_outer = CharacterBitmap::create_from_ascii(cursor_bitmap_outer_ascii, 12, 17);
|
||||
|
@ -402,7 +405,12 @@ void WSWindowManager::paint_window_frame(WSWindow& window)
|
|||
Color border_color2;
|
||||
Color middle_border_color;
|
||||
|
||||
if (&window == m_drag_window.ptr()) {
|
||||
if (&window == m_highlight_window.ptr()) {
|
||||
border_color = m_highlight_window_border_color;
|
||||
border_color2 = m_highlight_window_border_color2;
|
||||
title_color = m_highlight_window_title_color;
|
||||
middle_border_color = Color::White;
|
||||
} else if (&window == m_drag_window.ptr()) {
|
||||
border_color = m_dragging_window_border_color;
|
||||
border_color2 = m_dragging_window_border_color2;
|
||||
title_color = m_dragging_window_title_color;
|
||||
|
@ -458,6 +466,8 @@ void WSWindowManager::add_window(WSWindow& window)
|
|||
m_windows_in_order.append(&window);
|
||||
if (!active_window())
|
||||
set_active_window(&window);
|
||||
if (m_switcher.is_visible())
|
||||
m_switcher.invalidate();
|
||||
}
|
||||
|
||||
void WSWindowManager::move_to_front(WSWindow& window)
|
||||
|
@ -478,12 +488,16 @@ void WSWindowManager::remove_window(WSWindow& window)
|
|||
m_windows_in_order.remove(&window);
|
||||
if (!active_window() && !m_windows.is_empty())
|
||||
set_active_window(*m_windows.begin());
|
||||
if (m_switcher.is_visible())
|
||||
m_switcher.invalidate();
|
||||
}
|
||||
|
||||
void WSWindowManager::notify_title_changed(WSWindow& window)
|
||||
{
|
||||
printf("[WM] WSWindow{%p} title set to '%s'\n", &window, window.title().characters());
|
||||
invalidate(outer_window_rect(window.rect()));
|
||||
if (m_switcher.is_visible())
|
||||
m_switcher.invalidate();
|
||||
}
|
||||
|
||||
void WSWindowManager::notify_rect_changed(WSWindow& window, const Rect& old_rect, const Rect& new_rect)
|
||||
|
@ -491,6 +505,8 @@ void WSWindowManager::notify_rect_changed(WSWindow& window, const Rect& old_rect
|
|||
printf("[WM] WSWindow %p rect changed (%d,%d %dx%d) -> (%d,%d %dx%d)\n", &window, old_rect.x(), old_rect.y(), old_rect.width(), old_rect.height(), new_rect.x(), new_rect.y(), new_rect.width(), new_rect.height());
|
||||
invalidate(outer_window_rect(old_rect));
|
||||
invalidate(outer_window_rect(new_rect));
|
||||
if (m_switcher.is_visible())
|
||||
m_switcher.invalidate();
|
||||
}
|
||||
|
||||
void WSWindowManager::handle_menu_mouse_event(WSMenu& menu, WSMouseEvent& event)
|
||||
|
@ -777,50 +793,6 @@ void WSWindowManager::process_mouse_event(WSMouseEvent& event, WSWindow*& event_
|
|||
});
|
||||
}
|
||||
|
||||
template<typename Callback>
|
||||
IterationDecision WSWindowManager::for_each_visible_window_of_type_from_back_to_front(WSWindowType type, Callback callback)
|
||||
{
|
||||
for (auto* window = m_windows_in_order.head(); window; window = window->next()) {
|
||||
if (!window->is_visible())
|
||||
continue;
|
||||
if (window->type() != type)
|
||||
continue;
|
||||
if (callback(*window) == IterationDecision::Abort)
|
||||
return IterationDecision::Abort;
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
}
|
||||
|
||||
template<typename Callback>
|
||||
IterationDecision WSWindowManager::for_each_visible_window_from_back_to_front(Callback callback)
|
||||
{
|
||||
if (for_each_visible_window_of_type_from_back_to_front(WSWindowType::Normal, callback) == IterationDecision::Abort)
|
||||
return IterationDecision::Abort;
|
||||
return for_each_visible_window_of_type_from_back_to_front(WSWindowType::Menu, callback);
|
||||
}
|
||||
|
||||
template<typename Callback>
|
||||
IterationDecision WSWindowManager::for_each_visible_window_of_type_from_front_to_back(WSWindowType type, Callback callback)
|
||||
{
|
||||
for (auto* window = m_windows_in_order.tail(); window; window = window->prev()) {
|
||||
if (!window->is_visible())
|
||||
continue;
|
||||
if (window->type() != type)
|
||||
continue;
|
||||
if (callback(*window) == IterationDecision::Abort)
|
||||
return IterationDecision::Abort;
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
}
|
||||
|
||||
template<typename Callback>
|
||||
IterationDecision WSWindowManager::for_each_visible_window_from_front_to_back(Callback callback)
|
||||
{
|
||||
if (for_each_visible_window_of_type_from_front_to_back(WSWindowType::Menu, callback) == IterationDecision::Abort)
|
||||
return IterationDecision::Abort;
|
||||
return for_each_visible_window_of_type_from_front_to_back(WSWindowType::Normal, callback);
|
||||
}
|
||||
|
||||
void WSWindowManager::compose()
|
||||
{
|
||||
auto dirty_rects = move(m_dirty_rects);
|
||||
|
@ -866,12 +838,12 @@ void WSWindowManager::compose()
|
|||
m_back_painter->blit(dirty_rect.location(), *m_wallpaper, dirty_rect);
|
||||
}
|
||||
|
||||
for_each_visible_window_from_back_to_front([&] (WSWindow& window) {
|
||||
auto compose_window = [&] (WSWindow& window) {
|
||||
RetainPtr<GraphicsBitmap> backing_store = window.backing_store();
|
||||
if (!backing_store)
|
||||
return IterationDecision::Continue;
|
||||
return;
|
||||
if (!any_dirty_rect_intersects_window(window))
|
||||
return IterationDecision::Continue;
|
||||
return;
|
||||
for (auto& dirty_rect : dirty_rects.rects()) {
|
||||
m_back_painter->set_clip_rect(dirty_rect);
|
||||
paint_window_frame(window);
|
||||
|
@ -891,10 +863,19 @@ void WSWindowManager::compose()
|
|||
m_back_painter->clear_clip_rect();
|
||||
}
|
||||
m_back_painter->clear_clip_rect();
|
||||
};
|
||||
|
||||
for_each_visible_window_from_back_to_front([&] (WSWindow& window) {
|
||||
if (&window != m_highlight_window.ptr())
|
||||
compose_window(window);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
if (m_highlight_window)
|
||||
compose_window(*m_highlight_window);
|
||||
|
||||
draw_menubar();
|
||||
draw_window_switcher();
|
||||
draw_cursor();
|
||||
|
||||
if (m_flash_flush) {
|
||||
|
@ -964,6 +945,12 @@ void WSWindowManager::draw_menubar()
|
|||
}
|
||||
}
|
||||
|
||||
void WSWindowManager::draw_window_switcher()
|
||||
{
|
||||
if (m_switcher.is_visible())
|
||||
m_switcher.draw(*m_back_painter);
|
||||
}
|
||||
|
||||
void WSWindowManager::draw_cursor()
|
||||
{
|
||||
auto cursor_location = m_screen.cursor_location();
|
||||
|
@ -987,8 +974,15 @@ void WSWindowManager::on_message(WSMessage& message)
|
|||
}
|
||||
|
||||
if (message.is_key_event()) {
|
||||
// FIXME: This is a good place to hook key events globally. :)
|
||||
m_keyboard_modifiers = static_cast<WSKeyEvent&>(message).modifiers();
|
||||
auto& key_event = static_cast<WSKeyEvent&>(message);
|
||||
m_keyboard_modifiers = key_event.modifiers();
|
||||
|
||||
if (key_event.type() == WSMessage::KeyDown && key_event.modifiers() == Mod_Logo && key_event.key() == Key_Tab)
|
||||
m_switcher.show();
|
||||
if (m_switcher.is_visible()) {
|
||||
m_switcher.on_key_event(key_event);
|
||||
return;
|
||||
}
|
||||
if (m_active_window)
|
||||
return m_active_window->on_message(message);
|
||||
return;
|
||||
|
@ -1001,6 +995,17 @@ void WSWindowManager::on_message(WSMessage& message)
|
|||
}
|
||||
}
|
||||
|
||||
void WSWindowManager::set_highlight_window(WSWindow* window)
|
||||
{
|
||||
if (window == m_highlight_window.ptr())
|
||||
return;
|
||||
if (auto* previous_highlight_window = m_highlight_window.ptr())
|
||||
invalidate(*previous_highlight_window);
|
||||
m_highlight_window = window ? window->make_weak_ptr() : nullptr;
|
||||
if (m_highlight_window)
|
||||
invalidate(*m_highlight_window);
|
||||
}
|
||||
|
||||
void WSWindowManager::set_active_window(WSWindow* window)
|
||||
{
|
||||
if (window->type() == WSWindowType::Menu) {
|
||||
|
|
|
@ -10,7 +10,9 @@
|
|||
#include <AK/HashMap.h>
|
||||
#include "WSMessageReceiver.h"
|
||||
#include "WSMenuBar.h"
|
||||
#include <WindowServer/WSWindowSwitcher.h>
|
||||
#include <WindowServer/WSWindowType.h>
|
||||
#include <WindowServer/WSWindow.h>
|
||||
#include <AK/CircularQueue.h>
|
||||
|
||||
class WSAPIClientRequest;
|
||||
|
@ -20,6 +22,7 @@ class WSMouseEvent;
|
|||
class WSClientWantsToPaintMessage;
|
||||
class WSWindow;
|
||||
class WSClientConnection;
|
||||
class WSWindowSwitcher;
|
||||
class CharacterBitmap;
|
||||
class GraphicsBitmap;
|
||||
|
||||
|
@ -27,6 +30,7 @@ enum class IterationDecision { Continue, Abort };
|
|||
enum class ResizeDirection { None, Left, UpLeft, Up, UpRight, Right, DownRight, Down, DownLeft };
|
||||
|
||||
class WSWindowManager : public WSMessageReceiver {
|
||||
friend class WSWindowSwitcher;
|
||||
public:
|
||||
static WSWindowManager& the();
|
||||
|
||||
|
@ -43,11 +47,15 @@ public:
|
|||
WSWindow* active_window() { return m_active_window.ptr(); }
|
||||
const WSClientConnection* active_client() const;
|
||||
|
||||
WSWindow* highlight_window() { return m_highlight_window.ptr(); }
|
||||
void set_highlight_window(WSWindow*);
|
||||
|
||||
void move_to_front(WSWindow&);
|
||||
|
||||
void invalidate_cursor();
|
||||
void draw_cursor();
|
||||
void draw_menubar();
|
||||
void draw_window_switcher();
|
||||
|
||||
Rect menubar_rect() const;
|
||||
WSMenuBar* current_menubar() { return m_current_menubar.ptr(); }
|
||||
|
@ -107,6 +115,9 @@ private:
|
|||
Color m_dragging_window_border_color;
|
||||
Color m_dragging_window_border_color2;
|
||||
Color m_dragging_window_title_color;
|
||||
Color m_highlight_window_border_color;
|
||||
Color m_highlight_window_border_color2;
|
||||
Color m_highlight_window_title_color;
|
||||
|
||||
HashMap<int, OwnPtr<WSWindow>> m_windows_by_id;
|
||||
HashTable<WSWindow*> m_windows;
|
||||
|
@ -114,6 +125,7 @@ private:
|
|||
|
||||
WeakPtr<WSWindow> m_active_window;
|
||||
WeakPtr<WSWindow> m_hovered_window;
|
||||
WeakPtr<WSWindow> m_highlight_window;
|
||||
|
||||
WeakPtr<WSWindow> m_drag_window;
|
||||
Point m_drag_origin;
|
||||
|
@ -157,5 +169,51 @@ private:
|
|||
WeakPtr<WSMenuBar> m_current_menubar;
|
||||
WeakPtr<WSMenu> m_current_menu;
|
||||
|
||||
WSWindowSwitcher m_switcher;
|
||||
|
||||
CircularQueue<float, 30> m_cpu_history;
|
||||
};
|
||||
|
||||
template<typename Callback>
|
||||
IterationDecision WSWindowManager::for_each_visible_window_of_type_from_back_to_front(WSWindowType type, Callback callback)
|
||||
{
|
||||
for (auto* window = m_windows_in_order.head(); window; window = window->next()) {
|
||||
if (!window->is_visible())
|
||||
continue;
|
||||
if (window->type() != type)
|
||||
continue;
|
||||
if (callback(*window) == IterationDecision::Abort)
|
||||
return IterationDecision::Abort;
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
}
|
||||
|
||||
template<typename Callback>
|
||||
IterationDecision WSWindowManager::for_each_visible_window_from_back_to_front(Callback callback)
|
||||
{
|
||||
if (for_each_visible_window_of_type_from_back_to_front(WSWindowType::Normal, callback) == IterationDecision::Abort)
|
||||
return IterationDecision::Abort;
|
||||
return for_each_visible_window_of_type_from_back_to_front(WSWindowType::Menu, callback);
|
||||
}
|
||||
|
||||
template<typename Callback>
|
||||
IterationDecision WSWindowManager::for_each_visible_window_of_type_from_front_to_back(WSWindowType type, Callback callback)
|
||||
{
|
||||
for (auto* window = m_windows_in_order.tail(); window; window = window->prev()) {
|
||||
if (!window->is_visible())
|
||||
continue;
|
||||
if (window->type() != type)
|
||||
continue;
|
||||
if (callback(*window) == IterationDecision::Abort)
|
||||
return IterationDecision::Abort;
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
}
|
||||
|
||||
template<typename Callback>
|
||||
IterationDecision WSWindowManager::for_each_visible_window_from_front_to_back(Callback callback)
|
||||
{
|
||||
if (for_each_visible_window_of_type_from_front_to_back(WSWindowType::Menu, callback) == IterationDecision::Abort)
|
||||
return IterationDecision::Abort;
|
||||
return for_each_visible_window_of_type_from_front_to_back(WSWindowType::Normal, callback);
|
||||
}
|
||||
|
|
118
WindowServer/WSWindowSwitcher.cpp
Normal file
118
WindowServer/WSWindowSwitcher.cpp
Normal file
|
@ -0,0 +1,118 @@
|
|||
#include <WindowServer/WSWindowSwitcher.h>
|
||||
#include <WindowServer/WSWindowManager.h>
|
||||
#include <WindowServer/WSMessage.h>
|
||||
#include <SharedGraphics/Font.h>
|
||||
|
||||
WSWindowSwitcher::WSWindowSwitcher()
|
||||
{
|
||||
}
|
||||
|
||||
WSWindowSwitcher::~WSWindowSwitcher()
|
||||
{
|
||||
}
|
||||
|
||||
void WSWindowSwitcher::set_visible(bool visible)
|
||||
{
|
||||
if (m_visible == visible)
|
||||
return;
|
||||
m_visible = visible;
|
||||
if (!m_visible) {
|
||||
WSWindowManager::the().invalidate(m_rect);
|
||||
return;
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
WSWindow* WSWindowSwitcher::selected_window()
|
||||
{
|
||||
if (m_selected_index < 0 || m_selected_index >= m_windows.size())
|
||||
return nullptr;
|
||||
return m_windows[m_selected_index].ptr();
|
||||
}
|
||||
|
||||
void WSWindowSwitcher::on_key_event(const WSKeyEvent& event)
|
||||
{
|
||||
if (event.type() == WSMessage::KeyUp) {
|
||||
if (event.key() == Key_Logo) {
|
||||
if (auto* window = selected_window()) {
|
||||
WSWindowManager::the().set_active_window(window);
|
||||
WSWindowManager::the().move_to_front(*window);
|
||||
}
|
||||
WSWindowManager::the().set_highlight_window(nullptr);
|
||||
hide();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (event.key() != Key_Tab) {
|
||||
hide();
|
||||
return;
|
||||
}
|
||||
ASSERT(!m_windows.is_empty());
|
||||
m_selected_index = (m_selected_index + 1) % m_windows.size();
|
||||
ASSERT(m_selected_index < m_windows.size());
|
||||
auto* highlight_window = m_windows.at(m_selected_index).ptr();
|
||||
ASSERT(highlight_window);
|
||||
WSWindowManager::the().set_highlight_window(highlight_window);
|
||||
WSWindowManager::the().invalidate(m_rect);
|
||||
}
|
||||
|
||||
void WSWindowSwitcher::draw(Painter& painter)
|
||||
{
|
||||
painter.translate(m_rect.location());
|
||||
painter.fill_rect({ { }, m_rect.size() }, Color::LightGray);
|
||||
painter.draw_rect({ { }, m_rect.size() }, Color::DarkGray);
|
||||
|
||||
for (int index = 0; index < m_windows.size(); ++index) {
|
||||
auto& window = *m_windows.at(index);
|
||||
Rect item_rect {
|
||||
padding(),
|
||||
padding() + index * item_height(),
|
||||
m_rect.width() - padding() * 2,
|
||||
item_height()
|
||||
};
|
||||
Color text_color;
|
||||
Color rect_text_color;
|
||||
if (index == m_selected_index) {
|
||||
painter.fill_rect(item_rect, Color::from_rgb(0x84351a));
|
||||
text_color = Color::White;
|
||||
rect_text_color = Color::LightGray;
|
||||
} else {
|
||||
text_color = Color::Black;
|
||||
rect_text_color = Color::DarkGray;
|
||||
}
|
||||
|
||||
painter.set_font(Font::default_bold_font());
|
||||
painter.draw_text(item_rect, window.title(), TextAlignment::CenterLeft, text_color);
|
||||
painter.set_font(WSWindowManager::the().font());
|
||||
painter.draw_text(item_rect, window.rect().to_string(), TextAlignment::CenterRight, rect_text_color);
|
||||
}
|
||||
painter.translate(-m_rect.x(), -m_rect.y());
|
||||
}
|
||||
|
||||
void WSWindowSwitcher::invalidate()
|
||||
{
|
||||
WSWindow* selected_window = nullptr;
|
||||
if (m_selected_index > 0 && m_windows[m_selected_index])
|
||||
selected_window = m_windows[m_selected_index].ptr();
|
||||
m_windows.clear();
|
||||
m_selected_index = 0;
|
||||
int window_count = 0;
|
||||
int longest_title = 0;
|
||||
WSWindowManager::the().for_each_visible_window_of_type_from_back_to_front(WSWindowType::Normal, [&] (WSWindow& window) {
|
||||
++window_count;
|
||||
longest_title = max(longest_title, window.title().length());
|
||||
if (selected_window == &window)
|
||||
m_selected_index = m_windows.size();
|
||||
m_windows.append(window.make_weak_ptr());
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
if (m_windows.is_empty()) {
|
||||
hide();
|
||||
return;
|
||||
}
|
||||
int space_for_window_rect = WSWindowManager::the().font().glyph_width() * 24;
|
||||
m_rect.set_width(longest_title * WSWindowManager::the().font().glyph_width() + space_for_window_rect + padding() * 2);
|
||||
m_rect.set_height(window_count * item_height() + padding() * 2);
|
||||
m_rect.center_within(WSWindowManager::the().m_screen_rect);
|
||||
WSWindowManager::the().invalidate(m_rect);
|
||||
}
|
38
WindowServer/WSWindowSwitcher.h
Normal file
38
WindowServer/WSWindowSwitcher.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
#include <SharedGraphics/Rect.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <AK/WeakPtr.h>
|
||||
|
||||
class Painter;
|
||||
class WSKeyEvent;
|
||||
class WSWindow;
|
||||
|
||||
class WSWindowSwitcher {
|
||||
public:
|
||||
WSWindowSwitcher();
|
||||
~WSWindowSwitcher();
|
||||
|
||||
bool is_visible() const { return m_visible; }
|
||||
void set_visible(bool);
|
||||
|
||||
void show() { set_visible(true); }
|
||||
void hide() { set_visible(false); }
|
||||
|
||||
void on_key_event(const WSKeyEvent&);
|
||||
void invalidate();
|
||||
|
||||
void draw(Painter&);
|
||||
|
||||
int item_height() { return 20; }
|
||||
int padding() { return 8; }
|
||||
|
||||
WSWindow* selected_window();
|
||||
|
||||
private:
|
||||
|
||||
Rect m_rect;
|
||||
bool m_visible { false };
|
||||
Vector<WeakPtr<WSWindow>> m_windows;
|
||||
int m_selected_index { 0 };
|
||||
};
|
Loading…
Reference in a new issue