Skip to content

Commit fc65d95

Browse files
committed
Introduce drag and drop handling
1 parent 94500bc commit fc65d95

22 files changed

+1265
-17
lines changed

examples/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ if(LAF_BACKEND STREQUAL "skia")
1616
laf_add_example(base64 CONSOLE)
1717
laf_add_example(complextextlayout GUI)
1818
laf_add_example(custom_window GUI)
19+
laf_add_example(drag_and_drop GUI)
1920
laf_add_example(floating_window GUI)
2021
laf_add_example(helloworld GUI)
2122
laf_add_example(listfonts CONSOLE)

examples/drag_and_drop.cpp

+208
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
// LAF Library
2+
// Copyright (c) 2024 Igara Studio S.A.
3+
//
4+
// This file is released under the terms of the MIT license.
5+
// Read LICENSE.txt for more information.
6+
7+
8+
#include "base/paths.h"
9+
#include "gfx/hsv.h"
10+
#include "gfx/point.h"
11+
#include "gfx/rect.h"
12+
#include "gfx/rgb.h"
13+
#include "os/dnd.h"
14+
#include "os/draw_text.h"
15+
#include "os/os.h"
16+
#include "os/paint.h"
17+
#include "os/surface.h"
18+
19+
#include <algorithm>
20+
#include <cstdarg>
21+
#include <cstdio>
22+
#include <map>
23+
#include <memory>
24+
#include <vector>
25+
26+
using Boxes = std::vector<gfx::Rect>;
27+
28+
struct WindowData {
29+
bool dragEnter;
30+
bool dragLeave;
31+
int drag;
32+
base::paths paths;
33+
os::SurfaceRef image;
34+
gfx::Point dragPosition;
35+
gfx::Rect dropZone;
36+
};
37+
38+
static WindowData windowData;
39+
40+
static void redraw_window(os::Window* window);
41+
42+
class DragTarget : public os::DragTarget {
43+
public:
44+
void dragEnter(os::DragEvent& ev) override {
45+
if (!windowData.dropZone.contains(ev.position()) || !ev.sourceSupports(os::DropOperation::Copy))
46+
ev.dropResult(os::DropOperation::None);
47+
else if (ev.sourceSupports(os::DropOperation::Copy))
48+
ev.dropResult(os::DropOperation::Copy);
49+
50+
windowData.dragEnter = true;
51+
windowData.dragLeave = false;
52+
windowData.drag = 0;
53+
windowData.dragPosition = ev.position();
54+
redraw_window(ev.target());
55+
ev.target()->invalidate();
56+
}
57+
void dragLeave(os::DragEvent& ev) override {
58+
windowData.dragEnter = false;
59+
windowData.dragLeave = true;
60+
windowData.dragPosition = ev.position();
61+
redraw_window(ev.target());
62+
ev.target()->invalidate();
63+
}
64+
void drag(os::DragEvent& ev) override {
65+
++windowData.drag;
66+
windowData.dragPosition = ev.position();
67+
redraw_window(ev.target());
68+
ev.target()->invalidate();
69+
}
70+
void drop(os::DragEvent& ev) override {
71+
windowData.dragEnter = false;
72+
windowData.dragLeave = false;
73+
windowData.dragPosition = {0, 0};
74+
ev.acceptDrop(windowData.dropZone.contains(ev.position()));
75+
76+
if (ev.acceptDrop()) {
77+
if (ev.dataProvider()->contains(os::DragDataItemType::Paths))
78+
windowData.paths = ev.dataProvider()->getPaths();
79+
if (ev.dataProvider()->contains(os::DragDataItemType::Image))
80+
windowData.image = ev.dataProvider()->getImage();
81+
}
82+
83+
redraw_window(ev.target());
84+
ev.target()->invalidate();
85+
}
86+
};
87+
88+
static void redraw_window(os::Window* window)
89+
{
90+
os::Surface* s = window->surface();
91+
os::Paint paint;
92+
paint.color(gfx::rgba(0, 0, 0));
93+
s->drawRect(window->bounds(), paint);
94+
95+
paint.color(gfx::rgba(255, 255, 255));
96+
97+
char buf[256];
98+
int y = 12;
99+
std::snprintf(buf, sizeof(buf), "Drag Position = [%d, %d]", windowData.dragPosition.x, windowData.dragPosition.y);
100+
os::draw_text(s, nullptr, buf, gfx::Point(0, y), &paint);
101+
y += 12;
102+
std::snprintf(buf, sizeof(buf), "Drag Enter = %s", windowData.dragEnter ? "true" : "false");
103+
os::draw_text(s, nullptr, buf, gfx::Point(0, y), &paint);
104+
y += 12;
105+
std::snprintf(buf, sizeof(buf), "Drag = %d", windowData.drag);
106+
os::draw_text(s, nullptr, buf, gfx::Point(0, y), &paint);
107+
y += 12;
108+
std::snprintf(buf, sizeof(buf), "Drag Leave = %s", windowData.dragLeave ? "true" : "false");
109+
os::draw_text(s, nullptr, buf, gfx::Point(0, y), &paint);
110+
111+
if (!windowData.paths.empty()) {
112+
y += 12;
113+
std::snprintf(buf, sizeof(buf), "Paths = %lu", windowData.paths.size());
114+
os::draw_text(s, nullptr, buf, gfx::Point(0, y), &paint);
115+
for (const auto& path : windowData.paths) {
116+
y += 12;
117+
std::snprintf(buf, sizeof(buf), "%s", path.c_str());
118+
os::draw_text(s, nullptr, buf, gfx::Point(12, y), &paint);
119+
}
120+
}
121+
122+
if (windowData.image) {
123+
y += 12;
124+
s->drawSurface(windowData.image.get(), 0, y);
125+
}
126+
127+
paint.style(os::Paint::Style::Stroke);
128+
s->drawRect(window->bounds(), paint);
129+
130+
131+
auto zoneColor = gfx::rgba(100, 255, 100);
132+
auto textColor = zoneColor;
133+
if (windowData.dropZone.contains(windowData.dragPosition)){
134+
paint.style(os::Paint::Style::Fill);
135+
paint.color(zoneColor);
136+
s->drawRect(windowData.dropZone, paint);
137+
textColor = gfx::rgba(0, 0, 0);
138+
}
139+
140+
paint.color(zoneColor);
141+
paint.style(os::Paint::Style::Stroke);
142+
s->drawRect(windowData.dropZone, paint);
143+
144+
paint.color(textColor);
145+
paint.style(os::Paint::Style::Fill);
146+
os::draw_text(s, nullptr, "Drop here!", windowData.dropZone.center(), &paint, os::TextAlign::Center);
147+
}
148+
149+
static os::WindowRef create_window(const std::string& title,
150+
const os::WindowSpec& spec,
151+
os::DragTarget& dragTarget)
152+
{
153+
os::WindowRef newWindow = os::instance()->makeWindow(spec);
154+
newWindow->setCursor(os::NativeCursor::Arrow);
155+
newWindow->setTitle(title);
156+
newWindow->setVisible(true);
157+
newWindow->setDragTarget(&dragTarget);
158+
windowData.dropZone = {32, spec.frame().h - 64 - 40, 64, 64};
159+
redraw_window(newWindow.get());
160+
return newWindow;
161+
}
162+
163+
int app_main(int argc, char* argv[])
164+
{
165+
auto system = os::make_system();
166+
system->setAppMode(os::AppMode::GUI);
167+
system->handleWindowResize = redraw_window;
168+
169+
auto screen = system->mainScreen();
170+
os::WindowSpec spec;
171+
DragTarget dragTarget;
172+
spec.titled(true);
173+
spec.position(os::WindowSpec::Position::Frame);
174+
auto frame = screen->workarea()/2;
175+
spec.frame(frame);
176+
spec.screen(screen);
177+
os::WindowRef window = create_window("Drag & Drop example", spec, dragTarget);
178+
179+
bool running = true;
180+
181+
system->finishLaunching();
182+
system->activateApp();
183+
184+
os::EventQueue* queue = system->eventQueue();
185+
os::Event ev;
186+
while (running) {
187+
queue->getEvent(ev);
188+
189+
switch (ev.type()) {
190+
191+
case os::Event::CloseApp:
192+
case os::Event::CloseWindow:
193+
running = false;
194+
break;
195+
196+
case os::Event::ResizeWindow:
197+
redraw_window(ev.window().get());
198+
ev.window()->invalidate();
199+
break;
200+
201+
default:
202+
// Do nothing
203+
break;
204+
}
205+
}
206+
207+
return 0;
208+
}

