diff --git a/Base/res/icons/resize-corner.png b/Base/res/icons/resize-corner.png new file mode 100644 index 0000000000..408b88b7da Binary files /dev/null and b/Base/res/icons/resize-corner.png differ diff --git a/LibGUI/GEventLoop.cpp b/LibGUI/GEventLoop.cpp index 5731d30387..ae41ad7da1 100644 --- a/LibGUI/GEventLoop.cpp +++ b/LibGUI/GEventLoop.cpp @@ -24,6 +24,7 @@ //#define COALESCING_DEBUG int GEventLoop::s_event_fd = -1; +int GEventLoop::s_my_client_id = -1; pid_t GEventLoop::s_server_pid = -1; void GEventLoop::connect_to_server() @@ -59,8 +60,7 @@ void GEventLoop::connect_to_server() request.type = WSAPI_ClientMessage::Type::Greeting; request.greeting.client_pid = getpid(); auto response = sync_request(request, WSAPI_ServerMessage::Type::Greeting); - s_server_pid = response.greeting.server_pid; - GDesktop::the().did_receive_screen_rect(Badge(), response.greeting.screen_rect); + handle_greeting(response); } GEventLoop::GEventLoop() @@ -235,8 +235,7 @@ void GEventLoop::process_unprocessed_bundles() for (auto& bundle : unprocessed_bundles) { auto& event = bundle.message; if (event.type == WSAPI_ServerMessage::Type::Greeting) { - s_server_pid = event.greeting.server_pid; - GDesktop::the().did_receive_screen_rect(Badge(), event.greeting.screen_rect); + handle_greeting(event); continue; } @@ -402,3 +401,10 @@ WSAPI_ServerMessage GEventLoop::sync_request(const WSAPI_ClientMessage& request, ASSERT(success); return response; } + +void GEventLoop::handle_greeting(WSAPI_ServerMessage& message) +{ + s_server_pid = message.greeting.server_pid; + s_my_client_id = message.greeting.your_client_id; + GDesktop::the().did_receive_screen_rect(Badge(), message.greeting.screen_rect); +} diff --git a/LibGUI/GEventLoop.h b/LibGUI/GEventLoop.h index a88194a4ed..4e235aa21d 100644 --- a/LibGUI/GEventLoop.h +++ b/LibGUI/GEventLoop.h @@ -21,6 +21,7 @@ public: WSAPI_ServerMessage sync_request(const WSAPI_ClientMessage& request, WSAPI_ServerMessage::Type response_type); static pid_t server_pid() { return s_server_pid; } + static int my_client_id() { return s_my_client_id; } virtual void take_pending_events_from(CEventLoop& other) override { @@ -59,6 +60,7 @@ private: void handle_menu_event(const WSAPI_ServerMessage&); void handle_window_entered_or_left_event(const WSAPI_ServerMessage&, GWindow&); void handle_wm_event(const WSAPI_ServerMessage&, GWindow&); + void handle_greeting(WSAPI_ServerMessage&); void connect_to_server(); struct IncomingWSMessageBundle { @@ -68,5 +70,6 @@ private: Vector m_unprocessed_bundles; static pid_t s_server_pid; - static pid_t s_event_fd; + static int s_my_client_id; + static int s_event_fd; }; diff --git a/LibGUI/GResizeCorner.cpp b/LibGUI/GResizeCorner.cpp new file mode 100644 index 0000000000..91407a509f --- /dev/null +++ b/LibGUI/GResizeCorner.cpp @@ -0,0 +1,46 @@ +#include +#include +#include +#include +#include + +GResizeCorner::GResizeCorner(GWidget* parent) + : GWidget(parent) +{ + set_size_policy(SizePolicy::Fixed, SizePolicy::Fixed); + set_preferred_size({ 16, 16 }); + m_bitmap = GraphicsBitmap::load_from_file("/res/icons/resize-corner.png"); + ASSERT(m_bitmap); +} + +GResizeCorner::~GResizeCorner() +{ +} + +void GResizeCorner::paint_event(GPaintEvent& event) +{ + GPainter painter(*this); + painter.add_clip_rect(event.rect()); + painter.fill_rect(rect(), background_color()); + painter.blit({ 0, 0 }, *m_bitmap, m_bitmap->rect()); + GWidget::paint_event(event); +} + +void GResizeCorner::mousedown_event(GMouseEvent& event) +{ + if (event.button() == GMouseButton::Left) + window()->start_wm_resize(); + GWidget::mousedown_event(event); +} + +void GResizeCorner::enter_event(CEvent& event) +{ + window()->set_override_cursor(GStandardCursor::ResizeDiagonalTLBR); + GWidget::enter_event(event); +} + +void GResizeCorner::leave_event(CEvent& event) +{ + window()->set_override_cursor(GStandardCursor::None); + GWidget::leave_event(event); +} diff --git a/LibGUI/GResizeCorner.h b/LibGUI/GResizeCorner.h new file mode 100644 index 0000000000..2d6ae0c670 --- /dev/null +++ b/LibGUI/GResizeCorner.h @@ -0,0 +1,18 @@ +#include + +class GResizeCorner : public GWidget { +public: + explicit GResizeCorner(GWidget* parent); + virtual ~GResizeCorner() override; + + virtual const char* class_name() const override { return "GResizeCorner"; } + +protected: + virtual void paint_event(GPaintEvent&) override; + virtual void mousedown_event(GMouseEvent&) override; + virtual void enter_event(CEvent&) override; + virtual void leave_event(CEvent&) override; + +private: + RetainPtr m_bitmap; +}; diff --git a/LibGUI/GStatusBar.cpp b/LibGUI/GStatusBar.cpp index 65d0b2ade5..74c6816f22 100644 --- a/LibGUI/GStatusBar.cpp +++ b/LibGUI/GStatusBar.cpp @@ -3,6 +3,7 @@ #include #include #include +#include GStatusBar::GStatusBar(GWidget* parent) : GWidget(parent) @@ -17,6 +18,8 @@ GStatusBar::GStatusBar(GWidget* parent) m_label->set_frame_shape(FrameShape::Panel); m_label->set_frame_thickness(1); m_label->set_text_alignment(TextAlignment::CenterLeft); + + m_corner = new GResizeCorner(this); } GStatusBar::~GStatusBar() diff --git a/LibGUI/GStatusBar.h b/LibGUI/GStatusBar.h index d01648ff52..9bac204c30 100644 --- a/LibGUI/GStatusBar.h +++ b/LibGUI/GStatusBar.h @@ -3,6 +3,7 @@ #include class GLabel; +class GResizeCorner; class GStatusBar : public GWidget { public: @@ -18,4 +19,5 @@ private: virtual void paint_event(GPaintEvent&) override; GLabel* m_label { nullptr }; + GResizeCorner* m_corner { nullptr }; }; diff --git a/LibGUI/GWindow.cpp b/LibGUI/GWindow.cpp index 36dbd2fe11..c1384ebe97 100644 --- a/LibGUI/GWindow.cpp +++ b/LibGUI/GWindow.cpp @@ -490,3 +490,12 @@ void GWindow::set_icon_path(const String& path) message.text_length = path.length(); GEventLoop::post_message_to_server(message); } + +void GWindow::start_wm_resize() +{ + WSAPI_ClientMessage message; + message.type = WSAPI_ClientMessage::Type::WM_StartWindowResize; + message.wm.client_id = GEventLoop::my_client_id(); + message.wm.window_id = m_window_id; + GEventLoop::post_message_to_server(message); +} diff --git a/LibGUI/GWindow.h b/LibGUI/GWindow.h index cddebcef02..4adf2f7f9d 100644 --- a/LibGUI/GWindow.h +++ b/LibGUI/GWindow.h @@ -16,6 +16,8 @@ enum class GStandardCursor { IBeam, ResizeHorizontal, ResizeVertical, + ResizeDiagonalTLBR, + ResizeDiagonalBLTR, }; class GWindow : public CObject { @@ -71,6 +73,8 @@ public: void hide(); void close(); + void start_wm_resize(); + GWidget* main_widget() { return m_main_widget; } const GWidget* main_widget() const { return m_main_widget; } void set_main_widget(GWidget*); diff --git a/LibGUI/Makefile b/LibGUI/Makefile index 0255d05520..14b0427c43 100644 --- a/LibGUI/Makefile +++ b/LibGUI/Makefile @@ -53,6 +53,7 @@ LIBGUI_OBJS = \ GSpinBox.o \ GGroupBox.o \ GSlider.o \ + GResizeCorner.o \ GWindow.o OBJS = $(SHAREDGRAPHICS_OBJS) $(LIBGUI_OBJS) diff --git a/Servers/WindowServer/WSAPITypes.h b/Servers/WindowServer/WSAPITypes.h index a22ed4bc91..be8027c98e 100644 --- a/Servers/WindowServer/WSAPITypes.h +++ b/Servers/WindowServer/WSAPITypes.h @@ -55,6 +55,8 @@ enum class WSAPI_StandardCursor : unsigned char { IBeam, ResizeHorizontal, ResizeVertical, + ResizeDiagonalTLBR, + ResizeDiagonalBLTR, }; enum WSAPI_WMEventMask : unsigned { @@ -126,6 +128,7 @@ struct WSAPI_ServerMessage { union { struct { int server_pid; + int your_client_id; WSAPI_Rect screen_rect; } greeting; struct { @@ -211,6 +214,7 @@ struct WSAPI_ClientMessage { SetWindowOverrideCursor, WM_SetActiveWindow, WM_SetWindowMinimized, + WM_StartWindowResize, PopupMenu, DismissMenu, SetWindowIcon, diff --git a/Servers/WindowServer/WSClientConnection.cpp b/Servers/WindowServer/WSClientConnection.cpp index 76a953b435..b1dec3740d 100644 --- a/Servers/WindowServer/WSClientConnection.cpp +++ b/Servers/WindowServer/WSClientConnection.cpp @@ -48,6 +48,7 @@ WSClientConnection::WSClientConnection(int fd) WSAPI_ServerMessage message; message.type = WSAPI_ServerMessage::Type::Greeting; message.greeting.server_pid = getpid(); + message.greeting.your_client_id = m_client_id; message.greeting.screen_rect = WSScreen::the().rect(); post_message(message); } @@ -645,6 +646,24 @@ void WSClientConnection::handle_request(const WSWMAPISetActiveWindowRequest& req WSWindowManager::the().move_to_front_and_make_active(window); } +void WSClientConnection::handle_request(const WSWMAPIStartWindowResizeRequest& request) +{ + auto* client = WSClientConnection::from_client_id(request.target_client_id()); + if (!client) { + post_error("WSWMAPIStartWindowResizeRequest: Bad client ID"); + return; + } + auto it = client->m_windows.find(request.target_window_id()); + if (it == client->m_windows.end()) { + post_error("WSWMAPIStartWindowResizeRequest: Bad window ID"); + return; + } + auto& window = *(*it).value; + // FIXME: We are cheating a bit here by using the current cursor location and hard-coding the left button. + // Maybe the client should be allowed to specify what initiated this request? + WSWindowManager::the().start_window_resize(window, WSScreen::the().cursor_location(), MouseButton::Left); +} + void WSClientConnection::handle_request(const WSWMAPISetWindowMinimizedRequest& request) { auto* client = WSClientConnection::from_client_id(request.target_client_id()); @@ -722,6 +741,8 @@ void WSClientConnection::on_request(const WSAPIClientRequest& request) return handle_request(static_cast(request)); case WSEvent::WMAPISetWindowMinimizedRequest: return handle_request(static_cast(request)); + case WSEvent::WMAPIStartWindowResizeRequest: + return handle_request(static_cast(request)); case WSEvent::APIPopupMenuRequest: return handle_request(static_cast(request)); case WSEvent::APIDismissMenuRequest: diff --git a/Servers/WindowServer/WSClientConnection.h b/Servers/WindowServer/WSClientConnection.h index 8db0db1e30..3cd66a537e 100644 --- a/Servers/WindowServer/WSClientConnection.h +++ b/Servers/WindowServer/WSClientConnection.h @@ -74,6 +74,7 @@ private: void handle_request(const WSAPISetWindowOverrideCursorRequest&); void handle_request(const WSWMAPISetActiveWindowRequest&); void handle_request(const WSWMAPISetWindowMinimizedRequest&); + void handle_request(const WSWMAPIStartWindowResizeRequest&); void handle_request(const WSAPIPopupMenuRequest&); void handle_request(const WSAPIDismissMenuRequest&); diff --git a/Servers/WindowServer/WSCursor.cpp b/Servers/WindowServer/WSCursor.cpp index 7862447550..1923908513 100644 --- a/Servers/WindowServer/WSCursor.cpp +++ b/Servers/WindowServer/WSCursor.cpp @@ -34,6 +34,10 @@ RetainPtr WSCursor::create(WSStandardCursor standard_cursor) return WSWindowManager::the().resize_horizontally_cursor(); case WSStandardCursor::ResizeVertical: return WSWindowManager::the().resize_vertically_cursor(); + case WSStandardCursor::ResizeDiagonalTLBR: + return WSWindowManager::the().resize_diagonally_tlbr_cursor(); + case WSStandardCursor::ResizeDiagonalBLTR: + return WSWindowManager::the().resize_diagonally_bltr_cursor(); } ASSERT_NOT_REACHED(); } diff --git a/Servers/WindowServer/WSCursor.h b/Servers/WindowServer/WSCursor.h index 82d71bcbc8..889fdcdef1 100644 --- a/Servers/WindowServer/WSCursor.h +++ b/Servers/WindowServer/WSCursor.h @@ -8,6 +8,8 @@ enum class WSStandardCursor { IBeam, ResizeHorizontal, ResizeVertical, + ResizeDiagonalTLBR, + ResizeDiagonalBLTR, }; class WSCursor : public Retainable { diff --git a/Servers/WindowServer/WSEvent.h b/Servers/WindowServer/WSEvent.h index d8fe0cd9ca..a22dd4cb17 100644 --- a/Servers/WindowServer/WSEvent.h +++ b/Servers/WindowServer/WSEvent.h @@ -62,6 +62,7 @@ public: APISetWindowOverrideCursorRequest, WMAPISetActiveWindowRequest, WMAPISetWindowMinimizedRequest, + WMAPIStartWindowResizeRequest, APIPopupMenuRequest, APIDismissMenuRequest, __End_API_Client_Requests, @@ -104,6 +105,23 @@ private: int m_client_id { 0 }; }; +class WSWMAPIStartWindowResizeRequest : public WSAPIClientRequest { +public: + WSWMAPIStartWindowResizeRequest(int client_id, int target_client_id, int target_window_id) + : WSAPIClientRequest(WSEvent::WMAPIStartWindowResizeRequest, client_id) + , m_target_client_id(target_client_id) + , m_target_window_id(target_window_id) + { + } + + int target_client_id() const { return m_target_client_id; } + int target_window_id() const { return m_target_window_id; } + +private: + int m_target_client_id; + int m_target_window_id; +}; + class WSWMAPISetActiveWindowRequest : public WSAPIClientRequest { public: WSWMAPISetActiveWindowRequest(int client_id, int target_client_id, int target_window_id) diff --git a/Servers/WindowServer/WSEventLoop.cpp b/Servers/WindowServer/WSEventLoop.cpp index 745d88b9e2..c69722a5f6 100644 --- a/Servers/WindowServer/WSEventLoop.cpp +++ b/Servers/WindowServer/WSEventLoop.cpp @@ -250,6 +250,9 @@ bool WSEventLoop::on_receive_from_client(int client_id, const WSAPI_ClientMessag case WSAPI_ClientMessage::Type::WM_SetWindowMinimized: post_event(client, make(client_id, message.wm.client_id, message.wm.window_id, message.wm.minimized)); break; + case WSAPI_ClientMessage::Type::WM_StartWindowResize: + post_event(client, make(client_id, message.wm.client_id, message.wm.window_id)); + break; default: break; } diff --git a/Servers/WindowServer/WSWindowManager.cpp b/Servers/WindowServer/WSWindowManager.cpp index 630290efb4..e3728c29cf 100644 --- a/Servers/WindowServer/WSWindowManager.cpp +++ b/Servers/WindowServer/WSWindowManager.cpp @@ -490,7 +490,7 @@ void WSWindowManager::start_window_drag(WSWindow& window, const WSMouseEvent& ev invalidate(window); } -void WSWindowManager::start_window_resize(WSWindow& window, const WSMouseEvent& event) +void WSWindowManager::start_window_resize(WSWindow& window, const Point& position, MouseButton button) { move_to_front_and_make_active(window); constexpr ResizeDirection direction_for_hot_area[3][3] = { @@ -499,9 +499,9 @@ void WSWindowManager::start_window_resize(WSWindow& window, const WSMouseEvent& { ResizeDirection::DownLeft, ResizeDirection::Down, ResizeDirection::DownRight }, }; Rect outer_rect = window.frame().rect(); - ASSERT(outer_rect.contains(event.position())); - int window_relative_x = event.x() - outer_rect.x(); - int window_relative_y = event.y() - outer_rect.y(); + ASSERT(outer_rect.contains(position)); + int window_relative_x = position.x() - outer_rect.x(); + int window_relative_y = position.y() - outer_rect.y(); int hot_area_row = min(2, window_relative_y / (outer_rect.height() / 3)); int hot_area_column = min(2, window_relative_x / (outer_rect.width() / 3)); m_resize_direction = direction_for_hot_area[hot_area_row][hot_area_column]; @@ -513,15 +513,20 @@ void WSWindowManager::start_window_resize(WSWindow& window, const WSMouseEvent& #ifdef RESIZE_DEBUG printf("[WM] Begin resizing WSWindow{%p}\n", &window); #endif - m_resizing_mouse_button = event.button(); + m_resizing_mouse_button = button; m_resize_window = window.make_weak_ptr();; - m_resize_origin = event.position(); + m_resize_origin = position; m_resize_window_original_rect = window.rect(); invalidate(window); } -bool WSWindowManager::process_ongoing_window_drag(const WSMouseEvent& event, WSWindow*&) +void WSWindowManager::start_window_resize(WSWindow& window, const WSMouseEvent& event) +{ + start_window_resize(window, event.position(), event.button()); +} + +bool WSWindowManager::process_ongoing_window_drag(const WSMouseEvent& event, WSWindow*& hovered_window) { if (!m_drag_window) return false; @@ -530,6 +535,8 @@ bool WSWindowManager::process_ongoing_window_drag(const WSMouseEvent& event, WSW printf("[WM] Finish dragging WSWindow{%p}\n", m_drag_window.ptr()); #endif invalidate(*m_drag_window); + if (m_drag_window->rect().contains(event.position())) + hovered_window = m_drag_window; m_drag_window = nullptr; return true; } @@ -539,12 +546,14 @@ bool WSWindowManager::process_ongoing_window_drag(const WSMouseEvent& event, WSW #endif Point pos = m_drag_window_origin.translated(event.position() - m_drag_origin); m_drag_window->set_position_without_repaint(pos); + if (m_drag_window->rect().contains(event.position())) + hovered_window = m_drag_window; return true; } return false; } -bool WSWindowManager::process_ongoing_window_resize(const WSMouseEvent& event, WSWindow*&) +bool WSWindowManager::process_ongoing_window_resize(const WSMouseEvent& event, WSWindow*& hovered_window) { if (!m_resize_window) return false; @@ -555,6 +564,8 @@ bool WSWindowManager::process_ongoing_window_resize(const WSMouseEvent& event, W #endif WSEventLoop::the().post_event(*m_resize_window, make(m_resize_window->rect(), m_resize_window->rect())); invalidate(*m_resize_window); + if (m_resize_window->rect().contains(event.position())) + hovered_window = m_resize_window; m_resize_window = nullptr; m_resizing_mouse_button = MouseButton::None; return true; @@ -627,6 +638,9 @@ bool WSWindowManager::process_ongoing_window_resize(const WSMouseEvent& event, W new_rect.set_height(m_resize_window->base_size().height() + vertical_incs * m_resize_window->size_increment().height()); } + if (new_rect.contains(event.position())) + hovered_window = m_resize_window; + if (m_resize_window->rect() == new_rect) return true; #ifdef RESIZE_DEBUG @@ -644,14 +658,14 @@ void WSWindowManager::set_cursor_tracking_button(WSButton* button) m_cursor_tracking_button = button ? button->make_weak_ptr() : nullptr; } -void WSWindowManager::process_mouse_event(const WSMouseEvent& event, WSWindow*& event_window) +void WSWindowManager::process_mouse_event(const WSMouseEvent& event, WSWindow*& hovered_window) { - event_window = nullptr; + hovered_window = nullptr; - if (process_ongoing_window_drag(event, event_window)) + if (process_ongoing_window_drag(event, hovered_window)) return; - if (process_ongoing_window_resize(event, event_window)) + if (process_ongoing_window_resize(event, hovered_window)) return; if (m_cursor_tracking_button) @@ -687,7 +701,7 @@ void WSWindowManager::process_mouse_event(const WSMouseEvent& event, WSWindow*& if (event.type() == WSEvent::MouseDown || event.type() == WSEvent::MouseUp) close_current_menu(); } else { - event_window = &window; + hovered_window = &window; auto translated_event = event.translated(-window.position()); window.event(translated_event); } @@ -708,10 +722,12 @@ void WSWindowManager::process_mouse_event(const WSMouseEvent& event, WSWindow*& // In those cases, the event is swallowed by the window manager. if (window.type() == WSWindowType::Normal) { if (m_keyboard_modifiers == Mod_Logo && event.type() == WSEvent::MouseDown && event.button() == MouseButton::Left) { + hovered_window = &window; start_window_drag(window, event); return IterationDecision::Abort; } if (window.is_resizable() && m_keyboard_modifiers == Mod_Logo && event.type() == WSEvent::MouseDown && event.button() == MouseButton::Right && !window.is_blocked_by_modal_window()) { + hovered_window = &window; start_window_resize(window, event); return IterationDecision::Abort; } @@ -720,7 +736,7 @@ void WSWindowManager::process_mouse_event(const WSMouseEvent& event, WSWindow*& if (window.rect().contains(event.position())) { if (window.type() == WSWindowType::Normal && event.type() == WSEvent::MouseDown) move_to_front_and_make_active(window); - event_window = &window; + hovered_window = &window; if (!window.global_cursor_tracking() && !windows_who_received_mouse_event_due_to_cursor_tracking.contains(&window)) { auto translated_event = event.translated(-window.position()); window.event(translated_event); @@ -987,9 +1003,9 @@ void WSWindowManager::draw_cursor() void WSWindowManager::event(CEvent& event) { if (static_cast(event).is_mouse_event()) { - WSWindow* event_window = nullptr; - process_mouse_event(static_cast(event), event_window); - set_hovered_window(event_window); + WSWindow* hovered_window = nullptr; + process_mouse_event(static_cast(event), hovered_window); + set_hovered_window(hovered_window); return; } diff --git a/Servers/WindowServer/WSWindowManager.h b/Servers/WindowServer/WSWindowManager.h index b3bdd3178f..7d607f9d11 100644 --- a/Servers/WindowServer/WSWindowManager.h +++ b/Servers/WindowServer/WSWindowManager.h @@ -116,14 +116,16 @@ public: void tell_wm_listeners_window_icon_changed(WSWindow&); void tell_wm_listeners_window_rect_changed(WSWindow&); + void start_window_resize(WSWindow&, const Point&, MouseButton); + void start_window_resize(WSWindow&, const WSMouseEvent&); + private: - void process_mouse_event(const WSMouseEvent&, WSWindow*& event_window); - bool process_ongoing_window_resize(const WSMouseEvent&, WSWindow*& event_window); - bool process_ongoing_window_drag(const WSMouseEvent&, WSWindow*& event_window); + void process_mouse_event(const WSMouseEvent&, WSWindow*& hovered_window); + bool process_ongoing_window_resize(const WSMouseEvent&, WSWindow*& hovered_window); + bool process_ongoing_window_drag(const WSMouseEvent&, WSWindow*& hovered_window); void handle_menu_mouse_event(WSMenu&, const WSMouseEvent&); void handle_menubar_mouse_event(const WSMouseEvent&); void handle_close_button_mouse_event(WSWindow&, const WSMouseEvent&); - void start_window_resize(WSWindow&, const WSMouseEvent&); void start_window_drag(WSWindow&, const WSMouseEvent&); void handle_client_request(const WSAPIClientRequest&); void set_hovered_window(WSWindow*);