tiny.gui.widget
Defined in tiny.gui.
API (23)
Actions
Public operations.
findByActionfindByIdfindByRolefindByTextContainingfindIndexByIdfindNodeByActionfindNodeByRolefocusedhitTesthitTestActivatablehitTestIndexinteractionWidgetsForActionsisActivatableisDisabledNodeisDisabledWidgetisFocusableForTraversalisTextEditablepointForActionpointForTextContainingpointForWidgetrootWidgetIndex
Types and contracts
Public types and contracts.
Source
Source: lib/gui/src/root.zig:16
zig
pub const widget = @import("widget.zig");Source: lib/gui/src/widget.zig
zig
const std = @import("std");const model = @import("model.zig");const RootFrame = model.RootFrame;const UiNode = model.UiNode;const UiPoint = model.UiPoint;const WidgetFrame = model.WidgetFrame;const ui_state_disabled = model.ui_state_disabled;pub const ActionInteraction = struct { hovered_action: ?[]const u8 = null, captured_action: ?[]const u8 = null, focused_action: ?[]const u8 = null,};pub const InteractionWidgets = struct { hovered_widget: ?u64 = null, captured_widget: ?u64 = null, focused_widget: ?u64 = null,};pub fn hitTest(widgets: []const WidgetFrame, root_id: u64, x: f32, y: f32) ?*const WidgetFrame { const index = hitTestIndex(widgets, root_id, x, y) orelse return null; return &widgets[index];}pub fn hitTestIndex(widgets: []const WidgetFrame, root_id: u64, x: f32, y: f32) ?usize { var index: usize = widgets.len; while (index > 0) { index -= 1; const widget = &widgets[index]; if (widget.root_id != root_id) continue; if (isDisabledWidget(widget)) continue; if (widget.visible_rect.width <= 0 or widget.visible_rect.height <= 0) continue; if (model.pointInRect(widget.rect, x, y) and model.pointInRect(widget.visible_rect, x, y)) return index; } return null;}pub fn hitTestActivatable(widgets: []const WidgetFrame, root_id: u64, x: f32, y: f32) ?*const WidgetFrame { const hit = hitTest(widgets, root_id, x, y) orelse return null; if (isActivatable(hit)) return hit; const hit_index = findIndexById(widgets, root_id, hit.widget_id) orelse return null; var index = hit_index; while (index > 0) { index -= 1; const candidate = &widgets[index]; if (candidate.root_id != root_id) continue; if (index + candidate.subtree_size <= hit_index) continue; if (!isActivatable(candidate)) continue; if (model.pointInRect(candidate.rect, x, y) and model.pointInRect(candidate.visible_rect, x, y)) return candidate; } return null;}pub fn findById(widgets: []const WidgetFrame, root_id: u64, widget_id: u64) ?*const WidgetFrame { for (widgets) |*widget| { if (widget.root_id == root_id and widget.widget_id == widget_id) return widget; } return null;}pub fn findIndexById(widgets: []const WidgetFrame, root_id: u64, widget_id: u64) ?usize { for (widgets, 0..) |*widget, index| { if (widget.root_id == root_id and widget.widget_id == widget_id) return index; } return null;}pub fn rootWidgetIndex(roots: []const RootFrame, root_id: u64) ?usize { for (roots) |root| { if (root.root_id == root_id and root.widget_count > 0) return root.widget_start; } return null;}pub fn focused(widgets: []const WidgetFrame, root_id: u64, widget_id: ?u64) ?*const WidgetFrame { const current = widget_id orelse return null; const widget = findById(widgets, root_id, current) orelse return null; return if (isFocusableForTraversal(root_id, widget)) widget else null;}pub fn findNodeByAction(root: *const UiNode, action: []const u8) ?*const UiNode { if (action.len == 0) return null; if (std.mem.eql(u8, root.action, action)) return root; for (root.children) |*child| { if (findNodeByAction(child, action)) |found| return found; } return null;}pub fn findNodeByRole(root: *const UiNode, role: []const u8) ?*const UiNode { if (role.len == 0) return null; if (std.mem.eql(u8, root.role, role)) return root; for (root.children) |*child| { if (findNodeByRole(child, role)) |found| return found; } return null;}pub fn interactionWidgetsForActions(root: *const UiNode, interaction: ActionInteraction) InteractionWidgets { return .{ .hovered_widget = nodeIdForAction(root, interaction.hovered_action), .captured_widget = nodeIdForAction(root, interaction.captured_action), .focused_widget = nodeIdForAction(root, interaction.focused_action), };}pub fn findByAction(widgets: []const WidgetFrame, root_id: u64, action: []const u8) ?*const WidgetFrame { if (action.len == 0) return null; for (widgets) |*widget| { if (widget.root_id == root_id and std.mem.eql(u8, widget.action, action)) return widget; } return null;}pub fn findByRole(widgets: []const WidgetFrame, root_id: u64, role: []const u8) ?*const WidgetFrame { if (role.len == 0) return null; for (widgets) |*widget| { if (widget.root_id == root_id and std.mem.eql(u8, widget.role, role)) return widget; } return null;}pub fn findByTextContaining(widgets: []const WidgetFrame, root_id: u64, needle: []const u8) ?*const WidgetFrame { if (needle.len == 0) return null; for (widgets) |*widget| { if (widget.root_id != root_id) continue; const text = widget.text orelse continue; if (std.mem.indexOf(u8, text.content, needle) != null) return widget; } return null;}pub fn pointForAction(widgets: []const WidgetFrame, root_id: u64, action: []const u8) ?UiPoint { const widget = findByAction(widgets, root_id, action) orelse return null; if (!isActivatable(widget)) return null; return pointForWidget(widget);}pub fn pointForTextContaining(widgets: []const WidgetFrame, root_id: u64, needle: []const u8) ?UiPoint { const widget = findByTextContaining(widgets, root_id, needle) orelse return null; return pointForWidget(widget);}pub fn pointForWidget(widget: *const WidgetFrame) ?UiPoint { if (widget.visible_rect.width <= 0 or widget.visible_rect.height <= 0) return null; return .{ .x = widget.visible_rect.x + widget.visible_rect.width * 0.5, .y = widget.visible_rect.y + widget.visible_rect.height * 0.5, };}pub fn isActivatable(widget: *const WidgetFrame) bool { return !isDisabledWidget(widget) and widget.kind != .text_input and (widget.kind == .button or widget.action.len != 0);}pub fn isFocusableForTraversal(root_id: u64, widget: *const WidgetFrame) bool { return widget.root_id == root_id and widget.focusable and !isDisabledWidget(widget) and widget.rect.width > 0 and widget.rect.height > 0;}pub fn isDisabledNode(node: *const UiNode) bool { return (node.state_flags & ui_state_disabled) != 0;}pub fn isDisabledWidget(widget: *const WidgetFrame) bool { return (widget.state_flags & ui_state_disabled) != 0;}pub fn isTextEditable(widget: *const WidgetFrame) bool { return widget.kind == .text_input and !isDisabledWidget(widget);}fn nodeIdForAction(root: *const UiNode, action: ?[]const u8) ?u64 { const action_text = action orelse return null; const node = findNodeByAction(root, action_text) orelse return null; return node.widget_id;}test "activatable hit testing promotes child text hits to the owning widget" { const widgets = [_]WidgetFrame{ .{ .root_id = 7, .widget_id = 10, .kind = .button, .rect = .{ .x = 0, .y = 0, .width = 120, .height = 32 }, .visible_rect = .{ .x = 0, .y = 0, .width = 120, .height = 32 }, .paint = .{}, .scroll = .{}, .constraints = .{}, .content_size = .{ .width = 120, .height = 32 }, .focusable = true, .action = "demo.activate", .subtree_size = 2, }, .{ .root_id = 7, .widget_id = 11, .kind = .label, .rect = .{ .x = 8, .y = 6, .width = 72, .height = 18 }, .visible_rect = .{ .x = 8, .y = 6, .width = 72, .height = 18 }, .paint = .{}, .scroll = .{}, .constraints = .{}, .content_size = .{ .width = 72, .height = 18 }, .focusable = false, }, }; const raw = hitTest(widgets[0..], 7, 12, 12) orelse return error.MissingRawHit; try std.testing.expectEqual(@as(u64, 11), raw.widget_id); const promoted = hitTestActivatable(widgets[0..], 7, 12, 12) orelse return error.MissingPromotedHit; try std.testing.expectEqual(@as(u64, 10), promoted.widget_id); try std.testing.expectEqualStrings("demo.activate", promoted.action);}test "action and role lookup traverse surface trees and frame facts" { const text = model.UiText{ .content = "Alpha row" }; const nodes = [_]UiNode{ .{ .widget_id = 11, .kind = .button, .action = "demo.open", .role = "scene.graph-row", }, .{ .widget_id = 12, .kind = .label, .role = "scene.preview", .text = text, }, }; const root = UiNode{ .widget_id = 10, .role = "scene", .children = nodes[0..], }; try std.testing.expectEqual(@as(u64, 11), (findNodeByAction(&root, "demo.open") orelse return error.MissingActionNode).widget_id); try std.testing.expectEqual(@as(u64, 12), (findNodeByRole(&root, "scene.preview") orelse return error.MissingRoleNode).widget_id); try std.testing.expect(findNodeByAction(&root, "") == null); const widgets = [_]WidgetFrame{ .{ .root_id = 7, .widget_id = 11, .kind = .button, .rect = .{ .x = 0, .y = 0, .width = 80, .height = 24 }, .visible_rect = .{ .x = 0, .y = 0, .width = 80, .height = 24 }, .paint = .{}, .scroll = .{}, .constraints = .{}, .content_size = .{ .width = 80, .height = 24 }, .focusable = true, .action = "demo.open", .role = "scene.graph-row", }, .{ .root_id = 7, .widget_id = 12, .kind = .label, .rect = .{ .x = 90, .y = 0, .width = 100, .height = 24 }, .visible_rect = .{ .x = 90, .y = 0, .width = 100, .height = 24 }, .paint = .{}, .scroll = .{}, .constraints = .{}, .content_size = .{ .width = 100, .height = 24 }, .focusable = false, .role = "scene.preview", .has_text = true, .text = text, }, .{ .root_id = 8, .widget_id = 13, .kind = .button, .rect = .{ .x = 0, .y = 0, .width = 80, .height = 24 }, .visible_rect = .{ .x = 0, .y = 0, .width = 80, .height = 24 }, .paint = .{}, .scroll = .{}, .constraints = .{}, .content_size = .{ .width = 80, .height = 24 }, .focusable = true, .action = "demo.open", }, .{ .root_id = 7, .widget_id = 14, .kind = .button, .rect = .{ .x = 0, .y = 30, .width = 80, .height = 24 }, .visible_rect = .{ .x = 0, .y = 30, .width = 80, .height = 24 }, .paint = .{}, .scroll = .{}, .constraints = .{}, .content_size = .{ .width = 80, .height = 24 }, .focusable = true, .action = "demo.disabled", .state_flags = ui_state_disabled, }, }; try std.testing.expectEqual(@as(u64, 11), (findByAction(widgets[0..], 7, "demo.open") orelse return error.MissingActionWidget).widget_id); try std.testing.expectEqual(@as(u64, 12), (findByRole(widgets[0..], 7, "scene.preview") orelse return error.MissingRoleWidget).widget_id); try std.testing.expectEqual(@as(u64, 12), (findByTextContaining(widgets[0..], 7, "row") orelse return error.MissingTextWidget).widget_id); try std.testing.expectEqual(@as(u64, 13), (findByAction(widgets[0..], 8, "demo.open") orelse return error.MissingSecondRootActionWidget).widget_id); const point = pointForAction(widgets[0..], 7, "demo.open") orelse return error.MissingActionPoint; try std.testing.expectEqual(@as(f32, 40), point.x); try std.testing.expectEqual(@as(f32, 12), point.y); try std.testing.expect(pointForAction(widgets[0..], 7, "demo.disabled") == null); const text_point = pointForTextContaining(widgets[0..], 7, "Alpha") orelse return error.MissingTextPoint; try std.testing.expectEqual(@as(f32, 140), text_point.x); try std.testing.expectEqual(@as(f32, 12), text_point.y);}test "action interaction maps action descriptors to widget ids" { const nodes = [_]UiNode{ .{ .widget_id = 21, .kind = .button, .action = "inspector.tabs.items", }, .{ .widget_id = 22, .kind = .button, .action = "inspector.action.refresh", }, }; const root = UiNode{ .widget_id = 20, .children = nodes[0..], }; const mapped = interactionWidgetsForActions(&root, .{ .hovered_action = "inspector.tabs.items", .captured_action = "inspector.action.refresh", .focused_action = "inspector.tabs.items", }); try std.testing.expectEqual(@as(?u64, 21), mapped.hovered_widget); try std.testing.expectEqual(@as(?u64, 22), mapped.captured_widget); try std.testing.expectEqual(@as(?u64, 21), mapped.focused_widget); try std.testing.expectEqual(@as(?u64, null), interactionWidgetsForActions(&root, .{ .hovered_action = "" }).hovered_widget);}Complete caller list for widget.isDisabledWidget
9 direct callers.
fun.sdfii.src.engine.system.ui.input.scroll.ensureVisible[function] — private; no exact target atfun/sdfii/src/engine/system/ui/input/scroll.zig:113in nearest public ownerfun.sdfii.src.engine.system.ui.input.scrollfun.sdfii.src.engine.system.ui.input.scroll.keyboardTarget[function] — private; no exact target atfun/sdfii/src/engine/system/ui/input/scroll.zig:55in nearest public ownerfun.sdfii.src.engine.system.ui.input.scrollfun.sdfii.src.engine.system.ui.input.scroll.pointerTarget[function] — private; no exact target atfun/sdfii/src/engine/system/ui/input/scroll.zig:16in nearest public ownerfun.sdfii.src.engine.system.ui.input.scrollfun.sdfii.src.validation.ui.buildAccessibility[function] — private; no exact target atfun/sdfii/src/validation/ui.zig:311in nearest public ownerfun.sdfii.src.validation.uifun.sdfii.src.validation.ui.isFocusable[function] — private; no exact target atfun/sdfii/src/validation/ui.zig:483in nearest public ownerfun.sdfii.src.validation.uitiny.gui.widget.hitTestIndex[function] atlib/gui/src/widget.zig:27tiny.gui.widget.isActivatable[function] atlib/gui/src/widget.zig:154tiny.gui.widget.isFocusableForTraversal[function] atlib/gui/src/widget.zig:158tiny.gui.widget.isTextEditable[function] atlib/gui/src/widget.zig:174
Audit
| Definitions | 24 |
|---|---|
| Public names | 24 |
| Members | 6 |
| Version | 26.7.0 |
| Revision | daab053ee433 |