os/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ set(LAF_OS_SOURCES
1313
if(WIN32)
1414
list(APPEND LAF_OS_SOURCES
1515
win/color_space.cpp
16+
win/dnd.cpp
1617
win/event_queue.cpp
1718
win/keys.cpp
1819
win/native_dialogs.cpp

os/dnd.h

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// LAF OS Library
2+
// Copyright (C) 2024 Igara Studio S.A.
3+
//
4+
// This file is released under the terms of the MIT license.
5+
// Read LICENSE.txt for more information.
6+
7+
#ifndef OS_DND_H_INCLUDED
8+
#define OS_DND_H_INCLUDED
9+
#pragma once
10+
11+
#include "base/paths.h"
12+
#include "base/debug.h"
13+
#include "gfx/point.h"
14+
#include "os/surface.h"
15+
16+
#include <memory>
17+
18+
#pragma push_macro("None")
19+
#undef None // Undefine the X11 None macro
20+
21+
namespace os {
22+
23+
class Window;
24+
25+
// Operations that can be supported by source and target windows in a drag
26+
// and drop operation.
27+
enum DropOperation {
28+
None = 0,
29+
Copy = 1,
30+
Move = 2,
31+
Link = 4,
32+
Any = Copy | Move | Link,
33+
};
34+
35+
// Types of representations supported for each DragDataItem.
36+
enum class DragDataItemType {
37+
Paths,
38+
Image,
39+
};
40+
41+
// Interface to get dragged data from the platform's implementation.
42+
class DragDataProvider {
43+
public:
44+
virtual ~DragDataProvider() {}
45+
virtual base::paths getPaths() = 0;
46+
virtual SurfaceRef getImage() = 0;
47+
virtual bool contains(DragDataItemType type) { return false; }
48+
};
49+
50+
class DragEvent {
51+
public:
52+
DragEvent(os::Window* target,
53+
DropOperation supportedOperations,
54+
const gfx::Point& dragPosition,
55+
DragDataProvider* dataProvider)
56+
: m_target(target)
57+
, m_supportedOperations(supportedOperations)
58+
, m_position(dragPosition)
59+
, m_dataProvider(dataProvider) {}
60+
61+
// Destination window of the DragEvent.
62+
os::Window* target() const { return m_target; }
63+
DropOperation dropResult() const { return m_dropResult; }
64+
DropOperation supportedOperations() const { return m_supportedOperations; }
65+
bool acceptDrop() const { return m_acceptDrop; }
66+
const gfx::Point& position() { return m_position; }
67+
DragDataProvider* dataProvider() { return m_dataProvider; }
68+
69+
// Sets what will be the outcome of dropping the dragged data when it is
70+
// accepted by the target window. Only one of the enum values should be passed,
71+
// do not combine values using bitwise operators.
72+
void dropResult(DropOperation operation) { m_dropResult = operation; }
73+
// Set this to true when the dropped data was accepted/processed by the
74+
// target window, or set to false otherwise.
75+
void acceptDrop(bool value) { m_acceptDrop = value; }
76+
77+
bool sourceSupports(DropOperation op) { return (m_supportedOperations & op) == op; }
78+
79+
private:
80+
os::Window* m_target;
81+
DropOperation m_dropResult = DropOperation::Copy;
82+
// Bitwise OR of the operations supported by the drag and drop source.
83+
DropOperation m_supportedOperations;
84+
bool m_acceptDrop = false;
85+
gfx::Point m_position;
86+
DragDataProvider* m_dataProvider;
87+
};
88+
89+
class DragTarget {
90+
public:
91+
virtual ~DragTarget() {};
92+
93+
// Called when a drag action enters a window that supports DnD. The
94+
// DragEvent::dropResult must be set to the operation that is expected
95+
// to occur by the target window once the drop is accepted.
96+
virtual void dragEnter(os::DragEvent& ev) {}
97+
// Called when the dragged data exits the window that supports DnD.
98+
virtual void dragLeave(os::DragEvent& ev) {}
99+
virtual void drag(os::DragEvent& ev) {}
100+
virtual void drop(os::DragEvent& ev) {}
101+
};
102+
103+
104+
} // namespace os
105+
106+
#pragma pop_macro("None")
107+
108+
#endif

os/none/os.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ class NoneSystem : public System {
4242
Ref<Window> makeWindow(const WindowSpec& spec) override { return nullptr; }
4343
Ref<Surface> makeSurface(int width, int height,
4444
const os::ColorSpaceRef& colorSpace) override { return nullptr; }
45+
Ref<Surface> makeSurface(int width, int height,
46+
const os::SurfaceFormatData& sf,
47+
const unsigned char* data = nullptr) override { return nullptr; }
4548
Ref<Surface> makeRgbaSurface(int width, int height,
4649
const os::ColorSpaceRef& colorSpace) override { return nullptr; }
4750
Ref<Surface> loadSurface(const char* filename) override { return nullptr; }

os/os.h

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "gfx/region.h"
1717
#include "gfx/size.h"
1818
#include "os/capabilities.h"
19+
#include "os/dnd.h"
1920
#include "os/draw_text.h"
2021
#include "os/error.h"
2122
#include "os/event.h"

0 commit comments

Comments
 (0)