867 lines
32 KiB
Zig
867 lines
32 KiB
Zig
//! By convention, root.zig is the root source file when making a library.
|
|
const std = @import("std");
|
|
|
|
// base drawing functions
|
|
// text,
|
|
// rect,
|
|
// texture,
|
|
|
|
// sissor
|
|
pub const VERSION_MAJOR = 0;
|
|
pub const VERSION_MINOR = 1;
|
|
pub const VERSION_PATCH = 0;
|
|
|
|
pub const VERSION = std.fmt.comptimePrint("{}.{}.{}", .{
|
|
VERSION_MAJOR,
|
|
VERSION_MINOR,
|
|
VERSION_PATCH,
|
|
});
|
|
|
|
pub const Real = f32;
|
|
const Spaceing = 4;
|
|
|
|
pub fn TextType(
|
|
comptime FontType: type,
|
|
comptime getTextWidthFunc: fn (string: []const u8, FontType) Real,
|
|
comptime getTextHeightFunc: fn (string: []const u8, FontType) Real,
|
|
) type {
|
|
return struct {
|
|
const Optional = struct {
|
|
font: FontType,
|
|
};
|
|
const Self = @This();
|
|
string: []const u8,
|
|
colour: Colour,
|
|
background: Colour,
|
|
font: FontType,
|
|
|
|
pub fn init(string: []const u8, font: FontType, colour: Colour) Self {
|
|
return Self{
|
|
.string = string,
|
|
.font = font,
|
|
.colour = colour,
|
|
.background = .{
|
|
.r = 0,
|
|
.b = 0,
|
|
.g = 0,
|
|
.a = 0,
|
|
},
|
|
};
|
|
}
|
|
|
|
pub fn setBackground(self: Self, background: Colour) Self {
|
|
var s = self;
|
|
s.background = background;
|
|
return s;
|
|
}
|
|
|
|
pub fn getTextWidth(self: Self) Real {
|
|
return getTextWidthFunc(self.string, self.font);
|
|
}
|
|
|
|
pub fn getTextHeight(self: Self) Real {
|
|
return getTextHeightFunc(self.string, self.font);
|
|
}
|
|
};
|
|
}
|
|
|
|
pub fn TextureType(comptime TextureData: type) type {
|
|
return struct {
|
|
texture: TextureData,
|
|
rect: Rect,
|
|
};
|
|
}
|
|
|
|
pub const Rect = struct {
|
|
w: Real = 0,
|
|
h: Real = 0,
|
|
};
|
|
|
|
pub const Pos = struct {
|
|
x: Real = 0,
|
|
y: Real = 0,
|
|
};
|
|
|
|
const Sizing = enum {
|
|
fixed,
|
|
grow,
|
|
min,
|
|
max,
|
|
fit,
|
|
};
|
|
|
|
pub const Padding = struct {
|
|
top: Real = 0,
|
|
bottom: Real = 0,
|
|
left: Real = 0,
|
|
right: Real = 0,
|
|
|
|
pub fn all(padding: Real) Padding {
|
|
return .{
|
|
.top = padding,
|
|
.bottom = padding,
|
|
.left = padding,
|
|
.right = padding,
|
|
};
|
|
}
|
|
|
|
pub fn topf(padding: Real) Padding {
|
|
return .{
|
|
.top = padding,
|
|
};
|
|
}
|
|
pub fn bottomf(padding: Real) Padding {
|
|
return .{
|
|
.bottom = padding,
|
|
};
|
|
}
|
|
pub fn leftf(padding: Real) Padding {
|
|
return .{
|
|
.left = padding,
|
|
};
|
|
}
|
|
pub fn rightf(padding: Real) Padding {
|
|
return .{
|
|
.right = padding,
|
|
};
|
|
}
|
|
};
|
|
|
|
pub const Layout = enum {
|
|
right_to_left,
|
|
top_to_bottom,
|
|
};
|
|
|
|
fn printWithLevel(writer: *std.Io.Writer, level: usize, comptime fmt: []const u8, args: anytype) !void {
|
|
for (0..level * Spaceing) |_| try writer.writeByte(' ');
|
|
try writer.print(fmt, args);
|
|
}
|
|
|
|
pub const Style = struct {
|
|
size_x: Sizing = .fit,
|
|
size_y: Sizing = .fit,
|
|
|
|
background_colour: Colour = .{},
|
|
rounded: ?Real = null,
|
|
|
|
// require re-draw
|
|
padding: Padding = .{},
|
|
child_gap: Real = 0,
|
|
layout: Layout = .top_to_bottom,
|
|
|
|
pub fn format(
|
|
self: @This(),
|
|
writer: *std.Io.Writer,
|
|
) std.Io.Writer.Error!void {
|
|
try printWithLevel(writer, 0, "style : .{{\n", .{});
|
|
try self.printStyle(writer, 0);
|
|
}
|
|
|
|
pub fn printStyle(style: Style, writer: *std.Io.Writer, level: usize) std.Io.Writer.Error!void {
|
|
try printWithLevel(writer, level + 1, "size_x : {any},\n", .{style.size_x});
|
|
try printWithLevel(writer, level + 1, "size_y : {any},\n", .{style.size_y});
|
|
try printWithLevel(writer, level + 1, "background_colour : {any},\n", .{style.background_colour});
|
|
try printWithLevel(writer, level + 1, "rounded : {any},\n", .{style.rounded});
|
|
try printWithLevel(writer, level + 1, "padding : {any},\n", .{style.padding});
|
|
try printWithLevel(writer, level + 1, "child_gap : {any},\n", .{style.child_gap});
|
|
try printWithLevel(writer, level + 1, "layout : {any},\n", .{style.layout});
|
|
try printWithLevel(writer, level, "}},\n", .{});
|
|
}
|
|
};
|
|
|
|
pub const Colour = struct {
|
|
r: u8 = 0,
|
|
g: u8 = 0,
|
|
b: u8 = 0,
|
|
a: u8 = 255,
|
|
};
|
|
|
|
pub const MouseState = struct {
|
|
pos: Pos,
|
|
left: bool,
|
|
right: bool,
|
|
middle: bool,
|
|
};
|
|
|
|
pub fn pointinRect(point: Pos, rect_pos: Pos, rect: Rect) bool {
|
|
if (point.x > rect_pos.x and
|
|
point.x < rect_pos.x + rect.w and
|
|
point.y > rect_pos.y and
|
|
point.y < rect_pos.y + rect.h) return true;
|
|
return false;
|
|
}
|
|
|
|
// might add an easy conversion
|
|
// comptime mouse_type: type,
|
|
// comptime getMouseState: fn (mouse_type) MouseState,
|
|
|
|
// comptime getKeyboardInput: fn(keyboard_type) []const u8,
|
|
|
|
// comptime renderTextFunc: fn (text_type, Pos) void,
|
|
// comptime renderRectFunc: fn (Rect, Pos, Colour, rounding: ?Real) void,
|
|
// comptime renderTextureFunc: fn (texture_type, Pos) void,
|
|
|
|
pub fn Shoots(
|
|
comptime text_type: type,
|
|
comptime texture_type: type,
|
|
) type {
|
|
return struct {
|
|
const Self = @This();
|
|
|
|
pub const Text = text_type;
|
|
pub const Texture = texture_type;
|
|
|
|
const NodeTypes = enum {
|
|
element,
|
|
text,
|
|
texture,
|
|
};
|
|
pub const Node = union(NodeTypes) {
|
|
element: Ele,
|
|
text: Text,
|
|
texture: Texture,
|
|
|
|
fn printTreeHelper(writer: *std.Io.Writer, node: Node, level: usize) !void {
|
|
switch (node) {
|
|
.element => {
|
|
try printWithLevel(writer, level, "{s} : Ele{{\n", .{node.element.name});
|
|
try printWithLevel(writer, level + 1, "pos : {any},\n", .{node.element.pos});
|
|
try printWithLevel(writer, level + 1, "on_click : {?},\n", .{node.element.on_click});
|
|
try printWithLevel(writer, level + 1, "rect : {any},\n", .{node.element.rect});
|
|
try printWithLevel(writer, level + 1, "style : .{{\n", .{});
|
|
try node.element.style.printStyle(writer, level + 1);
|
|
try printWithLevel(writer, level + 1, "children : {{\n", .{});
|
|
for (node.element.children, 0..) |_, i| {
|
|
try printTreeHelper(writer, node.element.children[i], (level + 2));
|
|
}
|
|
try printWithLevel(writer, level + 1, "}},\n", .{});
|
|
try printWithLevel(writer, level, "}},\n", .{});
|
|
},
|
|
|
|
.text => {
|
|
try printWithLevel(writer, level, "text : {s}.\n", .{node.text.string});
|
|
return;
|
|
},
|
|
.texture => {
|
|
try printWithLevel(writer, level, "texture : {any},\n", .{node.texture});
|
|
return;
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn format(
|
|
self: Node,
|
|
writer: *std.Io.Writer,
|
|
) std.Io.Writer.Error!void {
|
|
try printTreeHelper(writer, self, 0);
|
|
}
|
|
};
|
|
|
|
pub const Ele = struct {
|
|
const Self = @This();
|
|
name: []const u8 = "",
|
|
z_index: usize = 0,
|
|
pos: Pos = .{},
|
|
rect: Rect = .{},
|
|
style: Style = .{},
|
|
|
|
children: []const Node = &[_]Node{},
|
|
|
|
on_hover: ?Interact = null,
|
|
on_click: ?Interact = null,
|
|
// the on hover call back will be ran
|
|
allow_on_hover_when_occluded: bool = true,
|
|
allow_on_click_when_occluded: bool = false,
|
|
};
|
|
|
|
pub const Interact = struct {
|
|
// returns true if the ui must be drawn again
|
|
func: *const fn (self: *Ele, mouse_state: MouseState, *anyopaque) bool,
|
|
data: *anyopaque,
|
|
};
|
|
|
|
const RenderCommandType = enum {
|
|
rect,
|
|
text,
|
|
texture,
|
|
};
|
|
|
|
pub const RenderCommand = union(RenderCommandType) {
|
|
rect: struct {
|
|
rect: Rect,
|
|
pos: Pos,
|
|
colour: Colour,
|
|
rounding: ?Real,
|
|
z_index: usize = 0,
|
|
},
|
|
text: struct {
|
|
text: Text,
|
|
pos: Pos,
|
|
z_index: usize = 0,
|
|
},
|
|
texture: struct {
|
|
texture: Texture,
|
|
pos: Pos,
|
|
z_index: usize = 0,
|
|
},
|
|
};
|
|
|
|
// focused: *Node,
|
|
|
|
// /// highly suggested that you use an area for the alloc
|
|
// pub fn init() Self {
|
|
// return Self{
|
|
// .alloc = alloc,
|
|
// };
|
|
// }
|
|
|
|
pub inline fn ElementWborder(
|
|
boarder_width: Padding,
|
|
boarder_colour: Colour,
|
|
ele: Ele,
|
|
) Node {
|
|
var internal = ele;
|
|
internal.rect.h -= boarder_width.bottom + boarder_width.top;
|
|
internal.rect.w -= boarder_width.right + boarder_width.left;
|
|
|
|
return Node{
|
|
.element = .{
|
|
.name = ele.name ++ "_boarder",
|
|
.style = .{
|
|
.padding = boarder_width,
|
|
.background_colour = boarder_colour,
|
|
.rounded = ele.style.rounded,
|
|
},
|
|
.children = &[_]Node{
|
|
Element(internal),
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
pub inline fn Element(ele: Ele) Node {
|
|
return Node{
|
|
.element = ele,
|
|
};
|
|
}
|
|
|
|
pub inline fn Txt(text: Text) Node {
|
|
return Element(
|
|
.{
|
|
.style = .{
|
|
.background_colour = text.background,
|
|
},
|
|
.children = &[_]Node{.{
|
|
.text = text,
|
|
}},
|
|
},
|
|
);
|
|
}
|
|
|
|
pub fn closeElement(node: *Node, parent: *Node) void {
|
|
switch (node.*) {
|
|
.element => {
|
|
// layout
|
|
const child_gap: Real = parent.element.style.child_gap;
|
|
|
|
switch (parent.element.style.layout) {
|
|
.right_to_left => parent.element.rect.w += child_gap,
|
|
.top_to_bottom => parent.element.rect.h += child_gap,
|
|
}
|
|
|
|
const padding = node.element.style.padding;
|
|
|
|
node.element.rect.w += padding.left + padding.right;
|
|
node.element.rect.h += padding.top + padding.bottom;
|
|
|
|
switch (parent.element.style.layout) {
|
|
.right_to_left => {
|
|
parent.element.rect.h = @max(node.element.rect.h, parent.element.rect.h);
|
|
parent.element.rect.w += node.element.rect.w;
|
|
},
|
|
|
|
.top_to_bottom => {
|
|
parent.element.rect.h += node.element.rect.h;
|
|
parent.element.rect.w = @max(node.element.rect.w, parent.element.rect.w);
|
|
},
|
|
}
|
|
},
|
|
.text => {
|
|
parent.element.rect.h += node.text.getTextHeight();
|
|
// std.debug.print("parent.element.rect.h : {}\n", .{parent.element.rect.h});
|
|
parent.element.rect.w += node.text.getTextWidth();
|
|
// std.debug.print("parent.element.rect.w : {}\n", .{parent.element.rect.w});
|
|
},
|
|
.texture => return,
|
|
}
|
|
}
|
|
|
|
fn deepCloneHelper(node: Node, node_location: *Node, alloc: std.mem.Allocator) !void {
|
|
// std.debug.print("nodeloc : {}\n", .{node_location});
|
|
node_location.* = node;
|
|
// std.debug.print("nodeloc : {}\n", .{node_location});
|
|
switch (node) {
|
|
.element => |ele| {
|
|
// BASE CASE
|
|
// node_location.element.name = "cloned";
|
|
if (ele.children.len == 0) return;
|
|
const children = try alloc.alloc(Node, ele.children.len);
|
|
// std.debug.print("children Alloc {x}\n", .{@intFromPtr(children.ptr)});
|
|
for (ele.children, 0..) |child, i| {
|
|
try deepCloneHelper(child, &children[i], alloc);
|
|
// std.debug.print("base node_loc {x}\n", .{@intFromPtr(node_location.element.children.ptr)});
|
|
node_location.*.element.children = children;
|
|
// std.debug.print("up[date node_loc {x}\n", .{@intFromPtr(node_location.element.children.ptr)});
|
|
}
|
|
},
|
|
else => return,
|
|
}
|
|
}
|
|
|
|
// we deep clone the root node as this make it easier for the temp node
|
|
fn deepClone(node: Node, alloc: std.mem.Allocator) !Node {
|
|
var new_node = node;
|
|
switch (new_node) {
|
|
.element => |ele| {
|
|
// BASE CASE
|
|
if (ele.children.len == 0) return new_node;
|
|
const children = try alloc.alloc(Node, ele.children.len);
|
|
for (ele.children, 0..) |child, i| {
|
|
try deepCloneHelper(child, &children[i], alloc);
|
|
new_node.element.children = children;
|
|
}
|
|
},
|
|
else => return new_node,
|
|
}
|
|
|
|
return new_node;
|
|
}
|
|
|
|
pub fn getRenderCommandsHelper(node: Node, alloc: std.mem.Allocator, command_list: *std.ArrayList(RenderCommand)) !void {
|
|
switch (node) {
|
|
.element => {
|
|
// std.debug.print("parent name : {s}\n", .{parent.element.name});
|
|
try command_list.append(alloc, .{
|
|
.rect = .{
|
|
.colour = node.element.style.background_colour,
|
|
.pos = node.element.pos,
|
|
.rect = node.element.rect,
|
|
.rounding = node.element.style.rounded,
|
|
.z_index = node.element.z_index,
|
|
},
|
|
});
|
|
|
|
for (node.element.children, 0..) |_, i| {
|
|
switch (node.element.children[i]) {
|
|
.element => try getRenderCommandsHelper(node.element.children[i], alloc, command_list),
|
|
.text => |text| try command_list.append(alloc, .{ .text = .{
|
|
.pos = node.element.pos,
|
|
.text = text,
|
|
.z_index = node.element.z_index,
|
|
} }),
|
|
.texture => |texture| try command_list.append(alloc, .{ .texture = .{
|
|
.pos = node.element.pos,
|
|
.texture = texture,
|
|
.z_index = node.element.z_index,
|
|
} }),
|
|
}
|
|
}
|
|
},
|
|
else => return, // the commands get created above, since the text and texture need a pos
|
|
// but they dont have the pos one the parent has it
|
|
}
|
|
}
|
|
|
|
pub fn getRenderCommands(node: Node, alloc: std.mem.Allocator) !std.ArrayList(RenderCommand) {
|
|
var render_commands: std.ArrayList(RenderCommand) = .empty;
|
|
try getRenderCommandsHelper(node, alloc, &render_commands);
|
|
return render_commands;
|
|
}
|
|
|
|
fn computeSizes(node: *Node, parent: *Node) void {
|
|
switch (node.*) {
|
|
.element => {
|
|
// std.debug.print("parent name : {s}\n", .{parent.element.name});
|
|
|
|
for (node.element.children, 0..) |_, i| {
|
|
computeSizes(@constCast(&node.element.children[i]), node);
|
|
}
|
|
|
|
// cheaters way to beat the fence post problem
|
|
// TODO fix this
|
|
switch (node.element.style.layout) {
|
|
.right_to_left => node.element.rect.w -= node.element.style.child_gap,
|
|
.top_to_bottom => node.element.rect.h -= node.element.style.child_gap,
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
closeElement(node, parent);
|
|
}
|
|
|
|
fn filterGrowableChildren(index: *usize, node: Node) ?*Ele {
|
|
switch (node) {
|
|
.element => {
|
|
if (node.element.children.len == 0) return null;
|
|
|
|
var i = index.*;
|
|
while (i < node.element.children.len - 1) : (i += 1) {
|
|
switch (node.element.children[i]) {
|
|
.element => |e| {
|
|
if (e.style.size_x == .grow or e.style.size_y == .grow) {
|
|
index.* = i + 1;
|
|
return @constCast(&node.element.children[i].element);
|
|
}
|
|
},
|
|
else => continue,
|
|
}
|
|
}
|
|
},
|
|
else => return null,
|
|
}
|
|
return null;
|
|
}
|
|
|
|
fn computeRemainingHeightAndWidth(node: Node) struct { Real, Real } {
|
|
// growable code
|
|
// std.debug.print("node.element.rect.w : {d}\n", .{node.element.rect.w});
|
|
var remaining_width = node.element.rect.w;
|
|
var remaining_height = node.element.rect.h;
|
|
// std.debug.print("remaining_width : {d}\n", .{remaining_width});
|
|
// std.debug.print("remaining_hieght : {d}\n", .{remaining_height});
|
|
|
|
remaining_width -= node.element.style.padding.left + node.element.style.padding.right;
|
|
remaining_height -= node.element.style.padding.top + node.element.style.padding.bottom;
|
|
|
|
if (node.element.children.len == 0) return .{
|
|
0,
|
|
0,
|
|
};
|
|
|
|
// std.debug.print("remaining_width : {d}\n", .{remaining_width});
|
|
// std.debug.print("remaining_hieght : {d}\n", .{remaining_height});
|
|
|
|
// are there too many indentaions here maybe
|
|
for (node.element.children) |child| {
|
|
// std.debug.print("child : {}\n", .{child});
|
|
switch (child) {
|
|
.element => {
|
|
remaining_height -= child.element.rect.h;
|
|
remaining_width -= child.element.rect.w;
|
|
// std.debug.print("remaining_width : {d}\n", .{remaining_width});
|
|
// std.debug.print("remaining_hieght : {d}\n", .{remaining_height});
|
|
},
|
|
.texture => {
|
|
switch (node.element.style.layout) {
|
|
.top_to_bottom => {
|
|
remaining_height -= child.texture.rect.h;
|
|
remaining_width = @max(remaining_width, child.texture.rect.w);
|
|
},
|
|
.right_to_left => {
|
|
remaining_width -= child.texture.rect.w;
|
|
remaining_height = @max(remaining_height, child.texture.rect.h);
|
|
},
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
// std.debug.print("remaining_width : {d}\n", .{remaining_width});
|
|
// std.debug.print("remaining_hieght : {d}\n", .{remaining_height});
|
|
remaining_width -= (@as(Real, @floatFromInt(node.element.children.len -| 1)) * node.element.style.child_gap);
|
|
remaining_height -= (@as(Real, @floatFromInt(node.element.children.len -| 1)) * node.element.style.child_gap);
|
|
// std.debug.print("remaining_width : {d}\n", .{remaining_width});
|
|
// std.debug.print("remaining_hieght : {d}\n", .{remaining_height});
|
|
|
|
return .{
|
|
remaining_height,
|
|
remaining_width,
|
|
};
|
|
}
|
|
|
|
pub fn getHeight(element: *const Ele) Real {
|
|
return element.rect.h;
|
|
}
|
|
|
|
pub fn getWidth(element: *const Ele) Real {
|
|
return element.rect.w;
|
|
}
|
|
|
|
pub fn addHeight(element: *Ele, height: Real) void {
|
|
element.rect.h += height;
|
|
}
|
|
|
|
pub fn addWidth(element: *Ele, width: Real) void {
|
|
element.rect.w += width;
|
|
}
|
|
|
|
pub fn getSizingY(element: *const Ele) Sizing {
|
|
return element.style.size_y;
|
|
}
|
|
|
|
pub fn getSizingX(element: *const Ele) Sizing {
|
|
return element.style.size_x;
|
|
}
|
|
|
|
fn growChildIndependedElements(
|
|
node: Node,
|
|
axis_x: bool,
|
|
) void {
|
|
const padding_x = node.element.style.padding.left + node.element.style.padding.right;
|
|
const padding_y = node.element.style.padding.left + node.element.style.padding.right;
|
|
|
|
for (node.element.children, 0..) |child, i| {
|
|
switch (child) {
|
|
.element => |e| {
|
|
if (e.style.size_x == .grow and axis_x) {
|
|
@constCast(&node.element.children[i]).element.rect.w = node.element.rect.w - padding_x;
|
|
}
|
|
if (e.style.size_y == .grow and !axis_x) {
|
|
@constCast(&node.element.children[i]).element.rect.h = node.element.rect.h - padding_y;
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
}
|
|
|
|
fn growChildDependedElements(
|
|
node: Node,
|
|
remaining_disance: Real,
|
|
total_growable: usize,
|
|
getAxis: *const fn (*const Ele) Real,
|
|
addToAxis: *const fn (*Ele, Real) void,
|
|
getSizing: *const fn (*const Ele) Sizing,
|
|
) void {
|
|
// this algorithm is based on the one used in clay : https://github.com/nicbarker/clay
|
|
var remaining = remaining_disance;
|
|
|
|
while (remaining > 0) {
|
|
var find_next_smallest_index: usize = 0;
|
|
var smallest = filterGrowableChildren(&find_next_smallest_index, node) orelse return;
|
|
var second_smallest = &Ele{ .rect = .{
|
|
.h = std.math.floatMax(Real),
|
|
.w = std.math.floatMax(Real),
|
|
} };
|
|
var width_to_add = remaining;
|
|
var next_smallest_index: usize = 0;
|
|
while (filterGrowableChildren(&next_smallest_index, node)) |e| {
|
|
if (getAxis(e) < getAxis(smallest)) {
|
|
second_smallest = smallest;
|
|
smallest = e;
|
|
}
|
|
|
|
if (getAxis(e) > getAxis(smallest)) {
|
|
second_smallest = if (getAxis(second_smallest) < getAxis(e)) second_smallest else e;
|
|
width_to_add = getAxis(second_smallest) - getAxis(smallest);
|
|
}
|
|
}
|
|
width_to_add = @min(width_to_add, remaining / @as(Real, @floatFromInt(total_growable)));
|
|
if (width_to_add == 0) return;
|
|
var grow_em: usize = 0;
|
|
// std.debug.print("smallest ; {}\n", .{smallest.rect});
|
|
while (filterGrowableChildren(&grow_em, node)) |growable| {
|
|
if (getAxis(growable) == getAxis(smallest)) {
|
|
// std.debug.print("{s} before growable : {} + width_to_add {}\n", .{ growable.name, growable.rect, width_to_add });
|
|
if (getSizing(growable) == .grow)
|
|
addToAxis(growable, width_to_add);
|
|
// std.debug.print("after growable : {}\n", .{growable.rect});
|
|
remaining -= width_to_add;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn growChildern(node: *Node) void {
|
|
// check that there are growable children
|
|
var total_growable_index: usize = 0;
|
|
var total_growable: usize = 0;
|
|
while (filterGrowableChildren(&total_growable_index, node.*)) |_| : (total_growable += 1) continue;
|
|
|
|
if (total_growable == 0) return;
|
|
|
|
const remaining_height, const remaining_width = computeRemainingHeightAndWidth(node.*);
|
|
// std.debug.print("remaining_height remaining_width : {} {}\n", .{ remaining_height, remaining_width });
|
|
|
|
if (node.element.style.layout == .top_to_bottom) {
|
|
growChildIndependedElements(node.*, true);
|
|
if (remaining_height > 0) growChildDependedElements(
|
|
node.*,
|
|
remaining_height,
|
|
total_growable,
|
|
&getHeight,
|
|
&addHeight,
|
|
getSizingY,
|
|
);
|
|
}
|
|
|
|
if (node.element.style.layout == .right_to_left) {
|
|
growChildIndependedElements(node.*, false);
|
|
|
|
if (remaining_width > 0) growChildDependedElements(
|
|
node.*,
|
|
remaining_width,
|
|
total_growable,
|
|
&getWidth,
|
|
&addWidth,
|
|
getSizingX,
|
|
);
|
|
}
|
|
}
|
|
|
|
pub fn computeGrowElements(node: *Node) void {
|
|
switch (node.*) {
|
|
.element => {
|
|
growChildern(node);
|
|
for (node.element.children, 0..) |_, i| {
|
|
computeGrowElements(@constCast(&node.element.children[i]));
|
|
}
|
|
},
|
|
else => return,
|
|
}
|
|
}
|
|
|
|
fn computeChildernsPostions(ele: *Ele) void {
|
|
const layout_style = ele.style.layout;
|
|
var children = @constCast(ele.children);
|
|
|
|
switch (layout_style) {
|
|
.right_to_left => {
|
|
var off_set_left: Real = 0;
|
|
for (ele.children, 0..) |_, i| {
|
|
const child_gap: Real = ele.style.child_gap;
|
|
switch (ele.children[i]) {
|
|
.element => {
|
|
// off set it by the parent
|
|
children[i].element.pos.x += ele.pos.x + ele.style.padding.left + off_set_left;
|
|
children[i].element.pos.y += ele.pos.y + ele.style.padding.top;
|
|
off_set_left += children[i].element.rect.w + child_gap;
|
|
// add
|
|
},
|
|
else => continue,
|
|
}
|
|
}
|
|
},
|
|
.top_to_bottom => {
|
|
var offset_top: Real = 0;
|
|
for (ele.children, 0..) |_, i| {
|
|
const child_gap: Real = ele.style.child_gap;
|
|
switch (ele.children[i]) {
|
|
.element => {
|
|
// off set it by the parent
|
|
children[i].element.pos.x += ele.pos.x + ele.style.padding.left;
|
|
children[i].element.pos.y += ele.pos.y + ele.style.padding.top + offset_top;
|
|
offset_top += children[i].element.rect.h + child_gap;
|
|
// add
|
|
},
|
|
else => continue,
|
|
}
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn computePostionsHelper(node: *Node, index: usize) void {
|
|
switch (node.*) {
|
|
.element => {
|
|
computeChildernsPostions(&node.element);
|
|
node.element.z_index = index;
|
|
for (node.element.children, 0..) |_, i| {
|
|
computePostionsHelper(@constCast(&node.element.children[i]), index + 1);
|
|
}
|
|
},
|
|
else => return,
|
|
}
|
|
}
|
|
|
|
pub fn computePositions(node: *Node) void {
|
|
computePostionsHelper(node, 0);
|
|
}
|
|
|
|
// this could be done real quick
|
|
fn filterHoveredNodes(node: Node, pos: Pos) ?*Node {
|
|
switch (node) {
|
|
.element => {
|
|
for (node.element.children, 0..) |value, i| {
|
|
switch (value) {
|
|
.element => |ele| {
|
|
if (pointinRect(pos, ele.pos, ele.rect)) {
|
|
return @constCast(&node.element.children[i]);
|
|
}
|
|
},
|
|
else => continue,
|
|
}
|
|
}
|
|
},
|
|
else => return null,
|
|
}
|
|
return null;
|
|
}
|
|
|
|
pub fn processInteractions(root: *Node, mouse_state: MouseState) bool {
|
|
var needs_redraw = false;
|
|
needs_redraw = true;
|
|
var current_node = root;
|
|
const clicked = mouse_state.left or mouse_state.middle or mouse_state.right;
|
|
// std.debug.print("state : {}\n", .{mouse_state});
|
|
while (filterHoveredNodes(current_node.*, mouse_state.pos)) |node| {
|
|
switch (node.*) {
|
|
.element => |e| {
|
|
// std.debug.print("clicked : {} hovered node name : {s}\n", .{ clicked, e.name });
|
|
|
|
if (e.on_hover != null and e.allow_on_hover_when_occluded) {
|
|
needs_redraw |= node.element.on_hover.?.func(&node.element, mouse_state, node.element.on_click.?.data);
|
|
}
|
|
|
|
if (e.on_click != null and clicked and e.allow_on_click_when_occluded) {
|
|
needs_redraw |= node.element.on_click.?.func(&node.element, mouse_state, node.element.on_click.?.data);
|
|
}
|
|
},
|
|
// .text => |t| std.debug.print("hovered node name : {s}\n", .{t.string}),
|
|
else => {},
|
|
}
|
|
current_node = node;
|
|
}
|
|
|
|
switch (current_node.*) {
|
|
.element => {
|
|
if (current_node.element.on_hover != null and !current_node.element.allow_on_hover_when_occluded) {
|
|
needs_redraw |= current_node.element.on_hover.?.func(
|
|
¤t_node.element,
|
|
mouse_state,
|
|
current_node.element.on_click.?.data,
|
|
);
|
|
}
|
|
|
|
if (current_node.element.on_click != null and clicked and !current_node.element.allow_on_click_when_occluded) {
|
|
needs_redraw |= current_node.element.on_click.?.func(
|
|
¤t_node.element,
|
|
mouse_state,
|
|
current_node.element.on_click.?.data,
|
|
);
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
return needs_redraw;
|
|
}
|
|
|
|
pub fn resolveSizing(alloc: std.mem.Allocator, root: Node) !Node {
|
|
var new_tree: Node = try deepClone(root, alloc);
|
|
|
|
var base_node: Node = Node{ .element = Ele{
|
|
.children = &[_]Node{
|
|
root,
|
|
},
|
|
} };
|
|
|
|
computeSizes(&new_tree, &base_node);
|
|
computeGrowElements(&new_tree);
|
|
computePositions(&new_tree);
|
|
|
|
return new_tree;
|
|
}
|
|
};
|
|
}
|