Просмотр исходного кода

LibGUI+WindowServer: Implement drag-to-select behavior in GTextEditor.

To make this feel right, I needed to start passing keyboard modifiers along
with mouse events. That allows shift-clicking to extend the selection. :^)
Andreas Kling 6 лет назад
Родитель
Сommit
f40d11f06d

+ 4 - 1
LibGUI/GEvent.h

@@ -137,11 +137,12 @@ private:
 
 class GMouseEvent final : public GEvent {
 public:
-    GMouseEvent(Type type, const Point& position, unsigned buttons, GMouseButton button = GMouseButton::None)
+    GMouseEvent(Type type, const Point& position, unsigned buttons, GMouseButton button, unsigned modifiers)
         : GEvent(type)
         , m_position(position)
         , m_buttons(buttons)
         , m_button(button)
+        , m_modifiers(modifiers)
     {
     }
 
@@ -150,11 +151,13 @@ public:
     int y() const { return m_position.y(); }
     GMouseButton button() const { return m_button; }
     unsigned buttons() const { return m_buttons; }
+    unsigned modifiers() const { return m_modifiers; }
 
 private:
     Point m_position;
     unsigned m_buttons { 0 };
     GMouseButton m_button { GMouseButton::None };
+    unsigned m_modifiers { 0 };
 };
 
 class GTimerEvent final : public GEvent {

+ 1 - 1
LibGUI/GEventLoop.cpp

@@ -187,7 +187,7 @@ void GEventLoop::handle_mouse_event(const WSAPI_ServerMessage& event, GWindow& w
     case WSAPI_MouseButton::Middle: button = GMouseButton::Middle; break;
     default: ASSERT_NOT_REACHED(); break;
     }
-    post_event(window, make<GMouseEvent>(type, event.mouse.position, event.mouse.buttons, button));
+    post_event(window, make<GMouseEvent>(type, event.mouse.position, event.mouse.buttons, button, event.mouse.modifiers));
 }
 
 void GEventLoop::handle_menu_event(const WSAPI_ServerMessage& event)

+ 48 - 12
LibGUI/GTextEditor.cpp

@@ -107,11 +107,47 @@ GTextPosition GTextEditor::text_position_at(const Point& a_position) const
 
 void GTextEditor::mousedown_event(GMouseEvent& event)
 {
-    set_cursor(text_position_at(event.position()));
-    // FIXME: Allow mouse selection!
-    if (m_selection_start.is_valid()) {
-        m_selection_start = { };
+    if (event.button() == GMouseButton::Left) {
+        if (event.modifiers() & Mod_Shift) {
+            if (!has_selection())
+                m_selection_start = m_cursor;
+        } else {
+            m_selection_start = { };
+        }
+
+        m_in_drag_select = true;
+        set_global_cursor_tracking(true);
+
+        set_cursor(text_position_at(event.position()));
+
+        if (!(event.modifiers() & Mod_Shift)) {
+            if (!has_selection())
+                m_selection_start = m_cursor;
+        }
+
+        // FIXME: Only update the relevant rects.
         update();
+        return;
+    }
+}
+
+void GTextEditor::mouseup_event(GMouseEvent& event)
+{
+    if (event.button() == GMouseButton::Left) {
+        if (m_in_drag_select) {
+            m_in_drag_select = false;
+            set_global_cursor_tracking(false);
+        }
+        return;
+    }
+}
+
+void GTextEditor::mousemove_event(GMouseEvent& event)
+{
+    if (m_in_drag_select) {
+        set_cursor(text_position_at(event.position()));
+        update();
+        return;
     }
 }
 
@@ -475,14 +511,14 @@ void GTextEditor::set_cursor(const GTextPosition& position)
     ASSERT(!m_lines.is_empty());
     ASSERT(position.line() < m_lines.size());
     ASSERT(position.column() <= m_lines[position.line()]->length());
-    if (m_cursor == position)
-        return;
-    auto old_cursor_line_rect = line_widget_rect(m_cursor.line());
-    m_cursor = position;
-    m_cursor_state = true;
-    scroll_cursor_into_view();
-    update(old_cursor_line_rect);
-    update_cursor();
+    if (m_cursor != position) {
+        auto old_cursor_line_rect = line_widget_rect(m_cursor.line());
+        m_cursor = position;
+        m_cursor_state = true;
+        scroll_cursor_into_view();
+        update(old_cursor_line_rect);
+        update_cursor();
+    }
     if (on_cursor_change)
         on_cursor_change(*this);
 }

+ 4 - 0
LibGUI/GTextEditor.h

@@ -24,6 +24,7 @@ public:
     void set_column(int column) { m_column = column; }
 
     bool operator==(const GTextPosition& other) const { return m_line == other.m_line && m_column == other.m_column; }
