mirror of
https://github.com/RGBCube/serenity
synced 2025-05-28 14:45:07 +00:00
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. :^)
This commit is contained in:
parent
6d172725c0
commit
f40d11f06d
11 changed files with 73 additions and 21 deletions
|
@ -137,11 +137,12 @@ private:
|
||||||
|
|
||||||
class GMouseEvent final : public GEvent {
|
class GMouseEvent final : public GEvent {
|
||||||
public:
|
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)
|
: GEvent(type)
|
||||||
, m_position(position)
|
, m_position(position)
|
||||||
, m_buttons(buttons)
|
, m_buttons(buttons)
|
||||||
, m_button(button)
|
, m_button(button)
|
||||||
|
, m_modifiers(modifiers)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,11 +151,13 @@ public:
|
||||||
int y() const { return m_position.y(); }
|
int y() const { return m_position.y(); }
|
||||||
GMouseButton button() const { return m_button; }
|
GMouseButton button() const { return m_button; }
|
||||||
unsigned buttons() const { return m_buttons; }
|
unsigned buttons() const { return m_buttons; }
|
||||||
|
unsigned modifiers() const { return m_modifiers; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Point m_position;
|
Point m_position;
|
||||||
unsigned m_buttons { 0 };
|
unsigned m_buttons { 0 };
|
||||||
GMouseButton m_button { GMouseButton::None };
|
GMouseButton m_button { GMouseButton::None };
|
||||||
|
unsigned m_modifiers { 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
class GTimerEvent final : public GEvent {
|
class GTimerEvent final : public GEvent {
|
||||||
|
|
|
@ -187,7 +187,7 @@ void GEventLoop::handle_mouse_event(const WSAPI_ServerMessage& event, GWindow& w
|
||||||
case WSAPI_MouseButton::Middle: button = GMouseButton::Middle; break;
|
case WSAPI_MouseButton::Middle: button = GMouseButton::Middle; break;
|
||||||
default: ASSERT_NOT_REACHED(); 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)
|
void GEventLoop::handle_menu_event(const WSAPI_ServerMessage& event)
|
||||||
|
|
|
@ -107,11 +107,47 @@ GTextPosition GTextEditor::text_position_at(const Point& a_position) const
|
||||||
|
|
||||||
void GTextEditor::mousedown_event(GMouseEvent& event)
|
void GTextEditor::mousedown_event(GMouseEvent& event)
|
||||||
{
|
{
|
||||||
set_cursor(text_position_at(event.position()));
|
if (event.button() == GMouseButton::Left) {
|
||||||
// FIXME: Allow mouse selection!
|
if (event.modifiers() & Mod_Shift) {
|
||||||
if (m_selection_start.is_valid()) {
|
if (!has_selection())
|
||||||
m_selection_start = { };
|
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();
|
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(!m_lines.is_empty());
|
||||||
ASSERT(position.line() < m_lines.size());
|
ASSERT(position.line() < m_lines.size());
|
||||||
ASSERT(position.column() <= m_lines[position.line()]->length());
|
ASSERT(position.column() <= m_lines[position.line()]->length());
|
||||||
if (m_cursor == position)
|
if (m_cursor != position) {
|
||||||
return;
|
auto old_cursor_line_rect = line_widget_rect(m_cursor.line());
|
||||||
auto old_cursor_line_rect = line_widget_rect(m_cursor.line());
|
m_cursor = position;
|
||||||
m_cursor = position;
|
m_cursor_state = true;
|
||||||
m_cursor_state = true;
|
scroll_cursor_into_view();
|
||||||
scroll_cursor_into_view();
|
update(old_cursor_line_rect);
|
||||||
update(old_cursor_line_rect);
|
update_cursor();
|
||||||
update_cursor();
|
}
|
||||||
if (on_cursor_change)
|
if (on_cursor_change)
|
||||||
on_cursor_change(*this);
|
on_cursor_change(*this);
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ public:
|
||||||
void set_column(int column) { m_column = column; }
|
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_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); }
|
bool operator<(const GTextPosition& other) const { return m_line < other.m_line || (m_line == other.m_line && m_column < other.m_column); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -64,6 +65,8 @@ private:
|
||||||
virtual void paint_event(GPaintEvent&) override;
|
virtual void paint_event(GPaintEvent&) override;
|
||||||
virtual void resize_event(GResizeEvent&) override;
|
virtual void resize_event(GResizeEvent&) override;
|
||||||
virtual void mousedown_event(GMouseEvent&) 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 keydown_event(GKeyEvent&) override;
|
||||||
virtual void focusin_event(GEvent&) override;
|
virtual void focusin_event(GEvent&) override;
|
||||||
virtual void focusout_event(GEvent&) override;
|
virtual void focusout_event(GEvent&) override;
|
||||||
|
@ -116,6 +119,7 @@ private:
|
||||||
Vector<OwnPtr<Line>> m_lines;
|
Vector<OwnPtr<Line>> m_lines;
|
||||||
GTextPosition m_cursor;
|
GTextPosition m_cursor;
|
||||||
bool m_cursor_state { true };
|
bool m_cursor_state { true };
|
||||||
|
bool m_in_drag_select { false };
|
||||||
int m_line_spacing { 2 };
|
int m_line_spacing { 2 };
|
||||||
GTextPosition m_selection_start;
|
GTextPosition m_selection_start;
|
||||||
};
|
};
|
||||||
|
|
|
@ -143,7 +143,7 @@ void GWindow::event(GEvent& event)
|
||||||
auto& mouse_event = static_cast<GMouseEvent&>(event);
|
auto& mouse_event = static_cast<GMouseEvent&>(event);
|
||||||
auto window_relative_rect = m_global_cursor_tracking_widget->window_relative_rect();
|
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() };
|
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);
|
m_global_cursor_tracking_widget->event(*local_event);
|
||||||
}
|
}
|
||||||
if (!m_main_widget)
|
if (!m_main_widget)
|
||||||
|
@ -151,7 +151,7 @@ void GWindow::event(GEvent& event)
|
||||||
auto& mouse_event = static_cast<GMouseEvent&>(event);
|
auto& mouse_event = static_cast<GMouseEvent&>(event);
|
||||||
if (m_main_widget) {
|
if (m_main_widget) {
|
||||||
auto result = m_main_widget->hit_test(mouse_event.x(), mouse_event.y());
|
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);
|
ASSERT(result.widget);
|
||||||
set_hovered_widget(result.widget);
|
set_hovered_widget(result.widget);
|
||||||
if (result.widget != m_global_cursor_tracking_widget.ptr())
|
if (result.widget != m_global_cursor_tracking_widget.ptr())
|
||||||
|
|
|
@ -109,6 +109,7 @@ struct WSAPI_ServerMessage {
|
||||||
WSAPI_Point position;
|
WSAPI_Point position;
|
||||||
WSAPI_MouseButton button;
|
WSAPI_MouseButton button;
|
||||||
unsigned buttons;
|
unsigned buttons;
|
||||||
|
byte modifiers;
|
||||||
} mouse;
|
} mouse;
|
||||||
struct {
|
struct {
|
||||||
char character;
|
char character;
|
||||||
|
|
|
@ -513,11 +513,12 @@ private:
|
||||||
|
|
||||||
class WSMouseEvent final : public WSMessage {
|
class WSMouseEvent final : public WSMessage {
|
||||||
public:
|
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)
|
: WSMessage(type)
|
||||||
, m_position(position)
|
, m_position(position)
|
||||||
, m_buttons(buttons)
|
, m_buttons(buttons)
|
||||||
, m_button(button)
|
, m_button(button)
|
||||||
|
, m_modifiers(modifiers)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -526,11 +527,13 @@ public:
|
||||||
int y() const { return m_position.y(); }
|
int y() const { return m_position.y(); }
|
||||||
MouseButton button() const { return m_button; }
|
MouseButton button() const { return m_button; }
|
||||||
unsigned buttons() const { return m_buttons; }
|
unsigned buttons() const { return m_buttons; }
|
||||||
|
unsigned modifiers() const { return m_modifiers; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Point m_position;
|
Point m_position;
|
||||||
unsigned m_buttons { 0 };
|
unsigned m_buttons { 0 };
|
||||||
MouseButton m_button { MouseButton::None };
|
MouseButton m_button { MouseButton::None };
|
||||||
|
unsigned m_modifiers { 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
class WSResizeEvent final : public WSMessage {
|
class WSResizeEvent final : public WSMessage {
|
||||||
|
|
|
@ -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) {
|
auto post_mousedown_or_mouseup_if_needed = [&] (MouseButton button) {
|
||||||
if (!(changed_buttons & (unsigned)button))
|
if (!(changed_buttons & (unsigned)button))
|
||||||
return;
|
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));
|
WSMessageLoop::the().post_message(WSWindowManager::the(), move(message));
|
||||||
};
|
};
|
||||||
post_mousedown_or_mouseup_if_needed(MouseButton::Left);
|
post_mousedown_or_mouseup_if_needed(MouseButton::Left);
|
||||||
post_mousedown_or_mouseup_if_needed(MouseButton::Right);
|
post_mousedown_or_mouseup_if_needed(MouseButton::Right);
|
||||||
post_mousedown_or_mouseup_if_needed(MouseButton::Middle);
|
post_mousedown_or_mouseup_if_needed(MouseButton::Middle);
|
||||||
if (m_cursor_location != prev_location) {
|
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));
|
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.)
|
// 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)
|
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());
|
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));
|
WSMessageLoop::the().post_message(WSWindowManager::the(), move(message));
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ private:
|
||||||
|
|
||||||
Point m_cursor_location;
|
Point m_cursor_location;
|
||||||
unsigned m_mouse_button_state { 0 };
|
unsigned m_mouse_button_state { 0 };
|
||||||
|
unsigned m_modifiers { 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
inline RGBA32* WSScreen::scanline(int y)
|
inline RGBA32* WSScreen::scanline(int y)
|
||||||
|
|
|
@ -82,18 +82,21 @@ void WSWindow::on_message(WSMessage& message)
|
||||||
server_message.mouse.position = static_cast<WSMouseEvent&>(message).position();
|
server_message.mouse.position = static_cast<WSMouseEvent&>(message).position();
|
||||||
server_message.mouse.button = WSAPI_MouseButton::NoButton;
|
server_message.mouse.button = WSAPI_MouseButton::NoButton;
|
||||||
server_message.mouse.buttons = static_cast<WSMouseEvent&>(message).buttons();
|
server_message.mouse.buttons = static_cast<WSMouseEvent&>(message).buttons();
|
||||||
|
server_message.mouse.modifiers = static_cast<WSMouseEvent&>(message).modifiers();
|
||||||
break;
|
break;
|
||||||
case WSMessage::MouseDown:
|
case WSMessage::MouseDown:
|
||||||
server_message.type = WSAPI_ServerMessage::Type::MouseDown;
|
server_message.type = WSAPI_ServerMessage::Type::MouseDown;
|
||||||
server_message.mouse.position = static_cast<WSMouseEvent&>(message).position();
|
server_message.mouse.position = static_cast<WSMouseEvent&>(message).position();
|
||||||
server_message.mouse.button = to_api(static_cast<WSMouseEvent&>(message).button());
|
server_message.mouse.button = to_api(static_cast<WSMouseEvent&>(message).button());
|
||||||
server_message.mouse.buttons = static_cast<WSMouseEvent&>(message).buttons();
|
server_message.mouse.buttons = static_cast<WSMouseEvent&>(message).buttons();
|
||||||
|
server_message.mouse.modifiers = static_cast<WSMouseEvent&>(message).modifiers();
|
||||||
break;
|
break;
|
||||||
case WSMessage::MouseUp:
|
case WSMessage::MouseUp:
|
||||||
server_message.type = WSAPI_ServerMessage::Type::MouseUp;
|
server_message.type = WSAPI_ServerMessage::Type::MouseUp;
|
||||||
server_message.mouse.position = static_cast<WSMouseEvent&>(message).position();
|
server_message.mouse.position = static_cast<WSMouseEvent&>(message).position();
|
||||||
server_message.mouse.button = to_api(static_cast<WSMouseEvent&>(message).button());
|
server_message.mouse.button = to_api(static_cast<WSMouseEvent&>(message).button());
|
||||||
server_message.mouse.buttons = static_cast<WSMouseEvent&>(message).buttons();
|
server_message.mouse.buttons = static_cast<WSMouseEvent&>(message).buttons();
|
||||||
|
server_message.mouse.modifiers = static_cast<WSMouseEvent&>(message).modifiers();
|
||||||
break;
|
break;
|
||||||
case WSMessage::WindowEntered:
|
case WSMessage::WindowEntered:
|
||||||
server_message.type = WSAPI_ServerMessage::Type::WindowEntered;
|
server_message.type = WSAPI_ServerMessage::Type::WindowEntered;
|
||||||
|
|
|
@ -762,7 +762,7 @@ void WSWindowManager::process_mouse_event(WSMouseEvent& event, WSWindow*& event_
|
||||||
continue;
|
continue;
|
||||||
ASSERT(window->is_visible()); // Maybe this should be supported? Idk. Let's catch it and think about it later.
|
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() };
|
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);
|
window->on_message(*local_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -815,7 +815,7 @@ void WSWindowManager::process_mouse_event(WSMouseEvent& event, WSWindow*& event_
|
||||||
if (!window.global_cursor_tracking()) {
|
if (!window.global_cursor_tracking()) {
|
||||||
// FIXME: Should we just alter the coordinates of the existing MouseEvent and pass it through?
|
// 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() };
|
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);
|
window.on_message(*local_event);
|
||||||
}
|
}
|
||||||
return IterationDecision::Abort;
|
return IterationDecision::Abort;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue