Skip to content

Commit e13e148

Browse files
committed
Add WindowDragSession and WindowManager::GetWindowAtPoint
The building blocks for tear-off tabs and dockable panels: - WindowDragSession follows the primary mouse button across the whole screen until it is released, optionally moving a window so an anchor point stays under the cursor. Start() on an active session retargets it within the same gesture. Emits Moved/Ended/Cancelled events. - WindowManager::GetWindowAtPoint() hit-tests the window stack and returns this application's frontmost window at a screen point, looking through an excluded window such as the one being dragged. Implemented for macOS, Windows, and Linux (X11); stubs elsewhere. Also fix Linux Window(void*) so wrapping a GdkWindow reports its GtkWindow and a window wrapped for the first time gets an ID, as on the other platforms.
1 parent 59b2252 commit e13e148

25 files changed

Lines changed: 1419 additions & 4 deletions

‎CMakeLists.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ add_subdirectory(examples/tray_icon_c_example)
3131
add_subdirectory(examples/url_opener_c_example)
3232
add_subdirectory(examples/window_c_example)
3333
add_subdirectory(examples/window_example)
34+
add_subdirectory(examples/window_drag_session_example)
3435
add_subdirectory(examples/desktop_features_example)
3536

3637
if(BUILD_TESTING)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
cmake_minimum_required(VERSION 3.10)
2+
3+
project(window_drag_session_example VERSION 0.0.1 LANGUAGES CXX)
4+
5+
# Set C++ standard
6+
set(CMAKE_CXX_STANDARD 17)
7+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
8+
9+
# Add example program
10+
add_executable(window_drag_session_example
11+
"main.cpp"
12+
)
13+
14+
# Link main library
15+
target_link_libraries(window_drag_session_example PRIVATE nativeapi)
16+
17+
# Set example program properties
18+
set_target_properties(window_drag_session_example PROPERTIES
19+
OUTPUT_NAME "window_drag_session_example"
20+
)
21+
22+
# Set example program compile options (macOS only)
23+
if(APPLE)
24+
set_source_files_properties("main.cpp"
25+
PROPERTIES
26+
COMPILE_FLAGS "-x objective-c++"
27+
)
28+
endif()
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
#include <iostream>
2+
#include "nativeapi.h"
3+
4+
using nativeapi::Point;
5+
using nativeapi::RunApp;
6+
using nativeapi::Window;
7+
using nativeapi::WindowDragCancelledEvent;
8+
using nativeapi::WindowDragEndedEvent;
9+
using nativeapi::WindowDragMovedEvent;
10+
using nativeapi::WindowDragSession;
11+
using nativeapi::WindowFocusedEvent;
12+
using nativeapi::WindowManager;
13+
14+
// A tear-off panel without any UI toolkit.
15+
//
16+
// There is no widget to press on, so a window becoming focused stands in for
17+
// "mouse down on the panel": press inside the Panel window (not on its title
18+
// bar, which starts the system's own window drag) and keep dragging to carry
19+
// it, drop it onto the Dock window to dock it, and press inside the Dock window
20+
// to tear the panel off again.
21+
int main() {
22+
WindowManager& window_manager = WindowManager::GetInstance();
23+
24+
auto dock = std::make_shared<Window>();
25+
dock->SetTitle("Dock - drop the panel here");
26+
dock->SetSize({640, 420}, false);
27+
dock->Center();
28+
29+
auto panel = std::make_shared<Window>();
30+
panel->SetTitle("Panel - drag me");
31+
panel->SetSize({260, 180}, false);
32+
Point dock_position = dock->GetPosition();
33+
panel->SetPosition({dock_position.x + 680, dock_position.y});
34+
panel->Show();
35+
36+
auto session = std::make_shared<WindowDragSession>();
37+
bool docked = false;
38+
bool over_dock = false;
39+
// Hiding the panel on drop hands focus to the dock; that is not a press.
40+
bool dropping = false;
41+
42+
auto contains = [](const nativeapi::Rectangle& r, const Point& p) {
43+
return p.x >= r.x && p.y >= r.y && p.x < r.x + r.width && p.y < r.y + r.height;
44+
};
45+
46+
window_manager.AddListener<WindowFocusedEvent>([&](const WindowFocusedEvent& event) {
47+
if (session->IsActive() || dropping) {
48+
return;
49+
}
50+
Point cursor = nativeapi::DisplayManager::GetInstance().GetCursorPosition();
51+
52+
if (event.GetWindowId() == panel->GetId() && !docked) {
53+
// Keep the point that was pressed under the cursor.
54+
Point position = panel->GetPosition();
55+
session->Start(panel, {cursor.x - position.x, cursor.y - position.y});
56+
} else if (event.GetWindowId() == dock->GetId() && docked &&
57+
contains(dock->GetContentBounds(), cursor)) {
58+
// Tear off: bring the panel back under the cursor, grabbed by its title bar.
59+
Point anchor{130, 12};
60+
panel->SetPosition({cursor.x - anchor.x, cursor.y - anchor.y});
61+
panel->Show();
62+
docked = false;
63+
session->Start(panel, anchor);
64+
dock->SetTitle("Dock - drop the panel here");
65+
std::cout << "Panel torn off" << std::endl;
66+
}
67+
});
68+
69+
session->AddListener<WindowDragMovedEvent>([&](const WindowDragMovedEvent& event) {
70+
auto target = window_manager.GetWindowAtPoint(event.GetCursorPosition(), panel->GetId());
71+
bool now_over_dock = target && target->GetId() == dock->GetId();
72+
if (now_over_dock != over_dock) {
73+
over_dock = now_over_dock;
74+
panel->SetOpacity(over_dock ? 0.5f : 1.0f);
75+
std::cout << (over_dock ? "Over the dock: release to dock" : "Left the dock") << std::endl;
76+
}
77+
});
78+
79+
session->AddListener<WindowDragEndedEvent>([&](const WindowDragEndedEvent& event) {
80+
Point cursor = event.GetCursorPosition();
81+
std::cout << "Drag ended at (" << cursor.x << ", " << cursor.y << ")" << std::endl;
82+
panel->SetOpacity(1.0f);
83+
if (over_dock) {
84+
over_dock = false;
85+
docked = true;
86+
dropping = true;
87+
panel->Hide();
88+
dropping = false;
89+
dock->SetTitle("Dock - panel docked, press here to tear it off");
90+
std::cout << "Panel docked" << std::endl;
91+
}
92+
});
93+
94+
session->AddListener<WindowDragCancelledEvent>(
95+
[&](const WindowDragCancelledEvent&) { std::cout << "Drag cancelled" << std::endl; });
96+
97+
std::cout << "Press inside the Panel window and drag it onto the Dock window to dock it."
98+
<< std::endl;
99+
return RunApp(dock);
100+
}

