Introduce drag & drop events to widgets

This commit is contained in:
Martín Capello 2024-09-03 10:16:28 -03:00
parent 2cf880eaf9
commit 65ec2bd7b7
7 changed files with 179 additions and 4 deletions

View File

@ -29,8 +29,9 @@ namespace ui {
DOUBLE_BUFFERED = 0x00002000, // The widget is painted in a back-buffer and then flipped to the main display
TRANSPARENT = 0x00004000, // The widget has transparent parts that needs the background painted before
CTRL_RIGHT_CLICK = 0x00008000, // The widget should transform Ctrl+click to right-click on OS X.
ALLOW_DROP = 0x40000000, // The widget can participate as a drop target in a drag & drop operation.
IGNORE_MOUSE = 0x80000000, // Don't process mouse messages for this widget (useful for labels, boxes, grids, etc.)
PROPERTIES_MASK = 0x8000ffff,
PROPERTIES_MASK = 0xC000ffff,
HORIZONTAL = 0x00010000,
VERTICAL = 0x00020000,
@ -43,7 +44,7 @@ namespace ui {
HOMOGENEOUS = 0x01000000,
WORDWRAP = 0x02000000,
CHARWRAP = 0x04000000,
ALIGN_MASK = 0x7fff0000,
ALIGN_MASK = 0x3fff0000,
};
} // namespace ui

48
src/ui/drag_event.h Normal file
View File

@ -0,0 +1,48 @@
// Aseprite UI Library
// Copyright (C) 2024 Igara Studio S.A.
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef UI_DRAG_EVENT_H_INCLUDED
#define UI_DRAG_EVENT_H_INCLUDED
#pragma once
#include "os/dnd.h"
#include "ui/event.h"
#include "ui/widget.h"
namespace ui {
class DragEvent : public Event {
public:
DragEvent(Component* source, ui::Widget* target, os::DragEvent& ev)
: Event(source)
, m_position(ev.position() - target->bounds().origin())
, m_ev(ev) {}
bool handled() const { return m_handled; }
void handled(bool value) { m_handled = value; }
// Operations allowed by the source of the drag & drop operation. Can be a
// bitwise combination of values.
os::DropOperation allowedOperations() const { return m_ev.supportedOperations(); }
// Operation supported by the target of the drag & drop operation. Cannot
// be a bitwise combination of values.
os::DropOperation supportsOperation() const { return m_ev.dropResult(); }
// Set the operation supported by the target of the drag & drop operation.
// Cannot be a bitwise combination of values.
void supportsOperation(os::DropOperation operation) { m_ev.dropResult(operation); }
const gfx::Point& position() const { return m_position; }
private:
bool m_handled = false;
gfx::Point m_position;
os::DragEvent& m_ev;
};
} // namespace ui
#endif // UI_DRAG_EVENT_H_INCLUDED

View File

@ -28,6 +28,8 @@
#include "os/system.h"
#include "os/window.h"
#include "os/window_spec.h"
#include "ui/base.h"
#include "ui/drag_event.h"
#include "ui/intern.h"
#include "ui/ui.h"
@ -205,8 +207,10 @@ Manager::Manager(const os::WindowRef& nativeWindow)
, m_mouseButton(kButtonNone)
{
// The native window can be nullptr when running tests
if (nativeWindow)
if (nativeWindow) {
nativeWindow->setUserData(&m_display);
nativeWindow->setDragTarget(this);
}
#ifdef DEBUG_UI_THREADS
ASSERT(manager_thread == std::thread::id());
@ -2037,6 +2041,76 @@ bool Manager::sendMessageToWidget(Message* msg, Widget* widget)
return used;
}
void Manager::dragEnter(os::DragEvent& ev)
{
Widget* widget = pick(ev.position());
if (widget && widget->hasFlags(ALLOW_DROP)) {
m_dragOverWidget = widget;
DragEvent uiev(this, widget, ev);
widget->onDragEnter(uiev);
ev.dropResult(uiev.supportsOperation());
}
}
void Manager::dragLeave(os::DragEvent& ev)
{
Widget* widget = m_dragOverWidget;
if (widget) {
DragEvent uiev(this, widget, ev);
widget->onDragLeave(uiev);
m_dragOverWidget = nullptr;
}
}
void Manager::drag(os::DragEvent& ev)
{
Widget* widget = pick(ev.position());
if (m_dragOverWidget && m_dragOverWidget != widget) {
DragEvent uiev(this, m_dragOverWidget, ev);
m_dragOverWidget->onDragLeave(uiev);
m_dragOverWidget = nullptr;
}
if (widget && widget->hasFlags(ALLOW_DROP)) {
DragEvent uiev(this, widget, ev);
if (m_dragOverWidget != widget) {
m_dragOverWidget = widget;
widget->onDragEnter(uiev);
}
widget->onDrag(uiev);
ev.dropResult(uiev.supportsOperation());
}
}
void Manager::drop(os::DragEvent& ev)
{
m_dragOverWidget = nullptr;
Widget* widget = pick(ev.position());
if (widget && widget->hasFlags(ALLOW_DROP)) {
DragEvent uiev(this, widget, ev);
widget->onDrop(uiev);
if (uiev.handled()) {
ev.acceptDrop(true);
return;
}
}
// There were no widget that accepted the drop, then see if we can treat it
// like a DropFiles event.
if (ev.dataProvider()->contains(os::DragDataItemType::Paths)) {
ev.acceptDrop(true);
// We must queue an os::Event to wakeup the underlying system queue on
// masOS. If we had used the enqueueMessage() method instead, it could
// happen that the program might look unresponsive because it is waiting
// for an OS event.
os::Event dropFilesEv;
dropFilesEv.setType(os::Event::DropFiles);
dropFilesEv.setFiles(ev.dataProvider()->getPaths());
os::System::instance()->eventQueue()->queueEvent(dropFilesEv);
}
}
// It's like Widget::onInvalidateRegion() but optimized for the
// Manager (as we know that all children in a Manager will be windows,
// we can use this knowledge to avoid some calculations).

