gtk: support cross-tree surface drag and drop

This commit is contained in:
Leah Amelia Chen
2026-01-24 02:08:41 +08:00
parent 1d053bd6ea
commit 98fae16a05
2 changed files with 46 additions and 31 deletions

View File

@@ -387,11 +387,6 @@ pub const SplitTree = extern struct {
return true;
}
pub const MoveSplitError = Allocator.Error || error{
SourceNotFound,
TargetNotFound,
};
/// Move the source split onto the target split in a given direction.
/// The target split must be located within this tree.
pub fn moveSplit(
@@ -399,21 +394,27 @@ pub const SplitTree = extern struct {
source: *Surface,
target: *Surface,
dir: Surface.Tree.Split.Direction,
) MoveSplitError!void {
) Allocator.Error!void {
const alloc = Application.default().allocator();
const tree = self.getTree() orelse return;
const source_handle = tree.locate(source) orelse return error.SourceNotFound;
const target_tree = self.getTree() orelse return;
// This really shouldn't fail, but just in case
const target_handle = tree.locate(target) orelse return error.TargetNotFound;
const target_handle = target_tree.locate(target) orelse {
log.warn("target is not placed in a split tree", .{});
return;
};
// TODO: is it perhaps possible to condense all of this
// into one atomic operation?
// Try to find the source within the current tree.
// If it exists, then it's a local move; otherwise the logic gets more
// complicated
const source_handle = target_tree.locate(source);
// First add the source into this tree.
// We have to do this no matter what
var branch = try Surface.Tree.init(alloc, source);
defer branch.deinit();
var after_split = try tree.split(
var after_split = try target_tree.split(
alloc,
target_handle,
dir,
@@ -422,10 +423,36 @@ pub const SplitTree = extern struct {
);
defer after_split.deinit();
var after_remove = try after_split.remove(alloc, source_handle);
defer after_remove.deinit();
if (source_handle) |handle| {
// Happy path: source and target are located within the same tree.
// In that case, we just remove the existing source from this tree
self.setTree(&after_remove);
var after_remove = try after_split.remove(alloc, handle);
defer after_remove.deinit();
self.setTree(&after_remove);
} else {
// :( Cross-tree moves are a bit more complicated.
// TODO: Find a better way to access the split tree from here
const source_tree_widget = ext.getAncestor(
SplitTree,
source.as(gtk.Widget),
) orelse {
log.warn("source is not placed in a split tree", .{});
return;
};
const source_tree = source_tree_widget.getTree() orelse return;
// Remove the source from its own tree
const handle = source_tree.locate(source) orelse return;
var new_source_tree = try source_tree.remove(alloc, handle);
defer new_source_tree.deinit();
// Finally, set the final tree structures for both tree widgets
source_tree_widget.setTree(&new_source_tree);
self.setTree(&after_split);
}
}
fn disconnectSurfaceHandlers(self: *Self) void {

View File

@@ -3773,21 +3773,9 @@ pub const Surface = extern struct {
const dir = self.calcDropDirection(x, y);
st.moveSplit(from, self, dir) catch |err| {
switch (err) {
error.OutOfMemory => log.warn("out of memory", .{}),
// FIXME: implement this!
error.SourceNotFound => log.debug(
"cross-tree drag-and-drop not yet supported",
.{},
),
error.TargetNotFound => log.warn(
"can't seem to find surface in tree - this shouldn't happen!",
.{},
),
}
return;
};
// The only error that could happen here is an OOM,
// and in that case we're already milliseconds away from crashing, so...
st.moveSplit(from, self, dir) catch return;
// Clean up overlay state
self.setDropOverlayDirection(null);