1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 00:17:46 +00:00

LibGUI+WindowServer: Start fleshing out drag&drop functionality

This patch enables basic drag&drop between applications.
You initiate a drag by creating a GDragOperation object and calling
exec() on it. This creates a nested event loop in the calling program
that only returns once the drag operation has ended.

On the receiving side, you get a call to GWidget::drop_event() with
a GDropEvent containing information about the dropped data.

The only data passed right now is a piece of text that's also used
to visually indicate that a drag is happening (by showing the text in
a little box that follows the mouse cursor around.)

There are things to fix here, but we're off to a nice start. :^)
This commit is contained in:
Andreas Kling 2019-12-08 16:50:23 +01:00
parent e09a02ad3f
commit a7f414bba7
19 changed files with 318 additions and 3 deletions

View file

@ -0,0 +1,53 @@
#include <LibGUI/GDragOperation.h>
#include <LibGUI/GWindowServerConnection.h>
static GDragOperation* s_current_drag_operation;
GDragOperation::GDragOperation(CObject* parent)
: CObject(parent)
{
}
GDragOperation::~GDragOperation()
{
}
GDragOperation::Outcome GDragOperation::exec()
{
ASSERT(!s_current_drag_operation);
ASSERT(!m_event_loop);
auto response = GWindowServerConnection::the().send_sync<WindowServer::StartDrag>(m_text, -1, Size());
if (!response->started()) {
m_outcome = Outcome::Cancelled;
return m_outcome;
}
s_current_drag_operation = this;
m_event_loop = make<CEventLoop>();
auto result = m_event_loop->exec();
m_event_loop = nullptr;
dbgprintf("%s: event loop returned with result %d\n", class_name(), result);
remove_from_parent();
s_current_drag_operation = nullptr;
return m_outcome;
}
void GDragOperation::done(Outcome outcome)
{
ASSERT(m_outcome == Outcome::None);
m_outcome = outcome;
m_event_loop->quit(0);
}
void GDragOperation::notify_accepted(Badge<GWindowServerConnection>)
{
ASSERT(s_current_drag_operation);
s_current_drag_operation->done(Outcome::Accepted);
}
void GDragOperation::notify_cancelled(Badge<GWindowServerConnection>)
{
ASSERT(s_current_drag_operation);
s_current_drag_operation->done(Outcome::Cancelled);
}

View file

@ -0,0 +1,36 @@
#pragma once
#include <LibCore/CEventLoop.h>
#include <LibCore/CObject.h>
class GWindowServerConnection;
class GDragOperation : public CObject {
C_OBJECT(GDragOperation)
public:
enum class Outcome {
None,
Accepted,
Cancelled,
};
virtual ~GDragOperation() override;
void set_text(const String& text) { m_text = text; }
Outcome exec();
Outcome outcome() const { return m_outcome; }
static void notify_accepted(Badge<GWindowServerConnection>);
static void notify_cancelled(Badge<GWindowServerConnection>);
protected:
explicit GDragOperation(CObject* parent = nullptr);
private:
void done(Outcome);
OwnPtr<CEventLoop> m_event_loop;
Outcome m_outcome { Outcome::None };
String m_text;
};

View file

@ -2,9 +2,9 @@
#include <Kernel/KeyCode.h>
#include <LibCore/CEvent.h>
#include <LibGUI/GWindowType.h>
#include <LibDraw/Point.h>
#include <LibDraw/Rect.h>
#include <LibGUI/GWindowType.h>
class CObject;
@ -34,6 +34,7 @@ public:
WindowCloseRequest,
ContextMenu,
EnabledChange,
Drop,
__Begin_WM_Events,
WM_WindowRemoved,
@ -278,3 +279,20 @@ private:
unsigned m_modifiers { 0 };
int m_wheel_delta { 0 };
};
class GDropEvent final : public GEvent {
public:
GDropEvent(const Point& position, const String& text)
: GEvent(GEvent::Drop)
, m_position(position)
, m_text(text)
{
}
const Point& position() const { return m_position; }
const String& text() const { return m_text; }
private:
Point m_position;
String m_text;
};

View file

