mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 17:52:45 +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
					
				
					 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…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Andreas Kling
						Andreas Kling