apprt/gtk-ng: spatial navigation
parent
70d48d03a5
commit
5a01877c77
|
|
@ -1675,7 +1675,10 @@ const Action = struct {
|
||||||
return tree.goto(switch (to) {
|
return tree.goto(switch (to) {
|
||||||
.previous => .previous_wrapped,
|
.previous => .previous_wrapped,
|
||||||
.next => .next_wrapped,
|
.next => .next_wrapped,
|
||||||
else => @panic("TODO"),
|
.up => .{ .spatial = .up },
|
||||||
|
.down => .{ .spatial = .down },
|
||||||
|
.left => .{ .spatial = .left },
|
||||||
|
.right => .{ .spatial = .right },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -263,7 +263,18 @@ pub const SplitTree = extern struct {
|
||||||
pub fn goto(self: *Self, to: Surface.Tree.Goto) bool {
|
pub fn goto(self: *Self, to: Surface.Tree.Goto) bool {
|
||||||
const tree = self.getTree() orelse return false;
|
const tree = self.getTree() orelse return false;
|
||||||
const active = self.getActiveSurfaceHandle() orelse return false;
|
const active = self.getActiveSurfaceHandle() orelse return false;
|
||||||
const target = tree.goto(active, to) orelse return false;
|
const target = if (tree.goto(
|
||||||
|
Application.default().allocator(),
|
||||||
|
active,
|
||||||
|
to,
|
||||||
|
)) |handle_|
|
||||||
|
handle_ orelse return false
|
||||||
|
else |err| switch (err) {
|
||||||
|
// Nothing we can do in this scenario. This is highly unlikely
|
||||||
|
// since split trees don't use that much memory. The application
|
||||||
|
// is probably about to crash in other ways.
|
||||||
|
error.OutOfMemory => return false,
|
||||||
|
};
|
||||||
|
|
||||||
// If we aren't changing targets then we did nothing.
|
// If we aren't changing targets then we did nothing.
|
||||||
if (active == target) return false;
|
if (active == target) return false;
|
||||||
|
|
@ -594,8 +605,10 @@ pub const SplitTree = extern struct {
|
||||||
// its the next.
|
// its the next.
|
||||||
const old_tree = self.getTree() orelse return;
|
const old_tree = self.getTree() orelse return;
|
||||||
const next_focus: ?*Surface = next_focus: {
|
const next_focus: ?*Surface = next_focus: {
|
||||||
const next_handle = old_tree.goto(handle, .previous) orelse
|
const alloc = Application.default().allocator();
|
||||||
old_tree.goto(handle, .next) orelse
|
const next_handle: Surface.Tree.Node.Handle =
|
||||||
|
(old_tree.goto(alloc, handle, .previous) catch null) orelse
|
||||||
|
(old_tree.goto(alloc, handle, .next) catch null) orelse
|
||||||
break :next_focus null;
|
break :next_focus null;
|
||||||
if (next_handle == handle) break :next_focus null;
|
if (next_handle == handle) break :next_focus null;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -177,7 +177,7 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Goto = enum {
|
pub const Goto = union(enum) {
|
||||||
/// Previous view, null if we're the first view.
|
/// Previous view, null if we're the first view.
|
||||||
previous,
|
previous,
|
||||||
|
|
||||||
|
|
@ -191,20 +191,34 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
/// Next view, but wrapped around to the first view. May return
|
/// Next view, but wrapped around to the first view. May return
|
||||||
/// the same view if this is the last view.
|
/// the same view if this is the last view.
|
||||||
next_wrapped,
|
next_wrapped,
|
||||||
|
|
||||||
|
/// A spatial direction. "Spatial" means that the direction is
|
||||||
|
/// based on the nearest surface in the given direction visually
|
||||||
|
/// as the surfaces are laid out on a 2D grid.
|
||||||
|
spatial: Spatial.Direction,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Goto a view from a certain point in the split tree. Returns null
|
/// Goto a view from a certain point in the split tree. Returns null
|
||||||
/// if the direction results in no visitable view.
|
/// if the direction results in no visitable view.
|
||||||
|
///
|
||||||
|
/// Allocator is only used for temporary state for spatial navigation.
|
||||||
pub fn goto(
|
pub fn goto(
|
||||||
self: *const Self,
|
self: *const Self,
|
||||||
|
alloc: Allocator,
|
||||||
from: Node.Handle,
|
from: Node.Handle,
|
||||||
to: Goto,
|
to: Goto,
|
||||||
) ?Node.Handle {
|
) Allocator.Error!?Node.Handle {
|
||||||
return switch (to) {
|
return switch (to) {
|
||||||
.previous => self.previous(from),
|
.previous => self.previous(from),
|
||||||
.next => self.next(from),
|
.next => self.next(from),
|
||||||
.previous_wrapped => self.previous(from) orelse self.deepest(.right, 0),
|
.previous_wrapped => self.previous(from) orelse self.deepest(.right, 0),
|
||||||
.next_wrapped => self.next(from) orelse self.deepest(.left, 0),
|
.next_wrapped => self.next(from) orelse self.deepest(.left, 0),
|
||||||
|
.spatial => |d| spatial: {
|
||||||
|
// Get our spatial representation.
|
||||||
|
var sp = try self.spatial(alloc);
|
||||||
|
defer sp.deinit(alloc);
|
||||||
|
break :spatial sp.nearestLeaf(from, d);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -586,17 +600,69 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
|
|
||||||
pub const empty: Spatial = .{ .slots = &.{} };
|
pub const empty: Spatial = .{ .slots = &.{} };
|
||||||
|
|
||||||
|
pub const Direction = enum { left, right, down, up };
|
||||||
|
|
||||||
const Slot = struct {
|
const Slot = struct {
|
||||||
x: f16,
|
x: f16,
|
||||||
y: f16,
|
y: f16,
|
||||||
width: f16,
|
width: f16,
|
||||||
height: f16,
|
height: f16,
|
||||||
|
|
||||||
|
fn maxX(self: *const Slot) f16 {
|
||||||
|
return self.x + self.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maxY(self: *const Slot) f16 {
|
||||||
|
return self.y + self.height;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn deinit(self: *const Spatial, alloc: Allocator) void {
|
pub fn deinit(self: *Spatial, alloc: Allocator) void {
|
||||||
alloc.free(self.slots);
|
alloc.free(self.slots);
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the nearest leaf node (view) in the given direction.
|
||||||
|
pub fn nearestLeaf(
|
||||||
|
self: *const Spatial,
|
||||||
|
from: Node.Handle,
|
||||||
|
direction: Direction,
|
||||||
|
) ?Node.Handle {
|
||||||
|
const target = self.slots[from];
|
||||||
|
|
||||||
|
var nearest: ?struct {
|
||||||
|
handle: Node.Handle,
|
||||||
|
distance: f16,
|
||||||
|
} = null;
|
||||||
|
for (self.slots, 0..) |slot, handle| {
|
||||||
|
// Never match ourself
|
||||||
|
if (handle == from) continue;
|
||||||
|
|
||||||
|
// Ensure it is in the proper direction
|
||||||
|
if (!switch (direction) {
|
||||||
|
.left => slot.maxX() <= target.maxX(),
|
||||||
|
.right => slot.x >= target.maxX(),
|
||||||
|
.up => slot.maxY() <= target.y,
|
||||||
|
.down => slot.y >= target.maxY(),
|
||||||
|
}) continue;
|
||||||
|
|
||||||
|
// Track our distance
|
||||||
|
const dx = slot.x - target.x;
|
||||||
|
const dy = slot.y - target.y;
|
||||||
|
const distance = @sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
|
// If we have a nearest it must be closer.
|
||||||
|
if (nearest) |n| {
|
||||||
|
if (distance >= n.distance) continue;
|
||||||
|
}
|
||||||
|
nearest = .{
|
||||||
|
.handle = @intCast(handle),
|
||||||
|
.distance = distance,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (nearest) |n| n.handle else null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Spatial representation of the split tree. This can be used to
|
/// Spatial representation of the split tree. This can be used to
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue