lib/ui/src/style/element.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const css = @import("css");
  3 const abi = @import("../abi/root.zig");
  4 const tree = @import("../tree/root.zig");
  5 
  6 pub const Interaction = struct {
  7     hovered: u64 = 0,
  8     focused: u64 = 0,
  9     active: u64 = 0,
 10     keyboard_focus: bool = false,
 11 };
 12 
 13 pub const Adapter = struct {
 14     view: tree.View,
 15     sheet: *const css.StyleSheet,
 16     interaction: Interaction = .{},
 17     hovered_index: ?u32 = null,
 18     focused_index: ?u32 = null,
 19     active_index: ?u32 = null,
 20     resolved: bool = false,
 21     ancestor_steps: u64 = 0,
 22 
 23     pub fn element(self: *Adapter) css.match.Element {
 24         if (!self.resolved) {
 25             self.hovered_index = self.find(self.interaction.hovered);
 26             self.focused_index = self.find(self.interaction.focused);
 27             self.active_index = self.find(self.interaction.active);
 28             self.resolved = true;
 29         }
 30         return .{
 31             .context = self,
 32             .parent = parent,
 33             .previous = previous,
 34             .kind = kind,
 35             .role = role,
 36             .identifier = identifier,
 37             .classes = classes,
 38             .pseudo = pseudo,
 39             .sibling_index = siblingIndex,
 40         };
 41     }
 42 
 43     fn selfOf(context: ?*anyopaque) *Adapter {
 44         return @ptrCast(@alignCast(context.?));
 45     }
 46 
 47     fn parent(context: ?*anyopaque, index: u32) callconv(.c) u32 {
 48         const self = selfOf(context);
 49         self.ancestor_steps += 1;
 50         std.debug.assert(index < self.view.nodes.len);
 51         return self.view.nodes[index].parent;
 52     }
 53 
 54     fn previous(context: ?*anyopaque, index: u32) callconv(.c) u32 {
 55         const self = selfOf(context);
 56         self.ancestor_steps += 1;
 57         std.debug.assert(index < self.view.nodes.len);
 58         if (index == 0) return index;
 59         const wanted = self.view.nodes[index].parent;
 60         var candidate = index;
 61         while (candidate > 0) {
 62             candidate -= 1;
 63             if (self.view.nodes[candidate].parent == wanted and candidate != wanted and
 64                 candidate + self.view.nodes[candidate].subtree_count < index) return candidate;
 65             if (candidate == wanted) break;
 66         }
 67         return index;
 68     }
 69 
 70     fn kind(context: ?*anyopaque, index: u32) callconv(.c) u16 {
 71         const self = selfOf(context);
 72         const value: abi.Kind = @fromBackingInt(@intCast(self.view.nodes[index].kind));
 73         return @intCast(self.sheet.token(@tagName(value)));
 74     }
 75 
 76     fn role(context: ?*anyopaque, index: u32) callconv(.c) u16 {
 77         const self = selfOf(context);
 78         const value: abi.Role = @fromBackingInt(@intCast(self.view.nodes[index].role));
 79         return @intCast(self.sheet.token(@tagName(value)));
 80     }
 81 
 82     fn identifier(context: ?*anyopaque, index: u32) callconv(.c) u32 {
 83         const self = selfOf(context);
 84         return self.sheet.token(self.view.text(self.view.nodes[index].identifier));
 85     }
 86 
 87     fn classes(context: ?*anyopaque, index: u32, out: [*]u32, capacity: u32) callconv(.c) u32 {
 88         const self = selfOf(context);
 89         const source = self.view.classesOf(index);
 90         std.debug.assert(source.len <= capacity);
 91         for (source, 0..) |atom, at| out[at] = self.sheet.token(self.view.text(atom));
 92         return @intCast(source.len);
 93     }
 94 
 95     fn hasAncestor(self: *const Adapter, index: u32, start: ?u32) bool {
 96         var candidate = start orelse return false;
 97         while (true) {
 98             if (candidate == index) return true;
 99             if (candidate == 0) return false;
100             candidate = self.view.nodes[candidate].parent;
101         }
102     }
103 
104     fn pseudo(context: ?*anyopaque, index: u32) callconv(.c) u32 {
105         const self = selfOf(context);
106         const node = self.view.nodes[index];
107         var word: u32 = 0;
108         inline for (@typeInfo(abi.State).@"enum".field_names, 0..) |name, at| {
109             const state: abi.State = @fromBackingInt(@intCast(at));
110             const class = @field(css.selector.PseudoClass, name);
111             if (abi.active(node.state, state)) word |= css.selector.bit(class);
112         }
113         if (index == 0) word |= css.selector.bit(.root);
114         if (node.subtree_count == 0 and node.text == abi.text_absent) word |= css.selector.bit(.empty);
115         if (!abi.active(node.state, .disabled)) word |= css.selector.bit(.enabled);
116         if (abi.holds(node.flags, .modal)) word |= css.selector.bit(.modal);
117         if (self.hasAncestor(index, self.hovered_index)) word |= css.selector.bit(.hover);
118         if (self.hasAncestor(index, self.active_index)) word |= css.selector.bit(.active);
119         if (self.view.nodes[index].id == self.interaction.focused and self.interaction.focused != 0) {
120             word |= css.selector.bit(.focus);
121             if (self.interaction.keyboard_focus) word |= css.selector.bit(.focus_visible);
122         }
123         if (self.focused_index) |focused| {
124             if (self.isAncestor(index, focused)) word |= css.selector.bit(.focus_within);
125         }
126         return word;
127     }
128 
129     pub fn find(self: *const Adapter, id: u64) ?u32 {
130         if (id == 0) return null;
131         if (self.view.identity) |identity| return identity.lookup(self.view.nodes, id);
132         for (self.view.nodes, 0..) |node, at| if (node.id == id) return @intCast(at);
133         return null;
134     }
135 
136     fn isAncestor(self: *const Adapter, ancestor: u32, child: u32) bool {
137         var index = child;
138         while (true) {
139             if (index == ancestor) return true;
140             if (index == 0) return false;
141             index = self.view.nodes[index].parent;
142         }
143     }
144 
145     fn siblingIndex(context: ?*anyopaque, index: u32, of_type: bool) callconv(.c) u32 {
146         const self = selfOf(context);
147         const node = self.view.nodes[index];
148         var first: u32 = if (index == 0) 0 else node.parent + 1;
149         var ordinal: u32 = 0;
150         var total: u32 = 0;
151         while (first < self.view.nodes.len and
152             (index == 0 or first <= node.parent + self.view.nodes[node.parent].subtree_count))
153         {
154             const candidate = self.view.nodes[first];
155             if (!of_type or candidate.kind == node.kind) {
156                 total += 1;
157                 if (first <= index) ordinal = total;
158             }
159             first += candidate.subtree_count + 1;
160         }
161         return css.match.packSibling(ordinal, total);
162     }
163 };