1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 23:37:36 +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

@ -1,12 +1,12 @@
#include <LibC/SharedBuffer.h>
#include <LibDraw/GraphicsBitmap.h>
#include <SharedBuffer.h>
#include <WindowServer/WSMenuApplet.h>
#include <WindowServer/WSClientConnection.h>
#include <WindowServer/WSClipboard.h>
#include <WindowServer/WSCompositor.h>
#include <WindowServer/WSEventLoop.h>
#include <WindowServer/WSMenu.h>
#include <WindowServer/WSMenuApplet.h>
#include <WindowServer/WSMenuBar.h>
#include <WindowServer/WSMenuItem.h>
#include <WindowServer/WSScreen.h>
@ -678,3 +678,24 @@ OwnPtr<WindowServer::InvalidateMenuAppletRectResponse> WSClientConnection::handl
it->value->invalidate(message.rect());
return make<WindowServer::InvalidateMenuAppletRectResponse>();
}
OwnPtr<WindowServer::StartDragResponse> WSClientConnection::handle(const WindowServer::StartDrag& message)
{
auto& wm = WSWindowManager::the();
if (wm.dnd_client())
return make<WindowServer::StartDragResponse>(false);
RefPtr<GraphicsBitmap> bitmap;
if (message.bitmap_id() != -1) {
auto shared_buffer = SharedBuffer::create_from_shared_buffer_id(message.bitmap_id());
ssize_t size_in_bytes = message.bitmap_size().area() * sizeof(RGBA32);
if (size_in_bytes > shared_buffer->size()) {
did_misbehave("SetAppletBackingStore: Shared buffer is too small for applet size");
return nullptr;
}
bitmap = GraphicsBitmap::create_with_shared_buffer(GraphicsBitmap::Format::RGBA32, *shared_buffer, message.bitmap_size());
}
wm.start_dnd_drag(*this, message.text(), bitmap);
return make<WindowServer::StartDragResponse>(true);
}

View file

@ -92,6 +92,7 @@ private:
virtual OwnPtr<WindowServer::DestroyMenuAppletResponse> handle(const WindowServer::DestroyMenuApplet&) override;
virtual OwnPtr<WindowServer::SetMenuAppletBackingStoreResponse> handle(const WindowServer::SetMenuAppletBackingStore&) override;
virtual OwnPtr<WindowServer::InvalidateMenuAppletRectResponse> handle(const WindowServer::InvalidateMenuAppletRect&) override;
virtual OwnPtr<WindowServer::StartDragResponse> handle(const WindowServer::StartDrag&) override;
HashMap<i32, NonnullOwnPtr<WSMenuApplet>> m_menu_applets;
HashMap<int, NonnullRefPtr<WSWindow>> m_windows;

View file

@ -93,6 +93,7 @@ void WSCompositor::compose()
dirty_rects.add(Rect::intersection(m_last_geometry_label_rect, WSScreen::the().rect()));
dirty_rects.add(Rect::intersection(m_last_cursor_rect, WSScreen::the().rect()));
dirty_rects.add(Rect::intersection(m_last_dnd_rect, WSScreen::the().rect()));
dirty_rects.add(Rect::intersection(current_cursor_rect(), WSScreen::the().rect()));
#ifdef DEBUG_COUNTERS
dbgprintf("[WM] compose #%u (%u rects)\n", ++m_compose_count, dirty_rects.rects().size());
@ -387,6 +388,9 @@ Rect WSCompositor::current_cursor_rect() const
void WSCompositor::invalidate_cursor()
{
auto& wm = WSWindowManager::the();
if (wm.dnd_client())
invalidate(wm.dnd_rect());
invalidate(current_cursor_rect());
}
@ -417,5 +421,17 @@ void WSCompositor::draw_cursor()
auto& wm = WSWindowManager::the();
Rect cursor_rect = current_cursor_rect();
m_back_painter->blit(cursor_rect.location(), wm.active_cursor().bitmap(), wm.active_cursor().rect());
if (wm.dnd_client()) {
auto dnd_rect = wm.dnd_rect();
if (!wm.dnd_text().is_empty()) {
m_back_painter->fill_rect(dnd_rect, Color(110, 34, 9, 200));
m_back_painter->draw_text(dnd_rect, wm.dnd_text(), TextAlignment::Center, Color::White);
}
// FIXME: Also do the drag_bitmap if present
m_last_dnd_rect = dnd_rect;
} else {
m_last_dnd_rect = {};
}
m_last_cursor_rect = cursor_rect;
}

View file

@ -61,6 +61,7 @@ private:
DisjointRectSet m_dirty_rects;
Rect m_last_cursor_rect;
Rect m_last_dnd_rect;
Rect m_last_geometry_label_rect;
String m_wallpaper_path;

View file