+    bool operator!=(const GTextPosition& other) const { return m_line != other.m_line || m_column != other.m_column; }
     bool operator<(const GTextPosition& other) const { return m_line < other.m_line || (m_line == other.m_line && m_column < other.m_column); }
 
 private:
@@ -64,6 +65,8 @@ private:
     virtual void paint_event(GPaintEvent&) override;
     virtual void resize_event(GResizeEvent&) override;
     virtual void mousedown_event(GMouseEvent&) override;
+    virtual void mouseup_event(GMouseEvent&) override;
+    virtual void mousemove_event(GMouseEvent&) override;
     virtual void keydown_event(GKeyEvent&) override;
     virtual void focusin_event(GEvent&) override;
     virtual void focusout_event(GEvent&) override;
@@ -116,6 +119,7 @@ private:
     Vector<OwnPtr<Line>> m_lines;
     GTextPosition m_cursor;
     bool m_cursor_state { true };
+    bool m_in_drag_select { false };
     int m_line_spacing { 2 };
     GTextPosition m_selection_start;
 };

+ 2 - 2
LibGUI/GWindow.cpp

@@ -143,7 +143,7 @@ void GWindow::event(GEvent& event)
             auto& mouse_event = static_cast<GMouseEvent&>(event);
             auto window_relative_rect = m_global_cursor_tracking_widget->window_relative_rect();
             Point local_point { mouse_event.x() - window_relative_rect.x(), mouse_event.y() - window_relative_rect.y() };
-            auto local_event = make<GMouseEvent>(event.type(), local_point, mouse_event.buttons(), mouse_event.button());
+            auto local_event = make<GMouseEvent>(event.type(), local_point, mouse_event.buttons(), mouse_event.button(), mouse_event.modifiers());
             m_global_cursor_tracking_widget->event(*local_event);
         }
         if (!m_main_widget)
