diff options
author | Andreas Kling <awesomekling@gmail.com> | 2019-03-03 15:17:05 +0100 |
---|---|---|
committer | Andreas Kling <awesomekling@gmail.com> | 2019-03-03 15:17:05 +0100 |
commit | 5c27e74fac54cbaffad781affb54bd8821c66458 (patch) | |
tree | cab18a88d4c152d7934bacdd6ba0f22049849314 | |
parent | 483e0a55262326846a1f3181ab2642449caf8be7 (diff) | |
download | serenity-5c27e74fac54cbaffad781affb54bd8821c66458.zip |
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. :^)
-rw-r--r-- | WindowServer/Makefile | 1 | ||||
-rw-r--r-- | WindowServer/WSWindow.cpp | 5 | ||||
-rw-r--r-- | WindowServer/WSWindow.h | 2 | ||||
-rw-r--r-- | WindowServer/WSWindowManager.cpp | 105 | ||||
-rw-r--r-- | WindowServer/WSWindowManager.h | 58 | ||||
-rw-r--r-- | WindowServer/WSWindowSwitcher.cpp | 118 | ||||
-rw-r--r-- | WindowServer/WSWindowSwitcher.h | 38 |
7 files changed, 277 insertions, 50 deletions
diff --git a/WindowServer/Makefile b/WindowServer/Makefile index 15802c26a2..af30561588 100644 --- a/WindowServer/Makefile +++ b/WindowServer/Makefile @@ -17,6 +17,7 @@ WINDOWSERVER_OBJS = \ WSMenu.o \ WSMenuItem.o \ WSClientConnection.o \ + WSWindowSwitcher.o \ main.o APP = WindowServer diff --git a/WindowServer/WSWindow.cpp b/WindowServer/WSWindow.cpp index dfca71e6fc..6105721fd8 100644 --- a/WindowServer/WSWindow.cpp +++ b/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; +} diff --git a/WindowServer/WSWindow.h b/WindowServer/WSWindow.h index 15745a55a0..9f506fed24 100644 --- a/WindowServer/WSWindow.h +++ b/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); diff --git a/WindowServer/WSWindowManager.cpp b/WindowServer/WSWindowManager.cpp index 169863c3f8..585ef2002b 100644 --- a/WindowServer/WSWindowManager.cpp +++ b/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) { diff --git a/WindowServer/WSWindowManager.h b/WindowServer/WSWindowManager.h index a0ff53402c..f76b3b93d0 100644 --- a/WindowServer/WSWindowManager.h +++ b/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); +} diff --git a/WindowServer/WSWindowSwitcher.cpp b/WindowServer/WSWindowSwitcher.cpp new file mode 100644 index 0000000000..1a8f320811 --- /dev/null +++ b/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); +} diff --git a/WindowServer/WSWindowSwitcher.h b/WindowServer/WSWindowSwitcher.h new file mode 100644 index 0000000000..81c4c2d830 --- /dev/null +++ b/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 }; +}; |