View File

@ -10,6 +10,7 @@
#pragma once
#include "gfx/region.h"
#include "os/dnd.h"
#include "ui/display.h"
#include "ui/keys.h"
#include "ui/message_type.h"
@ -28,7 +29,8 @@ namespace ui {
class Timer;
class Window;
class Manager : public Widget {
class Manager : public Widget
, public os::DragTarget {
public:
static Manager* getDefault() { return m_defaultManager; }
static bool widgetAssociatedToManager(Widget* widget);
@ -161,6 +163,11 @@ namespace ui {
int pumpQueue();
bool sendMessageToWidget(Message* msg, Widget* widget);
void dragEnter(os::DragEvent& ev) override;
void dragLeave(os::DragEvent& ev) override;
void drag(os::DragEvent& ev) override;
void drop(os::DragEvent& ev) override;
static Widget* findLowestCommonAncestor(Widget* a, Widget* b);
static bool someParentIsFocusStop(Widget* widget);
static Widget* findMagneticWidget(Widget* widget);
@ -189,6 +196,9 @@ namespace ui {
// Last pressed mouse button.
MouseButton m_mouseButton;
// Widget over which the drag is being hovered in a drag & drop operation.
Widget* m_dragOverWidget = nullptr;
};
} // namespace ui

View File

@ -21,6 +21,7 @@
#include "ui/cursor.h"
#include "ui/cursor_type.h"
#include "ui/display.h"
#include "ui/drag_event.h"
#include "ui/entry.h"
#include "ui/event.h"
#include "ui/fit_bounds.h"

View File

@ -22,6 +22,7 @@
#include "text/font.h"
#include "text/font_mgr.h"
#include "ui/app_state.h"
#include "ui/drag_event.h"
#include "ui/init_theme_event.h"
#include "ui/intern.h"
#include "ui/layout_io.h"
@ -1800,6 +1801,38 @@ text::ShaperFeatures Widget::onGetTextShaperFeatures() const
return text::ShaperFeatures();
}
void Widget::onDragEnter(DragEvent& e)
{
#ifdef _DEBUG
LOG(VERBOSE, "UI: [id=%s, type=%d]: onDragEnter(), position: (%d, %d)\n",
id().c_str(), type(), e.position().x, e.position().y);
#endif
}
void Widget::onDragLeave(DragEvent& e)
{
#ifdef _DEBUG
LOG(VERBOSE, "UI: [id=%s, type=%d]: onDragLeave(), position: (%d, %d)\n",
id().c_str(), type(), e.position().x, e.position().y);
#endif
}
void Widget::onDrag(DragEvent& e)
{
#ifdef _DEBUG
LOG(VERBOSE, "UI: [id=%s, type=%d]: onDrag(), position: (%d, %d)\n",
id().c_str(), type(), e.position().x, e.position().y);
#endif
}
void Widget::onDrop(DragEvent& e)
{
#ifdef _DEBUG
LOG(VERBOSE, "UI: [id=%s, type=%d]: onDrop(), position: (%d, %d)\n",
id().c_str(), type(), e.position().x, e.position().y);
#endif
}
void Widget::offsetWidgets(int dx, int dy)
{
if (dx == 0 && dy == 0)

View File

@ -44,6 +44,7 @@ namespace ui {
class Style;
class Theme;
class Window;
class DragEvent;
class Widget : public Component {
public:
@ -445,6 +446,11 @@ namespace ui {
virtual text::TextBlobRef onMakeTextBlob() const;
virtual text::ShaperFeatures onGetTextShaperFeatures() const;
virtual void onDragEnter(DragEvent& e);
virtual void onDragLeave(DragEvent& e);
virtual void onDrag(DragEvent& e);
virtual void onDrop(DragEvent& e);
private:
void removeChild(const WidgetsList::iterator& it);
void paint(Graphics* graphics,
@ -483,6 +489,8 @@ namespace ui {
gfx::Border m_border; // Border separation with the parent
int m_childSpacing; // Separation between children
friend Manager;
};
WidgetType register_widget_type();