Skip to content

Commit 529557a

Browse files
committed
Add drag-and-drop support and multi-file open
Introduce OS-level GLFW drag-and-drop integration and multi-file open support. - Add a tiny dnd_glfw helper (LICENSE, README, header, macOS implementation) to provide unified drag enter/over/leave/drop callbacks across platforms. - Add imiv_drag_drop.{h,cpp}: install/uninstall dnd callbacks, process pending drops, and render a drag-overlay in ImGui. - Wire drag-and-drop into the app: install/uninstall on window lifecycle and process drops each frame. - Extend file dialog API to support selecting multiple files (open_image_files) and update callers to append multiple paths into viewer state. - Update viewer/actions to manage loaded_image_paths (add/append/remove/sort/pick) and adapt slide-show, toggle, close, and navigation logic to use the new list. - Update CMakeLists to compile imiv_drag_drop.cpp and include the macOS dnd_glfw implementation and link Cocoa when available. These changes enable dragging files from the OS into the viewer and selecting multiple files from the open dialog, and refactor image-list handling to support the new workflows.
1 parent 99441bb commit 529557a

17 files changed

Lines changed: 1470 additions & 141 deletions

src/imiv/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ set (_imiv_core_sources
6666
imiv_app.cpp
6767
imiv_aux_windows.cpp
6868
imiv_capture.cpp
69+
imiv_drag_drop.cpp
6970
imiv_file_dialog.cpp
7071
imiv_frame.cpp
7172
imiv_image_view.cpp
@@ -82,6 +83,11 @@ set (_imiv_core_sources
8283
imiv_test_engine.cpp
8384
imiv_main.cpp)
8485

86+
if (APPLE)
87+
list (APPEND _imiv_core_sources
88+
external/dnd_glfw/dnd_glfw_macos.mm)
89+
endif ()
90+
8591
set (_imiv_shader_src "${CMAKE_CURRENT_SOURCE_DIR}/shaders/imiv_upload_to_rgba.comp")
8692
set (_imiv_preview_vert_src "${CMAKE_CURRENT_SOURCE_DIR}/shaders/imiv_preview.vert")
8793
set (_imiv_preview_frag_src "${CMAKE_CURRENT_SOURCE_DIR}/shaders/imiv_preview.frag")
@@ -317,6 +323,10 @@ if (TARGET imiv)
317323
endif ()
318324
if (APPLE)
319325
target_compile_definitions (imiv PRIVATE IMIV_BACKEND_METAL_GLFW=1)
326+
find_library (OIIO_IMIV_COCOA_FRAMEWORK Cocoa)
327+
if (OIIO_IMIV_COCOA_FRAMEWORK)
328+
target_link_libraries (imiv PRIVATE ${OIIO_IMIV_COCOA_FRAMEWORK})
329+
endif ()
320330
else ()
321331
target_compile_definitions (imiv PRIVATE IMIV_BACKEND_VULKAN_GLFW=1
322332
$<$<CONFIG:Debug>:IMIV_VULKAN_VALIDATION=1>)

src/imiv/external/dnd_glfw/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2025-2025 Erium Vladlen
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
# dnd_glfw – GLFW Drag-and-Drop Helper
2+
3+
`dnd_glfw` is a very small helper around GLFW that exposes a simple, C‑style drag‑and‑drop callback interface. It is designed for applications that already use GLFW for their main window (e.g. Dear ImGui with the GLFW/OpenGL backend) and want better control over file drops and drag‑hover overlays.
4+
5+
The implementation is intentionally minimal and C‑style C++: no heavy abstractions, only basic C++17 containers and RAII where needed.
6+
7+
## Supported platforms
8+
9+
- **Windows**
10+
- Uses the GLFW window handle (`HWND`) and registers an OLE `IDropTarget`.
11+
- Provides real OS‑level events for file drags:
12+
- `dragEnter`, `dragOver`, `dragLeave`, `dragCancel`.
13+
- File paths are still obtained via GLFW’s existing drop callback semantics and forwarded to your `drop` handler.
14+
15+
- **macOS**
16+
- Uses Cocoa drag‑and‑drop via an `NSView<NSDraggingDestination>` overlay on the GLFW window’s content view.
17+
- Provides OS‑level `dragEntered`, `draggingUpdated`, `draggingExited`, and `performDragOperation` mapped to:
18+
- `dragEnter`, `dragOver`, `dragLeave`, `dragCancel`, `drop`.
19+
- File paths are read from the pasteboard and passed to your `drop` handler as UTF‑8 strings.
20+
21+
- **Linux / X11 / Wayland**
22+
- Relies on GLFW’s internal Xdnd / DnD handling.
23+
- Wraps `glfwSetDropCallback` and exposes file paths and cursor position via the `drop` callback.
24+
- OS‑level drag‑enter / drag‑hover / drag‑leave for external file drags are **not** available via GLFW’s public API, so `dragEnter` / `dragOver` / `dragLeave` / `dragCancel` are not fired for system file drags on Linux.
25+
26+
In practice this means:
27+
28+
- You get full overlay‑style drag feedback on Windows and macOS.
29+
- On Linux you still get correct file drops with a consistent API, but no OS‑level “drag in progress” notifications.
30+
31+
## API overview
32+
33+
Header: `dnd_glfw/dnd_glfw.h`
34+
35+
```cpp
36+
namespace dnd_glfw {
37+
38+
enum class PayloadKind {
39+
Unknown = 0,
40+
Files = 1
41+
};
42+
43+
struct DragEvent {
44+
double x;
45+
double y;
46+
PayloadKind kind;
47+
};
48+
49+
struct DropEvent {
50+
double x;
51+
double y;
52+
PayloadKind kind;
53+
std::vector<std::string> paths; // UTF‑8 file paths
54+
};
55+
56+
struct Callbacks {
57+
void (*dragEnter)(GLFWwindow*, const DragEvent&, void* userData);
58+
void (*dragOver)(GLFWwindow*, const DragEvent&, void* userData);
59+
void (*dragLeave)(GLFWwindow*, void* userData);
60+
void (*drop)(GLFWwindow*, const DropEvent&, void* userData);
61+
void (*dragCancel)(GLFWwindow*, void* userData);
62+
};
63+
64+
bool init(GLFWwindow* window, const Callbacks& callbacks, void* userData);
65+
void shutdown(GLFWwindow* window);
66+
67+
} // namespace dnd_glfw
68+
```
69+
70+
All callbacks are optional; set a pointer to `nullptr` to ignore that event type.
71+
72+
## Typical usage
73+
74+
1. **Create your GLFW window as usual** and make it current.
75+
2. **Register drag‑and‑drop callbacks** once, after the window exists:
76+
77+
```cpp
78+
static void onDragEnter(GLFWwindow* window, const dnd_glfw::DragEvent& e, void* user)
79+
{
80+
(void)window;
81+
AppState* app = static_cast<AppState*>(user);
82+
if (!app) return;
83+
84+
if (e.kind == dnd_glfw::PayloadKind::Files) {
85+
app->dragOverlayActive = true;
86+
}
87+
}
88+
89+
static void onDrop(GLFWwindow* window, const dnd_glfw::DropEvent& e, void* user)
90+
{
91+
(void)window;
92+
AppState* app = static_cast<AppState*>(user);
93+
if (!app) return;
94+
95+
std::lock_guard<std::mutex> lock(app->dropMutex);
96+
app->pendingDrops = e.paths;
97+
app->dragOverlayActive = false;
98+
}
99+
100+
static void onDragLeave(GLFWwindow* window, void* user)
101+
{
102+
(void)window;
103+
AppState* app = static_cast<AppState*>(user);
104+
if (!app) return;
105+
app->dragOverlayActive = false;
106+
}
107+
108+
// ...
109+
110+
dnd_glfw::Callbacks cbs{};
111+
cbs.dragEnter = &onDragEnter;
112+
cbs.dragOver = nullptr; // not needed for simple overlay
113+
cbs.dragLeave = &onDragLeave;
114+
cbs.drop = &onDrop;
115+
cbs.dragCancel = &onDragLeave; // treat cancel as leave
116+
117+
dnd_glfw::init(window, cbs, &app);
118+
```
119+
120+
3. **Use your own flag to drive an overlay** in ImGui (or another GUI):
121+
122+
```cpp
123+
if (app.dragOverlayActive) {
124+
// Draw a full‑window dim background and center text
125+
}
126+
```
127+
128+
4. **On shutdown**, before destroying the GLFW window:
129+
130+
```cpp
131+
dnd_glfw::shutdown(window);
132+
glfwDestroyWindow(window);
133+
```
134+
135+
## Integration details
136+
137+
- **GLFW drop callback chaining**
138+
- On Windows and Linux, `dnd_glfw::init` installs an internal `glfwSetDropCallback` handler and remembers any previously registered callback.
139+
- When a drop occurs, the internal handler:
140+
- Builds a `DropEvent` (cursor position, `PayloadKind::Files`, file paths).
141+
- Calls your `Callbacks::drop` (if provided).
142+
- Forwards the event to the previous GLFW drop callback (if any).
143+
- On macOS, GLFW’s own DnD is handled internally; `dnd_glfw` uses Cocoa APIs instead and does **not** override `glfwSetDropCallback`.
144+
145+
- **Windows / OLE**
146+
- Uses `glfwGetWin32Window(window)` to get the `HWND` and registers an `IDropTarget` implementation.
147+
- Only CF_HDROP (file paths) is accepted; other payloads are ignored.
148+
- `DragEnter` / `DragOver` use client‑area coordinates (converted from screen) for the `DragEvent::x`/`y` fields.
149+
- The actual file paths are still delivered via GLFW’s drop events to keep behavior consistent with GLFW/X11 and Cocoa.
150+
151+
- **macOS / Cocoa**
152+
- Obtains the native `NSWindow*` for the GLFW window with `glfwGetCocoaWindow`.
153+
- Adds a transparent `NSView` subclass (`DndGlfwDragView`) above the content view and registers it for file URL types.
154+
- Uses the system pasteboard to extract file URLs and forwards them as UTF‑8 paths in `DropEvent::paths`.
155+
156+
- **Linux**
157+
- Depends on GLFW’s own X11/Wayland drag‑and‑drop implementation.
158+
- Only the `drop` callback is meaningful for OS‑level file drags; hover/enter/leave for external drags are not exposed by GLFW and therefore not emitted by `dnd_glfw`.
159+
160+
## CMake integration
161+
162+
The top‑level `CMakeLists.txt` for this project adds the helper as a static library:
163+
164+
```cmake
165+
if(APPLE)
166+
add_library(dnd_glfw STATIC
167+
dnd_glfw_macos.mm
168+
)
169+
else()
170+
add_library(dnd_glfw STATIC
171+
dnd_glfw_dummy.cpp
172+
)
173+
endif()
174+
175+
target_include_directories(dnd_glfw
176+
PUBLIC
177+
${CMAKE_CURRENT_SOURCE_DIR}
178+
)
179+
180+
if(APPLE)
181+
target_link_libraries(dnd_glfw PUBLIC "-framework Cocoa")
182+
endif()
183+
```
184+
185+
To use it from another target (like the GUI):
186+
187+
```cmake
188+
add_subdirectory(dnd_glfw)
189+
190+
target_link_libraries(meshrepair_gui
191+
PRIVATE
192+
dnd_glfw
193+
# other libs: glfw, OpenGL, ImGui, etc.
194+
)
195+
196+
if(WIN32)
197+
target_link_libraries(meshrepair_gui PRIVATE ole32)
198+
endif()
199+
```
200+
201+
The macOS Cocoa framework and Windows `ole32` import library are wired automatically via these targets.
202+
203+
## Notes and limitations
204+
205+
- Only a small subset of drag‑and‑drop is implemented: **file paths** dragged from the OS (Explorer/Finder/desktop/file manager) into your GLFW window.
206+
- No attempt is made to support arbitrary MIME types or cross‑process data beyond file URLs / paths.
207+
- On Linux, GLFW owns the Xdnd handling; `dnd_glfw` intentionally does **not** duplicate Xdnd parsing to avoid conflicting with GLFW’s internal event loop.
208+
- The internal state is stored in a small global vector keyed by `GLFWwindow*`. It assumes a small number of windows (typically one) and is not optimized for hundreds of windows. This matches the intended usage for a single ImGui main window.

0 commit comments

Comments
 (0)