@ -20,6 +20,7 @@
#include <WindowServer/WSButton.h>
#include <WindowServer/WSClientConnection.h>
#include <WindowServer/WSCursor.h>
#include <WindowServer/WindowClientEndpoint.h>
#include <errno.h>
#include <stdio.h>
#include <time.h>
@ -625,6 +626,36 @@ bool WSWindowManager::process_ongoing_window_resize(const WSMouseEvent& event, W
return true;
}
bool WSWindowManager::process_ongoing_drag(WSMouseEvent& event, WSWindow*& hovered_window)
{
if (!m_dnd_client)
return false;
if (!(event.type() == WSEvent::MouseUp && event.button() == MouseButton::Left))
return true;
hovered_window = nullptr;
for_each_visible_window_from_front_to_back([&](auto& window) {
if (window.frame().rect().contains(event.position())) {
hovered_window = &window;
return IterationDecision::Break;
}
return IterationDecision::Continue;
});
if (hovered_window) {
m_dnd_client->post_message(WindowClient::DragAccepted());
if (hovered_window->client()) {
auto translated_event = event.translated(-hovered_window->position());
hovered_window->client()->post_message(WindowClient::DragDropped(hovered_window->window_id(), translated_event.position(), m_dnd_text));
}
} else {
m_dnd_client->post_message(WindowClient::DragCancelled());
}
end_dnd_drag();
return true;
}
void WSWindowManager::set_cursor_tracking_button(WSButton* button)
{
m_cursor_tracking_button = button ? button->make_weak_ptr() : nullptr;
@ -708,6 +739,9 @@ void WSWindowManager::process_mouse_event(WSMouseEvent& event, WSWindow*& hovere
{
hovered_window = nullptr;
if (process_ongoing_drag(event, hovered_window))
return;
if (process_ongoing_window_drag(event, hovered_window))
return;
@ -943,6 +977,12 @@ void WSWindowManager::event(CEvent& event)
auto& key_event = static_cast<const WSKeyEvent&>(event);
m_keyboard_modifiers = key_event.modifiers();
if (key_event.type() == WSEvent::KeyDown && key_event.key() == Key_Escape && m_dnd_client) {
m_dnd_client->post_message(WindowClient::DragCancelled());
end_dnd_drag();
return;
}
if (key_event.type() == WSEvent::KeyDown && key_event.modifiers() == Mod_Logo && key_event.key() == Key_Tab)
m_switcher.show();
if (m_switcher.is_visible()) {
@ -1138,3 +1178,29 @@ WSMenu* WSWindowManager::find_internal_menu_by_id(int menu_id)
}
return nullptr;
}
void WSWindowManager::start_dnd_drag(WSClientConnection& client, const String& text, GraphicsBitmap* bitmap)
{
ASSERT(!m_dnd_client);
m_dnd_client = client.make_weak_ptr();
m_dnd_text = text;
m_dnd_bitmap = bitmap;
WSCompositor::the().invalidate_cursor();
}
void WSWindowManager::end_dnd_drag()
{
ASSERT(m_dnd_client);
WSCompositor::the().invalidate_cursor();
m_dnd_client = nullptr;
m_dnd_text = {};
m_dnd_bitmap = nullptr;
}
Rect WSWindowManager::dnd_rect() const
{
int width = font().width(m_dnd_text);
int height = font().glyph_height();
auto location = WSCompositor::the().current_cursor_rect().center().translated(8, 8);
return Rect(location, { width, height }).inflated(4, 4);
}

View file

@ -66,6 +66,14 @@ public:
Rect maximized_window_rect(const WSWindow&) const;
WSClientConnection* dnd_client() { return m_dnd_client.ptr(); }
const String& dnd_text() const { return m_dnd_text; }
const GraphicsBitmap* dnd_bitmap() const { return m_dnd_bitmap; }
Rect dnd_rect() const;
void start_dnd_drag(WSClientConnection&, const String& text, GraphicsBitmap*);
void end_dnd_drag();
WSWindow* active_window() { return m_active_window.ptr(); }
const WSClientConnection* active_client() const;
bool active_window_is_modal() const { return m_active_window && m_active_window->is_modal(); }
@ -156,6 +164,7 @@ private:
void deliver_mouse_event(WSWindow& window, WSMouseEvent& event);
bool process_ongoing_window_resize(const WSMouseEvent&, WSWindow*& hovered_window);
bool process_ongoing_window_drag(WSMouseEvent&, WSWindow*& hovered_window);
bool process_ongoing_drag(WSMouseEvent&, WSWindow*& hovered_window);
void start_window_drag(WSWindow&, const WSMouseEvent&);
void set_hovered_window(WSWindow*);
template<typename Callback>
@ -268,6 +277,10 @@ private:
};
Vector<AppMetadata> m_apps;
HashMap<String, NonnullRefPtr<WSMenu>> m_app_category_menus;
WeakPtr<WSClientConnection> m_dnd_client;
String m_dnd_text;
RefPtr<GraphicsBitmap> m_dnd_bitmap;
};
template<typename Callback>

View file

@ -27,4 +27,9 @@ endpoint WindowClient = 4
WM_WindowRectChanged(i32 client_id, i32 window_id, Rect rect) =|
AsyncSetWallpaperFinished(bool success) =|
DragAccepted() =|
DragCancelled() =|
DragDropped(i32 window_id, Point mouse_position, String text) =|
}

View file

@ -71,4 +71,6 @@ endpoint WindowServer = 2
GetWallpaper() => (String path)
SetWindowOverrideCursor(i32 window_id, i32 cursor_type) => ()
StartDrag(String text, i32 bitmap_id, Size bitmap_size) => (bool started)
}