Compare commits

...

3 Commits

Author SHA1 Message Date
8bea8eeda2 added scrollable code 2026-01-22 13:46:52 +13:00
38e37dddc4 can now scroll 2026-01-22 13:46:30 +13:00
61b278ac8f more todos 2026-01-22 13:45:52 +13:00
3 changed files with 361 additions and 244 deletions

View File

@@ -6,7 +6,10 @@
- percentage - percentage
- [ ] allow text rendering to support new lines - [ ] allow text rendering to support new lines
- [ ] possibly allow a more dearimgui approch to rendering - [ ] possibly allow a more dearimgui approch to rendering
- [x] scrollable Node
- [ ] min and max width - [ ] min and max width
- [ ] scrollable Node
- [ ] add ui logic to allow most of it to be hidden - [ ] add ui logic to allow most of it to be hidden
- [ ] add text input fields - [ ] add text input fields
- [ ] fix text rendering

View File

@@ -1,6 +1,6 @@
const std = @import("std"); const std = @import("std");
const rl = @import("rl"); const rl = @import("rl");
const shoots = @import("shoots"); const shoots = @import("root.zig");
fn getTextWidth(string: []const u8, font: rl.Font) shoots.Real { fn getTextWidth(string: []const u8, font: rl.Font) shoots.Real {
var width: c_int = 0; var width: c_int = 0;
@@ -117,6 +117,8 @@ pub fn main() anyerror!void {
std.debug.print("shoots version :{s}\n", .{shoots.VERSION}); std.debug.print("shoots version :{s}\n", .{shoots.VERSION});
std.debug.print("raylib time : {}\n", .{@divTrunc(timer.lap(), std.time.ns_per_ms)}); std.debug.print("raylib time : {}\n", .{@divTrunc(timer.lap(), std.time.ns_per_ms)});
var scroll_offset: shoots.Pos = .{};
const child = &[_]UI.Node{ const child = &[_]UI.Node{
sideBar(), sideBar(),
button(), button(),
@@ -153,183 +155,205 @@ pub fn main() anyerror!void {
}, },
}), }),
// UI.Element(.{ UI.Element(.{
// .name = "child", .name = "child",
// .style = .{ .background_colour = .{ .style = .{ .background_colour = .{
// .b = 255, .b = 255,
// }, .padding = .{ .bottom = 5 } }, }, .padding = .{ .bottom = 5 } },
// .children = &[_]UI.Node{ .children = &[_]UI.Node{
// UI.Element(.{ UI.Element(.{
// .name = "branch1", .name = "branch1",
// // .rect = .{ .w = 200 }, // .rect = .{ .w = 200 },
// .style = .{ .style = .{
// .size_x = .grow, .size_x = .grow,
// .padding = .{ .padding = .{
// .bottom = 5, .bottom = 5,
// .left = 5, .left = 5,
// .top = 5, .top = 5,
// .right = 5, .right = 5,
// }, },
// // .layout = .right_to_left, // .layout = .right_to_left,
// .child_gap = 5, .child_gap = 5,
// }, },
// .children = &[_]UI.Node{ .children = &[_]UI.Node{
// UI.Element(.{ UI.Element(.{
// .name = "branch11", .name = "branch11",
// .style = .{ .style = .{
// .background_colour = .{ .background_colour = .{
// .g = 128, .g = 128,
// .b = 128, .b = 128,
// .r = 128, .r = 128,
// }, },
// .size_x = .grow, .size_x = .grow,
// }, },
// .rect = .{ .rect = .{
// .h = 40, .h = 40,
// .w = 40, .w = 40,
// }, },
// }), }),
// UI.Element(.{ UI.Element(.{
// .name = "branch12", .name = "branch12",
// .style = .{ .style = .{
// .background_colour = .{ .background_colour = .{
// .g = 128, .g = 128,
// .r = 128, .r = 128,
// }, },
// .size_x = .grow, .size_x = .grow,
// }, },
// .rect = .{ .rect = .{
// .h = 40, .h = 40,
// .w = 40, .w = 40,
// }, },
// }), }),
// UI.Element(.{ UI.Element(.{
// .name = "branch13", .name = "branch13",
// .rect = .{ .rect = .{
// .h = 40, .h = 40,
// .w = 40, .w = 40,
// }, },
// .style = .{ .style = .{
// .background_colour = .{ .background_colour = .{
// .g = 90, .g = 90,
// .b = 150, .b = 150,
// }, },
// }, },
// }), }),
// }, },
// }), }),
// UI.Element(.{ UI.Element(.{
// .name = "branch2", .name = "branch2",
// .style = .{ .style = .{
// .background_colour = .{ .background_colour = .{
// .r = 255, .r = 255,
// .g = 128, .g = 128,
// }, },
// .layout = .right_to_left, .layout = .right_to_left,
// .child_gap = 4, .child_gap = 4,
// .padding = .{ .padding = .{
// .left = 10, .left = 10,
// .bottom = 10, .bottom = 10,
// .right = 10, .right = 10,
// .top = 10, .top = 10,
// }, },
// }, },
// .children = &[_]UI.Node{ .children = &[_]UI.Node{
// UI.Element(.{ UI.Element(.{
// .name = "branch21", .name = "branch21",
// .rect = .{ .rect = .{
// .h = 30, .h = 30,
// .w = 30, .w = 30,
// }, },
// .style = .{ .style = .{
// .rounded = 40, .rounded = 40,
// .background_colour = .{ .background_colour = .{
// .r = 255, .r = 255,
// .b = 150, .b = 150,
// }, },
// }, },
// }), }),
// UI.Element(.{ UI.Element(.{
// .name = "branch22", .name = "branch22",
// .rect = .{ .rect = .{
// .h = 30, .h = 30,
// .w = 30, .w = 30,
// }, },
// .style = .{ .style = .{
// .rounded = 40, .rounded = 40,
// .background_colour = .{ .background_colour = .{
// .r = 90, .r = 90,
// .b = 150, .b = 150,
// }, },
// }, },
// }), }),
// UI.Element(.{ UI.Element(.{
// .name = "branch21", .name = "branch21",
// .rect = .{ .rect = .{
// .h = 30, .h = 30,
// .w = 30, .w = 30,
// }, },
// .style = .{ .style = .{
// .rounded = 40, .rounded = 40,
// .background_colour = .{ .background_colour = .{
// .r = 255, .r = 255,
// .b = 150, .b = 150,
// }, },
// }, },
// }), }),
// UI.Element(.{ UI.Element(.{
// .name = "branch22", .name = "branch22",
// .rect = .{ .rect = .{
// .h = 30, .h = 30,
// .w = 30, .w = 30,
// }, },
// .style = .{ .style = .{
// .rounded = 40, .rounded = 40,
// .background_colour = .{ .background_colour = .{
// .r = 90, .r = 90,
// .b = 150, .b = 150,
// }, },
// }, },
// }), }),
// UI.Element(.{ UI.Element(.{
// .name = "branch21", .name = "branch21",
// .rect = .{ .rect = .{
// .h = 30, .h = 30,
// .w = 30, .w = 30,
// }, },
// .style = .{ .style = .{
// .rounded = 40, .rounded = 40,
// .background_colour = .{ .background_colour = .{
// .r = 255, .r = 255,
// .b = 150, .b = 150,
// }, },
// }, },
// }), }),
// UI.Element(.{ UI.Element(.{
// .name = "branch22", .name = "branch22",
// .rect = .{ .rect = .{
// .h = 30, .h = 30,
// .w = 30, .w = 30,
// }, },
// .style = .{ .style = .{
// .rounded = 40, .rounded = 40,
// .background_colour = .{ .background_colour = .{
// .r = 90, .r = 90,
// .b = 150, .b = 150,
// }, },
// }, },
// }), }),
// }, },
// }), }),
// }, },
// }), }),
button(),
button(), UI.Element(.{
UI.Image(.{ .name = "scrollable",
.texture = image_texture, .scrollable = .{
.rect = .{ .dir = .y,
.h = @floatFromInt(image_texture.height), .offset = &scroll_offset,
.w = @floatFromInt(image_texture.width), },
.rect = .{ .h = 250 },
.children = &[_]UI.Node{
UI.Element(
.{
.style = .{
.background_colour = .{
.r = 255,
},
},
.children = &[_]UI.Node{
UI.Image(.{
.texture = image_texture,
.rect = .{
.h = @floatFromInt(image_texture.height),
.w = @floatFromInt(image_texture.width),
},
}),
button(),
button(),
},
},
),
}, },
}), }),
}; };
@@ -391,16 +415,21 @@ pub fn main() anyerror!void {
.left = rl.isMouseButtonDown(.left), .left = rl.isMouseButtonDown(.left),
.middle = rl.isMouseButtonDown(.middle), .middle = rl.isMouseButtonDown(.middle),
.right = rl.isMouseButtonDown(.right), .right = rl.isMouseButtonDown(.right),
.scroll_delta = .{
.y = rl.getMouseWheelMoveV().y,
.x = rl.getMouseWheelMoveV().x,
},
}); });
const commands = try UI.getRenderCommands(sized, al.allocator()); const commands = try UI.getRenderCommands(sized, al.allocator());
std.debug.print("{f}\n", .{sized}); // std.debug.print("{f}\n", .{sized});
// std.debug.print("layout time : {}ms\n", .{ std.debug.print("layout time : {}ms\n", .{
// @as(f32, @floatFromInt(timer.lap())) / @as(f32, @floatFromInt(std.time.ns_per_ms)), @as(f32, @floatFromInt(timer.lap())) / @as(f32, @floatFromInt(std.time.ns_per_ms)),
// }); });
for (commands.items) |command| { for (commands.items) |command| {
// std.debug.print("command : {any}\n", .{command}); // std.debug.print("command : {any}\n", .{command});
rl.clearBackground(.white);
switch (command) { switch (command) {
.rect => |r| { .rect => |r| {
if (r.rounding) |rounding| { if (r.rounding) |rounding| {
@@ -436,15 +465,22 @@ pub fn main() anyerror!void {
}, },
.text => |t| { .text => |t| {
const string = @as([:0]const u8, @ptrCast(t.text.string)); const string = @as([:0]const u8, @ptrCast(t.text.string));
rl.drawText(string, @as(i32, @intFromFloat(t.pos.x)), @as(i32, @intFromFloat(t.pos.y)), @as(i32, @intFromFloat(t.text.getTextHeight())), rl.Color.init( rl.drawText(
t.text.colour.r, string,
t.text.colour.g, @as(i32, @intFromFloat(t.pos.x)),
t.text.colour.b, @as(i32, @intFromFloat(t.pos.y)),
t.text.colour.a, @as(i32, @intFromFloat(t.text.getTextHeight())),
)); rl.Color.init(
t.text.colour.r,
t.text.colour.g,
t.text.colour.b,
t.text.colour.a,
),
);
}, },
.texture => |tex| { .texture => |tex| {
// std.debug.print("{}\n", .{tex.texture.texture}); // std.debug.print("{}\n", .{tex.texture.texture});
// std.debug.print("texture {}\n", .{tex});
rl.drawTexture( rl.drawTexture(
tex.texture.texture, tex.texture.texture,
@as(i32, @intFromFloat(tex.pos.x)), @as(i32, @intFromFloat(tex.pos.x)),
@@ -452,10 +488,28 @@ pub fn main() anyerror!void {
.white, .white,
); );
}, },
.clip_start => |clip| {
// std.debug.print("clip {}\n", .{clip});
rl.beginScissorMode(
@as(i32, @intFromFloat(clip.pos.x)),
@as(i32, @intFromFloat(clip.pos.y)),
@as(i32, @intFromFloat(clip.rect.w)),
@as(i32, @intFromFloat(clip.rect.h)),
);
// rl.drawRectangleRec(rl.Rectangle.init(
// clip.pos.x,
// clip.pos.y,
// clip.rect.w,
// clip.rect.h,
// ), .blue);
},
.clip_end => |_| {
rl.endScissorMode();
},
} }
} }
rl.clearBackground(.white);
_ = al.reset(.retain_capacity); _ = al.reset(.retain_capacity);
// rl.drawRectangle(0, 0, 200, 100, .blue); // rl.drawRectangle(0, 0, 200, 100, .blue);

View File

@@ -176,14 +176,15 @@ pub const Colour = struct {
a: u8 = 255, a: u8 = 255,
}; };
const Dir = enum {
x,
y,
};
pub const Scrollable = struct { pub const Scrollable = struct {
offset: *Real, offset: *Pos,
dir: Dir, child_size: Rect = .{},
scroll_amount: f32 = 10.0,
dir: enum {
x,
y,
// both,
},
}; };
pub const MouseState = struct { pub const MouseState = struct {
@@ -191,6 +192,7 @@ pub const MouseState = struct {
left: bool, left: bool,
right: bool, right: bool,
middle: bool, middle: bool,
scroll_delta: Pos,
}; };
pub fn pointinRect(point: Pos, rect_pos: Pos, rect: Rect) bool { pub fn pointinRect(point: Pos, rect_pos: Pos, rect: Rect) bool {
@@ -238,12 +240,12 @@ pub fn Shoots(
try printWithLevel(writer, level + 1, "pos : {any},\n", .{node.element.pos}); 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, "on_click : {?},\n", .{node.element.on_click});
try printWithLevel(writer, level + 1, "rect : {any},\n", .{node.element.rect}); try printWithLevel(writer, level + 1, "rect : {any},\n", .{node.element.rect});
try printWithLevel(writer, level + 1, "style : .{{\n", .{});
try printWithLevel(writer, level + 1, "on_click : {any},\n", .{node.element.on_click}); try printWithLevel(writer, level + 1, "on_click : {any},\n", .{node.element.on_click});
try printWithLevel(writer, level + 1, "allow_on_click_when_occluded : {any},\n", .{node.element.allow_on_click_when_occluded}); try printWithLevel(writer, level + 1, "allow_on_click_when_occluded : {any},\n", .{node.element.allow_on_click_when_occluded});
try printWithLevel(writer, level + 1, "on_hover : {any},\n", .{node.element.on_hover}); try printWithLevel(writer, level + 1, "on_hover : {any},\n", .{node.element.on_hover});
try printWithLevel(writer, level + 1, "allow_on_hover_when_occluded : {any},\n", .{node.element.allow_on_hover_when_occluded}); try printWithLevel(writer, level + 1, "allow_on_hover_when_occluded : {any},\n", .{node.element.allow_on_hover_when_occluded});
try printWithLevel(writer, level + 1, "scrollable : {?},\n", .{node.element.scrollable}); try printWithLevel(writer, level + 1, "scrollable : {?},\n", .{node.element.scrollable});
try printWithLevel(writer, level + 1, "style : .{{\n", .{});
try node.element.style.printStyle(writer, level + 1); try node.element.style.printStyle(writer, level + 1);
try printWithLevel(writer, level + 1, "children : {{\n", .{}); try printWithLevel(writer, level + 1, "children : {{\n", .{});
for (node.element.children, 0..) |_, i| { for (node.element.children, 0..) |_, i| {
@@ -301,8 +303,8 @@ pub fn Shoots(
rect, rect,
text, text,
texture, texture,
// clip_start, clip_start,
// clip_end, clip_end,
}; };
pub const RenderCommand = union(RenderCommandType) { pub const RenderCommand = union(RenderCommandType) {
@@ -323,13 +325,16 @@ pub fn Shoots(
z_index: usize = 0, z_index: usize = 0,
pos: Pos, pos: Pos,
}, },
// clip_start: struct { clip_start: struct {
// pos: Pos, pos: Pos,
// z_index: usize = 0, z_index: usize = 0,
// rect: Rect, rect: Rect,
// rounding: ?Real, // might allow rounded rectangles for clipping,
// }, // hover this would require the user to implement a
// clip_end: struct {}, // stencil buffer and that would be annoying
// rounding: ?Real,
},
clip_end: struct {},
}; };
pub inline fn ElementWborder( pub inline fn ElementWborder(
@@ -420,14 +425,49 @@ pub fn Shoots(
// std.debug.print("parent.element.rect.w : {}\n", .{parent.element.rect.w}); // std.debug.print("parent.element.rect.w : {}\n", .{parent.element.rect.w});
}, },
.texture => { .texture => {
std.debug.print("before {any}\n", .{parent.element.rect});
parent.element.rect.h += node.texture.rect.h; parent.element.rect.h += node.texture.rect.h;
parent.element.rect.w += node.texture.rect.w; parent.element.rect.w += node.texture.rect.w;
std.debug.print("after {any}\n", .{parent.element.rect});
}, },
} }
} }
fn computeSizes(node: *Node, parent: *Node) void {
switch (node.*) {
.element => {
// std.debug.print("before {s} {}\n", .{ node.element.name, node.element.rect });
// std.debug.print("parent name : {s}\n", .{parent.element.name});
const size = node.element.rect;
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,
}
if (node.element.scrollable) |scroll| {
// have to remove the parents size
node.element.scrollable.?.child_size = node.element.rect;
node.element.scrollable.?.child_size.w -= size.w;
node.element.scrollable.?.child_size.h -= size.h;
switch (scroll.dir) {
// .both => node.element.rect = size, // retain size
.x => node.element.rect.w = size.w, // scroll in the x then the hieght stays the same
.y => node.element.rect.h = size.h, // scroll in the y then the width stays the same
}
}
// std.debug.print("after {s} {}\n", .{ node.element.name, node.element.rect });
},
else => {},
}
closeElement(node, parent);
}
fn deepCloneHelper(node: Node, node_location: *Node, alloc: std.mem.Allocator) !void { fn deepCloneHelper(node: Node, node_location: *Node, alloc: std.mem.Allocator) !void {
// std.debug.print("nodeloc : {}\n", .{node_location}); // std.debug.print("nodeloc : {}\n", .{node_location});
node_location.* = node; node_location.* = node;
@@ -473,15 +513,26 @@ pub fn Shoots(
switch (node) { switch (node) {
.element => { .element => {
// std.debug.print("parent name : {s}\n", .{parent.element.name}); // std.debug.print("parent name : {s}\n", .{parent.element.name});
try command_list.append(alloc, .{
.rect = .{ if (node.element.scrollable) |_| {
.colour = node.element.style.background_colour, try command_list.append(alloc, .{
.pos = node.element.pos, .clip_start = .{
.rect = node.element.rect, .rect = node.element.rect,
.rounding = node.element.style.rounded, .z_index = node.element.z_index,
.z_index = node.element.z_index, .pos = node.element.pos,
}, },
}); });
} else {
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| { for (node.element.children, 0..) |_, i| {
switch (node.element.children[i]) { switch (node.element.children[i]) {
@@ -500,6 +551,12 @@ pub fn Shoots(
}), }),
} }
} }
if (node.element.scrollable) |_| {
try command_list.append(alloc, .{
.clip_end = .{},
});
}
}, },
else => return, // the commands get created above, since the text and texture need a pos 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 // but they dont have the pos one the parent has it
@@ -512,27 +569,6 @@ pub fn Shoots(
return 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 { fn filterGrowableChildren(index: *usize, node: Node) ?*Ele {
switch (node) { switch (node) {
.element => { .element => {
@@ -758,6 +794,14 @@ pub fn Shoots(
fn computeChildernsPostions(ele: *Ele) void { fn computeChildernsPostions(ele: *Ele) void {
const layout_style = ele.style.layout; const layout_style = ele.style.layout;
var children = @constCast(ele.children); var children = @constCast(ele.children);
const scroll_offset: Pos = blk: {
if (ele.scrollable) |scroll| {
const scroll_offset = scroll.offset.*;
break :blk scroll_offset;
}
break :blk .{};
};
switch (layout_style) { switch (layout_style) {
.right_to_left => { .right_to_left => {
@@ -767,8 +811,8 @@ pub fn Shoots(
switch (ele.children[i]) { switch (ele.children[i]) {
.element => { .element => {
// off set it by the parent // 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.x += ele.pos.x + ele.style.padding.left + off_set_left - scroll_offset.x;
children[i].element.pos.y += ele.pos.y + ele.style.padding.top; children[i].element.pos.y += ele.pos.y + ele.style.padding.top - scroll_offset.y;
off_set_left += children[i].element.rect.w + child_gap; off_set_left += children[i].element.rect.w + child_gap;
// add // add
}, },
@@ -783,8 +827,8 @@ pub fn Shoots(
switch (ele.children[i]) { switch (ele.children[i]) {
.element => { .element => {
// off set it by the parent // off set it by the parent
children[i].element.pos.x += ele.pos.x + ele.style.padding.left; children[i].element.pos.x += ele.pos.x + ele.style.padding.left - scroll_offset.x;
children[i].element.pos.y += ele.pos.y + ele.style.padding.top + offset_top; children[i].element.pos.y += ele.pos.y + ele.style.padding.top + offset_top - scroll_offset.y;
offset_top += children[i].element.rect.h + child_gap; offset_top += children[i].element.rect.h + child_gap;
// add // add
}, },
@@ -843,6 +887,22 @@ pub fn Shoots(
.element => |e| { .element => |e| {
// std.debug.print("clicked : {} hovered node name : {s}\n", .{ clicked, e.name }); // std.debug.print("clicked : {} hovered node name : {s}\n", .{ clicked, e.name });
// TODO fix when another scrollable contaier occludes the other one
if (current_node.element.scrollable) |scroll| {
current_node.element.scrollable.?.offset.x += mouse_state.scroll_delta.x * scroll.child_size.w / current_node.element.rect.w;
current_node.element.scrollable.?.offset.y += mouse_state.scroll_delta.y * scroll.child_size.h / current_node.element.rect.h;
// fixes being able to scroll past the min value
if (scroll.offset.x < 0) current_node.element.scrollable.?.offset.x = 0;
if (scroll.offset.y < 0) current_node.element.scrollable.?.offset.y = 0;
// fixes being able to scroll past the max value
if (scroll.offset.x > scroll.child_size.w - current_node.element.rect.w)
current_node.element.scrollable.?.offset.x = scroll.child_size.w - current_node.element.rect.w;
if (scroll.offset.y > scroll.child_size.h - current_node.element.rect.h)
current_node.element.scrollable.?.offset.y = scroll.child_size.h - current_node.element.rect.h;
}
if (e.on_hover != null and e.allow_on_hover_when_occluded) { 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); needs_redraw |= node.element.on_hover.?.func(&node.element, mouse_state, node.element.on_click.?.data);
} }