@@ -151,7 +151,7 @@ void GWindow::event(GEvent& event)
         auto& mouse_event = static_cast<GMouseEvent&>(event);
         if (m_main_widget) {
             auto result = m_main_widget->hit_test(mouse_event.x(), mouse_event.y());
-            auto local_event = make<GMouseEvent>(event.type(), Point { result.localX, result.localY }, mouse_event.buttons(), mouse_event.button());
+            auto local_event = make<GMouseEvent>(event.type(), Point { result.localX, result.localY }, mouse_event.buttons(), mouse_event.button(), mouse_event.modifiers());
             ASSERT(result.widget);
             set_hovered_widget(result.widget);
             if (result.widget != m_global_cursor_tracking_widget.ptr())

+ 1 - 0
WindowServer/WSAPITypes.h

@@ -109,6 +109,7 @@ struct WSAPI_ServerMessage {
             WSAPI_Point position;
             WSAPI_MouseButton button;
             unsigned buttons;
+            byte modifiers;
         } mouse;
         struct {
             char character;

+ 4 - 1
WindowServer/WSMessage.h

@@ -513,11 +513,12 @@ private:
 
 class WSMouseEvent final : public WSMessage {
 public:
-    WSMouseEvent(Type type, const Point& position, unsigned buttons, MouseButton button = MouseButton::None)
+    WSMouseEvent(Type type, const Point& position, unsigned buttons, MouseButton button, unsigned modifiers)
         : WSMessage(type)
         , m_position(position)
         , m_buttons(buttons)
         , m_button(button)
+        , m_modifiers(modifiers)
     {
     }
 
@@ -526,11 +527,13 @@ public:
     int y() const { return m_position.y(); }
     MouseButton button() const { return m_button; }
     unsigned buttons() const { return m_buttons; }
+    unsigned modifiers() const { return m_modifiers; }
 
 private:
     Point m_position;
     unsigned m_buttons { 0 };
     MouseButton m_button { MouseButton::None };
+    unsigned m_modifiers { 0 };
 };
 
 class WSResizeEvent final : public WSMessage {

+ 3 - 2
WindowServer/WSScreen.cpp

@@ -69,14 +69,14 @@ void WSScreen::on_receive_mouse_data(int dx, int dy, unsigned buttons)
     auto post_mousedown_or_mouseup_if_needed = [&] (MouseButton button) {
         if (!(changed_buttons & (unsigned)button))
             return;
-        auto message = make<WSMouseEvent>(buttons & (unsigned)button ? WSMessage::MouseDown : WSMessage::MouseUp, m_cursor_location, buttons, button);
+        auto message = make<WSMouseEvent>(buttons & (unsigned)button ? WSMessage::MouseDown : WSMessage::MouseUp, m_cursor_location, buttons, button, m_modifiers);
         WSMessageLoop::the().post_message(WSWindowManager::the(), move(message));
     };
     post_mousedown_or_mouseup_if_needed(MouseButton::Left);
     post_mousedown_or_mouseup_if_needed(MouseButton::Right);
     post_mousedown_or_mouseup_if_needed(MouseButton::Middle);
     if (m_cursor_location != prev_location) {
-        auto message = make<WSMouseEvent>(WSMessage::MouseMove, m_cursor_location, buttons);
+        auto message = make<WSMouseEvent>(WSMessage::MouseMove, m_cursor_location, buttons, MouseButton::None, m_modifiers);
         WSMessageLoop::the().post_message(WSWindowManager::the(), move(message));
     }
     // NOTE: Invalidate the cursor if it moved, or if the left button changed state (for the cursor color inversion.)
@@ -86,6 +86,7 @@ void WSScreen::on_receive_mouse_data(int dx, int dy, unsigned buttons)
 
 void WSScreen::on_receive_keyboard_data(KeyEvent kernel_event)
 {
+    m_modifiers = kernel_event.modifiers();
     auto message = make<WSKeyEvent>(kernel_event.is_press() ? WSMessage::KeyDown : WSMessage::KeyUp, kernel_event.key, kernel_event.character, kernel_event.modifiers());
     WSMessageLoop::the().post_message(WSWindowManager::the(), move(message));
 }

+ 1 - 0
WindowServer/WSScreen.h

@@ -38,6 +38,7 @@ private:
 
     Point m_cursor_location;
     unsigned m_mouse_button_state { 0 };
+    unsigned m_modifiers { 0 };
 };
 
 inline RGBA32* WSScreen::scanline(int y)

+ 3 - 0
WindowServer/WSWindow.cpp

@@ -82,18 +82,21 @@ void WSWindow::on_message(WSMessage& message)
         server_message.mouse.position = static_cast<WSMouseEvent&>(message).position();
         server_message.mouse.button = WSAPI_MouseButton::NoButton;
         server_message.mouse.buttons = static_cast<WSMouseEvent&>(message).buttons();
+        server_message.mouse.modifiers = static_cast<WSMouseEvent&>(message).modifiers();
         break;
     case WSMessage::MouseDown:
         server_message.type = WSAPI_ServerMessage::Type::MouseDown;
         server_message.mouse.position = static_cast<WSMouseEvent&>(message).position();
         server_message.mouse.button = to_api(static_cast<WSMouseEvent&>(message).button());
         server_message.mouse.buttons = static_cast<WSMouseEvent&>(message).buttons();
+        server_message.mouse.modifiers = static_cast<WSMouseEvent&>(message).modifiers();
         break;
     case WSMessage::MouseUp:
         server_message.type = WSAPI_ServerMessage::Type::MouseUp;
         server_message.mouse.position = static_cast<WSMouseEvent&>(message).position();
         server_message.mouse.button = to_api(static_cast<WSMouseEvent&>(message).button());
         server_message.mouse.buttons = static_cast<WSMouseEvent&>(message).buttons();
+        server_message.mouse.modifiers = static_cast<WSMouseEvent&>(message).modifiers();
         break;
     case WSMessage::WindowEntered:
         server_message.type = WSAPI_ServerMessage::Type::WindowEntered;

+ 2 - 2
WindowServer/WSWindowManager.cpp

@@ -762,7 +762,7 @@ void WSWindowManager::process_mouse_event(WSMouseEvent& event, WSWindow*& event_
             continue;
         ASSERT(window->is_visible()); // Maybe this should be supported? Idk. Let's catch it and think about it later.
         Point position { event.x() - window->rect().x(), event.y() - window->rect().y() };
-        auto local_event = make<WSMouseEvent>(event.type(), position, event.buttons(), event.button());
+        auto local_event = make<WSMouseEvent>(event.type(), position, event.buttons(), event.button(), event.modifiers());
         window->on_message(*local_event);
     }
 
@@ -815,7 +815,7 @@ void WSWindowManager::process_mouse_event(WSMouseEvent& event, WSWindow*& event_
             if (!window.global_cursor_tracking()) {
                 // FIXME: Should we just alter the coordinates of the existing MouseEvent and pass it through?
                 Point position { event.x() - window.rect().x(), event.y() - window.rect().y() };
-                auto local_event = make<WSMouseEvent>(event.type(), position, event.buttons(), event.button());
+                auto local_event = make<WSMouseEvent>(event.type(), position, event.buttons(), event.button(), event.modifiers());
                 window.on_message(*local_event);
             }
             return IterationDecision::Abort;