@ -1,4 +1,6 @@
#include <AK/StringBuilder.h>
#include <Kernel/KeyCode.h>
#include <LibGUI/GDragOperation.h>
#include <LibGUI/GItemView.h>
#include <LibGUI/GModel.h>
#include <LibGUI/GPainter.h>
@ -83,6 +85,7 @@ void GItemView::mousedown_event(GMouseEvent& event)
int item_index = item_at_event_position(event.position());
if (event.button() == GMouseButton::Left) {
m_left_mousedown_position = event.position();
if (item_index == -1) {
selection().clear();
} else {
@ -97,6 +100,45 @@ void GItemView::mousedown_event(GMouseEvent& event)
GAbstractView::mousedown_event(event);
}
void GItemView::mousemove_event(GMouseEvent& event)
{
if (!model())
return GAbstractView::mousemove_event(event);
if (event.buttons() & GMouseButton::Left && !selection().is_empty()) {
auto diff = event.position() - m_left_mousedown_position;
auto distance_travelled_squared = diff.x() * diff.x() + diff.y() * diff.y();
constexpr int drag_distance_threshold = 5;
if (distance_travelled_squared > (drag_distance_threshold)) {
dbg() << "Initiate drag!";
auto drag_operation = GDragOperation::construct();
StringBuilder builder;
selection().for_each_index([&](auto& index) {
auto data = model()->data(index);
builder.append(data.to_string());
builder.append(" ");
});
drag_operation->set_text(builder.to_string());
auto outcome = drag_operation->exec();
switch (outcome) {
case GDragOperation::Outcome::Accepted:
dbg() << "Drag was accepted!";
break;
case GDragOperation::Outcome::Cancelled:
dbg() << "Drag was cancelled!";
break;
default:
ASSERT_NOT_REACHED();
break;
}
}
}
GAbstractView::mousemove_event(event);
}
void GItemView::context_menu_event(GContextMenuEvent& event)
{
if (!model())

View file

@ -29,6 +29,7 @@ private:
virtual void paint_event(GPaintEvent&) override;
virtual void resize_event(GResizeEvent&) override;
virtual void mousedown_event(GMouseEvent&) override;
virtual void mousemove_event(GMouseEvent&) override;
virtual void keydown_event(GKeyEvent&) override;
virtual void doubleclick_event(GMouseEvent&) override;
virtual void context_menu_event(GContextMenuEvent&) override;
@ -43,5 +44,7 @@ private:
int m_visual_column_count { 0 };
int m_visual_row_count { 0 };
Point m_left_mousedown_position;
Size m_effective_item_size { 80, 80 };
};

View file

@ -156,6 +156,8 @@ void GWidget::event(CEvent& event)
return handle_mouseup_event(static_cast<GMouseEvent&>(event));
case GEvent::MouseWheel:
return mousewheel_event(static_cast<GMouseEvent&>(event));
case GEvent::Drop:
return drop_event(static_cast<GDropEvent&>(event));
case GEvent::Enter:
return handle_enter_event(event);
case GEvent::Leave:
@ -350,6 +352,11 @@ void GWidget::change_event(GEvent&)
{
}
void GWidget::drop_event(GDropEvent& event)
{
dbg() << class_name() << "{" << this << "} DROP position: " << event.position() << ", text: '" << event.text() << "'";
}
void GWidget::update()
{
if (rect().is_empty())
@ -686,4 +693,3 @@ Vector<GWidget*> GWidget::child_widgets() const
}
return widgets;
}

View file

@ -254,6 +254,7 @@ protected:
virtual void leave_event(CEvent&);
virtual void child_event(CChildEvent&) override;
virtual void change_event(GEvent&);
virtual void drop_event(GDropEvent&);
private:
void handle_paint_event(GPaintEvent&);

View file

@ -154,6 +154,16 @@ void GWindow::set_override_cursor(GStandardCursor cursor)
void GWindow::event(CEvent& event)
{
if (event.type() == GEvent::Drop) {
auto& drop_event = static_cast<GDropEvent&>(event);
if (!m_main_widget)
return;
auto result = m_main_widget->hit_test(drop_event.position());
auto local_event = make<GDropEvent>(result.local_position, drop_event.text());
ASSERT(result.widget);
return result.widget->dispatch_event(*local_event, this);
}
if (event.type() == GEvent::MouseUp || event.type() == GEvent::MouseDown || event.type() == GEvent::MouseDoubleClick || event.type() == GEvent::MouseMove || event.type() == GEvent::MouseWheel) {
auto& mouse_event = static_cast<GMouseEvent&>(event);
if (m_global_cursor_tracking_widget) {

View file

@ -2,6 +2,7 @@
#include <LibGUI/GApplication.h>
#include <LibGUI/GClipboard.h>
#include <LibGUI/GDesktop.h>
#include <LibGUI/GDragOperation.h>
#include <LibGUI/GEvent.h>
#include <LibGUI/GMenu.h>
#include <LibGUI/GWidget.h>
@ -261,3 +262,19 @@ void GWindowServerConnection::handle(const WindowClient::AsyncSetWallpaperFinish
{
// This is handled manually by GDesktop::set_wallpaper().
}
void GWindowServerConnection::handle(const WindowClient::DragDropped& message)
{
if (auto* window = GWindow::from_window_id(message.window_id()))
CEventLoop::current().post_event(*window, make<GDropEvent>(message.mouse_position(), message.text()));
}
void GWindowServerConnection::handle(const WindowClient::DragAccepted&)
{
GDragOperation::notify_accepted({});
}
void GWindowServerConnection::handle(const WindowClient::DragCancelled&)
{
GDragOperation::notify_cancelled({});
}

View file

@ -41,4 +41,7 @@ private:
virtual void handle(const WindowClient::WM_WindowIconBitmapChanged&) override;
virtual void handle(const WindowClient::WM_WindowRectChanged&) override;
virtual void handle(const WindowClient::AsyncSetWallpaperFinished&) override;
virtual void handle(const WindowClient::DragDropped&) override;
virtual void handle(const WindowClient::DragAccepted&) override;
virtual void handle(const WindowClient::DragCancelled&) override;
};

View file

@ -59,6 +59,7 @@ OBJS = \
GLazyWidget.o \
GCommand.o \
GUndoStack.o \
GDragOperation.o \
GWindow.o
LIBRARY = libgui.a