ソースを参照

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. :^)
Andreas Kling 6 年 前
コミット
5c27e74fac

+ 1 - 0
WindowServer/Makefile

@@ -17,6 +17,7 @@ WINDOWSERVER_OBJS = \
     WSMenu.o \
     WSMenuItem.o \
     WSClientConnection.o \
+    WSWindowSwitcher.o \
     main.o
 
 APP = WindowServer

+ 5 - 0
WindowServer/WSWindow.cpp

@@ -148,3 +148,8 @@ void WSWindow::invalidate()
 {
     WSWindowManager::the().invalidate(*this);
 }
+
+bool WSWindow::is_active() const
+{
+    return WSWindowManager::the().active_window() == this;
+}

+ 2 - 0
WindowServer/WSWindow.h

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

+ 55 - 50
WindowServer/WSWindowManager.cpp

@@ -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) {

+ 58 - 0
WindowServer/WSWindowManager.h

@@ -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 - 0
WindowServer/WSWindowSwitcher.cpp

@@ -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 - 0
WindowServer/WSWindowSwitcher.h

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