‎include/nativeapi.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "../src/tray_manager.h"
3333
#include "../src/url_opener.h"
3434
#include "../src/window.h"
35+
#include "../src/window_drag_session.h"
3536
#include "../src/window_manager.h"
3637
#endif
3738

@@ -65,4 +66,5 @@
6566
#include "../src/capi/tray_manager_c.h"
6667
#include "../src/capi/url_opener_c.h"
6768
#include "../src/capi/window_c.h"
69+
#include "../src/capi/window_drag_session_c.h"
6870
#include "../src/capi/window_manager_c.h"

‎src/capi/window_drag_session_c.cpp‎

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
// AUTO-GENERATED. DO NOT EDIT.
2+
// Any manual changes WILL BE LOST when this file is regenerated.
3+
4+
#include "window_drag_session_c.h"
5+
6+
#include <cstdio>
7+
#include <memory>
8+
#include <new>
9+
#include <optional>
10+
#include <string>
11+
#include <utility>
12+
#include <vector>
13+
14+
#include "string_utils_c.h"
15+
#include "../foundation/handle_table.h"
16+
#include "../foundation/geometry.h"
17+
#include "geometry_c.h"
18+
#include "../window.h"
19+
#include "window_c.h"
20+
#include "../window_drag_session.h"
21+
22+
native_window_drag_session_t native_window_drag_session_create(void) {
23+
try {
24+
return nativeapi::HandleTable::GetInstance().Insert(
25+
std::make_shared<nativeapi::WindowDragSession>());
26+
} catch (...) {
27+
fprintf(stderr, "[nativeapi] %s: unexpected exception\n", "native_window_drag_session_create");
28+
return 0;
29+
}
30+
}
31+
32+
bool native_window_drag_session_start(native_window_drag_session_t window_drag_session, native_window_t window, native_point_t anchor) {
33+
auto self = nativeapi::HandleTable::GetInstance().Resolve<nativeapi::WindowDragSession>(window_drag_session);
34+
if (!self) {
35+
return false;
36+
}
37+
try {
38+
auto window_cpp = nativeapi::HandleTable::GetInstance().Resolve<nativeapi::Window>(window);
39+
auto anchor_cpp = to_cpp_point(anchor);
40+
return self->Start(window_cpp, anchor_cpp);
41+
} catch (...) {
42+
fprintf(stderr, "[nativeapi] %s: unexpected exception\n", "native_window_drag_session_start");
43+
return false;
44+
}
45+
}
46+
47+
void native_window_drag_session_cancel(native_window_drag_session_t window_drag_session) {
48+
auto self = nativeapi::HandleTable::GetInstance().Resolve<nativeapi::WindowDragSession>(window_drag_session);
49+
if (!self) {
50+
return;
51+
}
52+
try {
53+
self->Cancel();
54+
return;
55+
} catch (...) {
56+
fprintf(stderr, "[nativeapi] %s: unexpected exception\n", "native_window_drag_session_cancel");
57+
return;
58+
}
59+
}
60+
61+
bool native_window_drag_session_is_active(native_window_drag_session_t window_drag_session) {
62+
auto self = nativeapi::HandleTable::GetInstance().Resolve<nativeapi::WindowDragSession>(window_drag_session);
63+
if (!self) {
64+
return false;
65+
}
66+
try {
67+
return self->IsActive();
68+
} catch (...) {
69+
fprintf(stderr, "[nativeapi] %s: unexpected exception\n", "native_window_drag_session_is_active");
70+
return false;
71+
}
72+
}
73+
74+
native_window_id_t native_window_drag_session_get_window_id(native_window_drag_session_t window_drag_session) {
75+
auto self = nativeapi::HandleTable::GetInstance().Resolve<nativeapi::WindowDragSession>(window_drag_session);
76+
if (!self) {
77+
return 0;
78+
}
79+
try {
80+
return self->GetWindowId();
81+
} catch (...) {
82+
fprintf(stderr, "[nativeapi] %s: unexpected exception\n", "native_window_drag_session_get_window_id");
83+
return 0;
84+
}
85+
}
86+
87+
native_point_t native_window_drag_session_get_anchor(native_window_drag_session_t window_drag_session) {
88+
auto self = nativeapi::HandleTable::GetInstance().Resolve<nativeapi::WindowDragSession>(window_drag_session);
89+
if (!self) {
90+
native_point_t result = {};
91+
return result;
92+
}
93+
try {
94+
const auto cpp_result = self->GetAnchor();
95+
return to_c_point(cpp_result);
96+
} catch (...) {
97+
fprintf(stderr, "[nativeapi] %s: unexpected exception\n", "native_window_drag_session_get_anchor");
98+
native_point_t result = {};
99+
return result;
100+
}
101+
}
102+
103+
void native_window_drag_session_free(native_window_drag_session_t window_drag_session) {
104+
// The table invalidates the handle itself, so releasing an unknown or
105+
// already-released one is a no-op rather than a double free.
106+
nativeapi::HandleTable::GetInstance().Release(window_drag_session);
107+
}
108+
109+
native_listener_id_t native_window_drag_session_add_listener(native_window_drag_session_t window_drag_session, native_window_drag_event_callback_t callback, void* user_data) {
110+
if (!callback) {
111+
return 0;
112+
}
113+
auto self = nativeapi::HandleTable::GetInstance().Resolve<nativeapi::WindowDragSession>(window_drag_session);
114+
if (!self) {
115+
return 0;
116+
}
117+
try {
118+
return static_cast<native_listener_id_t>(self->AddListener<nativeapi::WindowDragEvent>(
119+
[callback, user_data](const nativeapi::WindowDragEvent& event) {
120+
native_window_drag_event_t c_event = {};
121+
if (!to_c_window_drag_event(event, &c_event)) {
122+
return;
123+
}
124+
callback(&c_event, user_data);
125+
free_c_window_drag_event(&c_event);
126+
}));
127+
} catch (...) {
128+
return 0;
129+
}
130+
}
131+
132+
bool native_window_drag_session_remove_listener(native_window_drag_session_t window_drag_session, native_listener_id_t listener_id) {
133+
auto self = nativeapi::HandleTable::GetInstance().Resolve<nativeapi::WindowDragSession>(window_drag_session);
134+
if (!self) {
135+
return false;
136+
}
137+
try {
138+
return self->RemoveListener(static_cast<size_t>(listener_id));
139+
} catch (...) {
140+
return false;
141+
}
142+
}
143+
144+
bool to_c_window_drag_event(const nativeapi::WindowDragEvent& event, native_window_drag_event_t* out) {
145+
if (!out) {
146+
return false;
147+
}
148+
*out = native_window_drag_event_t{};
149+
out->window_id = event.GetWindowId();
150+
out->cursor_position = to_c_point(event.GetCursorPosition());
151+
if (const auto* typed = dynamic_cast<const nativeapi::WindowDragMovedEvent*>(&event)) {
152+
out->type = NATIVE_WINDOW_DRAG_EVENT_TYPE_MOVED;
153+
(void)typed;
154+
return true;
155+
}
156+
if (const auto* typed = dynamic_cast<const nativeapi::WindowDragEndedEvent*>(&event)) {
157+
out->type = NATIVE_WINDOW_DRAG_EVENT_TYPE_ENDED;
158+
(void)typed;
159+
return true;
160+
}
161+
if (const auto* typed = dynamic_cast<const nativeapi::WindowDragCancelledEvent*>(&event)) {
162+
out->type = NATIVE_WINDOW_DRAG_EVENT_TYPE_CANCELLED;
163+
(void)typed;
164+
return true;
165+
}
166+
return false;
167+
}
168+
169+
void free_c_window_drag_event(native_window_drag_event_t* value) {
170+
if (!value) {
171+
return;
172+
}
173+
}
174+

0 commit comments

Comments
 (0)