Skip to content

Instantly share code, notes, and snippets.

@ikskuh
Last active December 16, 2022 17:12
Show Gist options
  • Save ikskuh/c6f1c68d21b97997ac6d106a0536b188 to your computer and use it in GitHub Desktop.
Save ikskuh/c6f1c68d21b97997ac6d106a0536b188 to your computer and use it in GitHub Desktop.
Zig UI API Concept
//! xq @ UTC+2 (MasterQ32 @ GitHub) — gestern um 22:01 Uhr
//! more gui thoughts:
//!
//! i stopped using tree structures for my UIs and return to flat, ordered lists. this made the implementations sinpler by magnitudes, and one could always build stuff like a 'container' widget which would make it hierarchical by nesting UIs.
//!
//! right now, dunstwolke uses a immediate mode interface, which is now borked by a bugfix in stage2.
//!
//! now i'm thinking about how to decouple styling, layout and logic of the ui. also i'm still not sure how to manage UI events. i really dislike the callback approach of wpf/qt/winforms/...
//! compared to callbacks, immediate mode interfaces are very convenient to program, so i wonder if i can use a similar interface for a retained guy, for example a pull interface:
//! gui.pushInput(.mouse_down);
//! gui.pushInput(.mouse_up);
//!
//! while(gui.pullEvent()) |evt| {
//! process(evt);
//! }
//! such an interface would fit the zig spirit well i guess
//! i was using a simple Event enumeration for such events, but i guess its better to enrich them with more infos
//! so for layouting:
//!
//! assuming each widget has properties like position, size, min- and size, we can then use "external" means to implement a layout engine on top of that. layout engines have to be in a hierarchical tree structure for most stuff, except for solver-based ones (which are hard to control)
//! so having a tree for layouts, layout nodes can point to 'target rectangles'
//! allowing the layout engine to move widgets around without coupling them
//! hm. good thing about this design: it can be made zero-alloc except for dynamic layouts, or text input fields
//! i guess the main thing that bugged me about UI layouting is that the layout engine has to know about the size of strings
const gui = struct {
/// A view is a "scene" that displays widgets and manages input and event processing.
/// It can be considered a "screen" or "window".
const View = struct {
widgets: []Widget,
event_queue: RingBuffer(Event),
/// Sends input to the UI system and processes it. When events are generated, they are
/// put into the event queue and can be received with ´fetchEvent`.
pub fn sendInput(view: *View, input: Input) void;
/// Returns an event if there was any since the last call.
/// You should call this as long as a non-`null` value is returned.
/// Each `sendInput` call can generate events, sometimes more than one.
pub fn fetchEvent(view: *View) ?Event;
};
const Input = union(enum) {
mouse_down: MouseButton,
mouse_up: MouseButton,
mouse_motion: Point,
mouse_wheel: i16,
key_pressed: struct { KeyCode, KeyboardModifiers },
key_released: struct { KeyCode, KeyboardModifiers },
text_input: []const u8,
};
const EventHandler = struct {
user_data: ?*anyopaque,
id: u32,
};
const Event = struct {
widget: *Widget,
handler: EventHandler,
data: Data,
const Data = union(enum) {
none,
radio_group: *RadioGroup,
key: KeyCode,
};
};
const Widget = struct {
// logic properties:
control: Control,
// common events:
on_enter: ?EventHandler,
on_leave: ?EventHandler,
// layout properties:
bounds: Rectangle,
};
const Control = union(enum) {
button: Button, // a button that can be clicked by the user
check_box: CheckBox, // a box that can be checked or unchecked
radio_button: RadioButton, // a button withing a group where only one could be selected
label: Label, // a text label
text_box: TextBox, // a single-line text editor
text_editor: TextEditor, // a multi-line text editor
picture: Picture, // a control displaying an image
panel: Panel, // a visual group of controls
};
const Button = struct {
on_click: ?EventHandler,
text: []const u8,
};
const CheckBox = struct {
checked: bool,
on_checked_changed: ?EventHandler,
text: []const u8,
};
const RadioButton = struct {
group: *RadioGroup,
index: u32,
text: []const u8,
};
const RadioGroup = struct {
selection: ?u32,
on_selection_changed: ?EventHandler,
};
const Label = struct {
text: []const u8,
on_click: ?EventHandler,
is_link: bool,
};
const TextBox = struct {
text_buffer: std.ArrayList(u8),
on_text_changed: ?EventHandler,
on_return_pressed: ?EventHandler,
on_escape_pressed: ?EventHandler,
on_key_press: ?EventHandler,
};
};
/// initialize a layout node from a given widget, using the reference
/// mode of the node to keep a back-reference to the widget.
/// margins and paddings are set up correctly.
fn layoutNodeForWidget(widget: *gui.Widget) layout_engine.Node {
return switch (widget.control) {
// ...
};
}
const layout_engine = struct {
/// A rectangle that can either be stored out-of-tree (for example in a widget)
/// or in-tree (in the `Bounds` value itself).
const Bounds = union(enum) {
storeage: Rectangle,
reference: *Rectangle,
pub fn set(bounds: *Bounds, val: Rectangle) void;
pub fn get(bounds: Bounds) Rectangle;
};
const Margins = struct {
left: u15,
right: u15,
top: u15,
bottom: u15,
};
const VerticalAlignment = enum { stretch, left, center, right }; // stretch expands to available space
const HorizontalAlignment = enum { stretch, top, middle, bottom }; // stretch expands to available space
/// A node in the layout tree, has a position and size
const Node = struct {
bounds: Bounds,
min_size: Size,
max_size: Size,
children: []Node,
layout: Layout,
margin: Margins, // outer margins
padding: Margins, // inner margins
vertical_alignment: VerticalAlignment, // layout inside the parent region
horizontal_alignment: HorizontalAlignment, // layout inside the parent region
};
const Layout = union(enum) {
basic, // all children are layed out in the bounds independent of other children
stack: StackLayout, // items are put side-by-side
dock: DockLayout, // items dock on an edge of the parent, the last item is expanded into the rest
flow: FlowLayout, // flow based layout (similar to CSS flex)
table: TableLayout, // row/column based layout
canvas: CanvasLayout, // absolute positioning with (x,y) offset
};
const StackLayout = struct {
direction: enum {
left_to_right,
right_to_left,
top_to_bottom,
bottom_to_top,
},
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment