1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 23:07:35 +00:00

LibGUI: Have widgets signal willingness to accept drops

If a widget accept()'s a "drag enter" event, that widget now becomes
the application-wide "pending drop" widget. That state is cleared if
the drag moves over another widget (or leaves the window entirely.)
This commit is contained in:
Andreas Kling 2021-01-09 00:11:17 +01:00
parent dbd090fd95
commit 3b94af2c07
5 changed files with 60 additions and 19 deletions

View file

@ -93,6 +93,9 @@ Application::Application(int argc, char** argv)
if (getenv("GUI_FOCUS_DEBUG"))
m_focus_debugging_enabled = true;
if (getenv("GUI_DND_DEBUG"))
m_dnd_debugging_enabled = true;
for (int i = 1; i < argc; i++) {
String arg(argv[i]);
m_args.append(move(arg));
@ -247,21 +250,36 @@ void Application::window_did_become_inactive(Badge<Window>, Window& window)
m_active_window = nullptr;
}
void Application::set_pending_drop_widget(Widget* widget)
{
if (m_pending_drop_widget == widget)
return;
if (m_pending_drop_widget)
m_pending_drop_widget->update();
m_pending_drop_widget = widget;
if (m_pending_drop_widget)
m_pending_drop_widget->update();
}
void Application::set_drag_hovered_widget_impl(Widget* widget, const Gfx::IntPoint& position, const String& mime_type)
{
if (widget == m_drag_hovered_widget)
return;
if (m_drag_hovered_widget) {
m_drag_hovered_widget->update();
Core::EventLoop::current().post_event(*m_drag_hovered_widget, make<Event>(Event::DragLeave));
Event leave_event(Event::DragLeave);
m_drag_hovered_widget->dispatch_event(leave_event, m_drag_hovered_widget->window());
}
set_pending_drop_widget(nullptr);
m_drag_hovered_widget = widget;
if (m_drag_hovered_widget) {
m_drag_hovered_widget->update();
Core::EventLoop::current().post_event(*m_drag_hovered_widget, make<DragEvent>(Event::DragEnter, position, mime_type));
DragEvent enter_event(Event::DragEnter, position, mime_type);
enter_event.ignore();
m_drag_hovered_widget->dispatch_event(enter_event, m_drag_hovered_widget->window());
if (enter_event.is_accepted())
set_pending_drop_widget(m_drag_hovered_widget);
}
}

View file

@ -74,6 +74,7 @@ public:
void set_system_palette(SharedBuffer&);
bool focus_debugging_enabled() const { return m_focus_debugging_enabled; }
bool dnd_debugging_enabled() const { return m_dnd_debugging_enabled; }
Core::EventLoop& event_loop() { return *m_event_loop; }
@ -86,6 +87,9 @@ public:
Widget* drag_hovered_widget() { return m_drag_hovered_widget.ptr(); }
const Widget* drag_hovered_widget() const { return m_drag_hovered_widget.ptr(); }
Widget* pending_drop_widget() { return m_pending_drop_widget.ptr(); }
const Widget* pending_drop_widget() const { return m_pending_drop_widget.ptr(); }
void set_drag_hovered_widget(Badge<Window>, Widget* widget, const Gfx::IntPoint& position = {}, const String& mime_type = {})
{
set_drag_hovered_widget_impl(widget, position, mime_type);
@ -99,6 +103,7 @@ private:
void tooltip_hide_timer_did_fire();
void set_drag_hovered_widget_impl(Widget*, const Gfx::IntPoint& = {}, const String& = {});
void set_pending_drop_widget(Widget*);
OwnPtr<Core::EventLoop> m_event_loop;
RefPtr<MenuBar> m_menubar;
@ -113,9 +118,11 @@ private:
WeakPtr<Window> m_active_window;
bool m_quit_when_last_window_deleted { true };
bool m_focus_debugging_enabled { false };
bool m_dnd_debugging_enabled { false };
String m_invoked_as;
Vector<String> m_args;
WeakPtr<Widget> m_drag_hovered_widget;
WeakPtr<Widget> m_pending_drop_widget;
};
}

View file

@ -314,16 +314,21 @@ void Widget::handle_paint_event(PaintEvent& event)
});
second_paint_event(event);
if (is_being_inspected()) {
auto* app = Application::the();
if (app && app->dnd_debugging_enabled() && has_pending_drop()) {
Painter painter(*this);
painter.draw_rect(rect(), Color::Magenta);
painter.draw_rect(rect(), Color::Blue);
}
if (Application::the()->focus_debugging_enabled()) {
if (is_focused()) {
if (app && app->focus_debugging_enabled() && is_focused()) {
Painter painter(*this);
painter.draw_rect(rect(), Color::Cyan);
}
if (is_being_inspected()) {
Painter painter(*this);
painter.draw_rect(rect(), Color::Magenta);
}
}
@ -499,27 +504,23 @@ void Widget::change_event(Event&)
{
}
void Widget::drag_move_event(DragEvent& event)
void Widget::drag_move_event(DragEvent&)
{
event.ignore();
}
void Widget::drag_enter_event(DragEvent& event)
{
dbgln("{} {:p} DRAG ENTER @ {}, {}", class_name(), this, event.position(), event.data_type());
event.ignore();
}
void Widget::drag_leave_event(Event& event)
void Widget::drag_leave_event(Event&)
{
dbgln("{} {:p} DRAG LEAVE", class_name(), this);
event.ignore();
}
void Widget::drop_event(DropEvent& event)
{
dbgln("{} {:p} DROP @ {}, '{}'", class_name(), this, event.position(), event.text());
event.ignore();
}
void Widget::theme_change_event(ThemeChangeEvent&)
@ -1035,4 +1036,9 @@ void Widget::set_shrink_to_fit(bool b)
invalidate_layout();
}
bool Widget::has_pending_drop() const
{
return Application::the()->pending_drop_widget() == this;
}
}

View file

@ -302,6 +302,8 @@ public:
void set_shrink_to_fit(bool);
bool is_shrink_to_fit() const { return m_shrink_to_fit; }
bool has_pending_drop() const;
protected:
Widget();

View file

@ -444,10 +444,18 @@ void Window::handle_drag_move_event(DragEvent& event)
if (!m_main_widget)
return;
auto result = m_main_widget->hit_test(event.position());
auto local_event = make<DragEvent>(static_cast<Event::Type>(event.type()), result.local_position, event.data_type());
ASSERT(result.widget);
Application::the()->set_drag_hovered_widget({}, result.widget, result.local_position, event.data_type());
return result.widget->dispatch_event(*local_event, this);
// NOTE: Setting the drag hovered widget may have executed arbitrary code, so re-check that the widget is still there.
if (!result.widget)
return;
if (result.widget->has_pending_drop()) {
DragEvent drag_move_event(static_cast<Event::Type>(event.type()), result.local_position, event.data_type());
result.widget->dispatch_event(drag_move_event, this);
}
}
void Window::handle_left_event()