commit a5f7d377fdf8cd9a17d63bf993cc8aa0bb9fbbb4 Author: LordMZTE Date: Sat Jan 13 19:20:48 2024 +0100 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fe95f8d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/zig-* diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..96a2f9b --- /dev/null +++ b/build.zig @@ -0,0 +1,31 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const zenolith_dep = b.dependency("zenolith", .{ .target = target, .optimize = optimize }); + const zenolith_sdl2_dep = b.dependency("zenolith_sdl2", .{ .target = target, .optimize = optimize }); + + inline for (.{ "themeswitcher", "counter" }) |name| { + const exe = b.addExecutable(.{ + .name = name, + .root_source_file = .{ .path = "src/" ++ name ++ ".zig" }, + .target = target, + .optimize = optimize, + }); + + exe.root_module.addImport("zenolith", zenolith_dep.module("zenolith")); + exe.root_module.addImport("zenolith-sdl2", zenolith_sdl2_dep.module("zenolith-sdl2")); + + b.installArtifact(exe); + + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + + if (b.args) |args| run_cmd.addArgs(args); + + const run_step = b.step("run-" ++ name, "Run the " ++ name ++ " example"); + run_step.dependOn(&run_cmd.step); + } +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..b242f2d --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,19 @@ +.{ + .name = "zenolith-exmaples", + .version = "0.0.0", + + .dependencies = .{ + // Make sure this is the same version as the one used by zenolith-sdl2. + // See: https://git.mzte.de/zenolith/zenolith-sdl2 + .zenolith = .{ + .url = "git+https://git.mzte.de/zenolith/zenolith.git#6292688915f91da3ace4f38caa1dc33c718c8fc0", + .hash = "1220b709a856fe648fd5be6351b319d3626b3e368062d301e666a969032121b5ecde", + }, + .zenolith_sdl2 = .{ + .url = "git+https://git.mzte.de/zenolith/zenolith-sdl2.git#46431dbe65d50c899c3b701e2a3218dbaf1b4c8d", + .hash = "1220e3161064d9eae555ae33e74e0d0bcd07daa9a70b9052dd4094451f4b27ac2e2a", + }, + }, + + .paths = .{""}, +} diff --git a/src/counter.zig b/src/counter.zig new file mode 100644 index 0000000..ba2ec6d --- /dev/null +++ b/src/counter.zig @@ -0,0 +1,187 @@ +const std = @import("std"); +const zl = @import("zenolith"); +const zsdl2 = @import("zenolith-sdl2"); + +pub const zenolith_options = struct { + // Register zenolith-sdl2 platform and painter implementations. + // This is required as Zenolith builds a statspatch type from these. + // See: https://git.mzte.de/lordmzte/statspatch + pub const platform_impls = [_]type{zsdl2.Sdl2Platform}; + pub const painter_impls = [_]type{zsdl2.Sdl2Painter}; + + // Register widget implementations: builtin implementations and our Root widget. + pub const widget_impls = zl.default_widget_impls ++ [_]type{Root}; + + // Enable to draw debug information. + //pub const debug_render = true; +}; + +/// Our root widget type. Most functions in here will be invoked by Zenolith. +const Root = struct { + // We use this box as our only child widget. It will contain our buttons and a label. + box: *zl.widget.Widget, + + // These aren't immediate children, we just save them have to be able to easily reference them. + // Zenolith will take care of freeing these. + sub_btn: *zl.widget.Widget, + label: *zl.widget.Widget, + add_btn: *zl.widget.Widget, + + counter: i32, + + pub fn init(alloc: std.mem.Allocator) !*zl.widget.Widget { + const vbox = try zl.widget.Box.init(alloc, .vertical); + errdefer vbox.deinit(); + + try vbox.addChild(null, try zl.widget.Label.init(alloc, "A simple counter example.")); + try vbox.addChild(null, try zl.widget.Label.init(alloc, "Use the '+' and '-' buttons to change the counter!")); + try vbox.addChild(null, try zl.widget.Spacer.init(alloc, .{ .fixed = .{ .width = 0, .height = 20 } })); + + const hbox = try zl.widget.Box.init(alloc, .horizontal); + + const sub_btn = try zl.widget.Button.init(alloc, "-"); + try hbox.addChild(null, sub_btn); // null => append to end + + // Add a spacer with a flex-expand of 1. This works somewhat like + // (but not completely identical to) CSS. + try hbox.addChild(null, try zl.widget.Spacer.init(alloc, .{ .flex = 1 })); + + const label = try zl.widget.Label.init(alloc, "0"); + try hbox.addChild(null, label); + + try hbox.addChild(null, try zl.widget.Spacer.init(alloc, .{ .flex = 1 })); + + const add_btn = try zl.widget.Button.init(alloc, "+"); + try hbox.addChild(null, add_btn); + + try vbox.addChild(null, hbox); + try vbox.addChild(null, try zl.widget.Spacer.init(alloc, .{ .flex = 1 })); + try vbox.addChild(null, try zl.widget.Label.init(alloc, "Hint: also try the plus and minus keys or tab + space!")); + + const self = Root{ + .box = vbox, + + .sub_btn = sub_btn, + .label = label, + .add_btn = add_btn, + + .counter = 0, + }; + + return try zl.widget.Widget.init(alloc, self); + } + + /// This is called by Zenolith whenever a treevent is fired on our widget tree. + /// We'll handle a few ones, and simply call the default dispatcher on others. + pub fn treevent(self: *Root, selfw: *zl.widget.Widget, tv: anytype) !void { + switch (@TypeOf(tv)) { + // The LayoutSize treevent is fired when a layout path is being done on the Widget tree. + // We'll simply let the Box do it's layout and use the exact same size. + // We force box to use the maximum available size (= the window's size), + // so it takes up all space. + zl.treevent.LayoutSize => { + var tvv = tv; + tvv.constraints = zl.layout.Constraints.tight(tv.constraints.max); + try self.box.treevent(tvv); + selfw.data.size = self.box.data.size; + }, + + // This event is fired when a letter is typed. + *zl.treevent.CharType => { + // Dispatch the key event to children first... + try tv.dispatch(selfw); + + // If no child handled it, and it's a press event.. + if (!tv.handled) { + // ... update the counter accordingly + switch (tv.codepoint) { + '+' => { + self.counter +%= 1; + try self.updateLabel(); + }, + '-' => { + self.counter -%= 1; + try self.updateLabel(); + }, + else => {}, + } + } + }, + + else => try tv.dispatch(selfw), + } + } + + /// Called upon this widget receiving a backevent. Backevents are events which travel the + /// widget tree upwards. Buttons emit the ButtonActivated backevent upon being clicked. + pub fn backevent(self: *Root, selfw: *zl.widget.Widget, be: zl.backevent.Backevent) !void { + if (be.downcast(zl.backevent.ButtonActivated)) |ba| { + if (ba.btn_widget == self.add_btn) { + self.counter +%= 1; + } else if (ba.btn_widget == self.sub_btn) { + self.counter -%= 1; + } + try self.updateLabel(); + } else { + // Dispatch other backevents. + try be.dispatch(selfw); + } + } + + /// This function is required so Zenolith can operate on our child widgets. + pub fn children(self: *Root, selfw: *zl.widget.Widget) []const *zl.widget.Widget { + _ = selfw; + return @as([*]const *zl.widget.Widget, @ptrCast(&self.box))[0..1]; + } + + fn updateLabel(self: *Root) !void { + // update label text + var buf: [64]u8 = undefined; + try self.label.downcast(zl.widget.Label).?.span.?.updateGlyphs(.{ + .text = try std.fmt.bufPrint(&buf, "{}", .{self.counter}), + }); + } +}; + +pub fn main() !void { + const alloc = std.heap.c_allocator; + + // Initialize the SDL2 platform. + var platform = try zsdl2.Sdl2Platform.init(.{ .alloc = alloc }); + defer platform.deinit(); + + // Use the platform to create a font. Font discovery will be supported in a future version. + var font = zl.text.Font.create(try platform.createFont(.{ + .source = .{ .path = "/usr/share/fonts/liberation/LiberationSans-Regular.ttf" }, + }), {}); + defer font.deinit(); + + // Create statspatch-based platform type for use by platform-agnostic code. + var zplatf = zl.platform.Platform.create(platform, .{}); + + const root = try Root.init(alloc); + defer root.deinit(); + + // Create an AttreebuteMap for the root widget. + // Attreebutes are tree-bound attributes, which are inherited to child widgets. + // They're backed by a type-indexed map (think ECS). + { + var attrs = zl.attreebute.AttreebuteMap.init(); + errdefer attrs.deinit(alloc); + + // This is required by various widgets in order to render text. + (try attrs.mod(alloc, zl.attreebute.CurrentFont)).* = .{ .font = &font }; + + // This sets a variety of theming-related attributes to the builtin catppuccin mocha theme. + try zl.Theme.catppuccin_mocha.apply(alloc, &attrs); + + root.data.attreebutes = attrs; + } + + // This links the widget tree (ie. correctly sets parent pointers and updates the platform). + // Some widgets also do initialization here. + try root.link(null, &zplatf); + + // Run the main event loop until the application is closed; + try platform.run(root); +} diff --git a/src/themeswitcher.zig b/src/themeswitcher.zig new file mode 100644 index 0000000..538b8ac --- /dev/null +++ b/src/themeswitcher.zig @@ -0,0 +1,178 @@ +const std = @import("std"); +const zl = @import("zenolith"); +const zsdl2 = @import("zenolith-sdl2"); + +pub const zenolith_options = struct { + // Register zenolith-sdl2 platform and painter implementations. + // This is required as Zenolith builds a statspatch type from these. + // See: https://git.mzte.de/lordmzte/statspatch + pub const platform_impls = [_]type{zsdl2.Sdl2Platform}; + pub const painter_impls = [_]type{zsdl2.Sdl2Painter}; + + // Register widget implementations: builtin implementations and our Root widget. + pub const widget_impls = zl.default_widget_impls ++ [_]type{Root}; + + // Enable to draw debug information. + //pub const debug_render = true; +}; + +/// Our root widget type. Most functions in here will be invoked by Zenolith. +const Root = struct { + // We use this box as our only child widget. It will contain our buttons and a label. + box: *zl.widget.Widget, + + // These aren't immediate children, we just save them have to be able to easily reference them. + // Zenolith will take care of freeing these. + label: *zl.widget.Widget, + latte_btn: *zl.widget.Widget, + frappe_btn: *zl.widget.Widget, + macchiato_btn: *zl.widget.Widget, + mocha_btn: *zl.widget.Widget, + + pub fn init(alloc: std.mem.Allocator) !*zl.widget.Widget { + const vbox = try zl.widget.Box.init(alloc, .vertical); + errdefer vbox.deinit(); + + try vbox.addChild(null, try zl.widget.Label.init(alloc, "An example demonstrating theming in Zenolith.")); + try vbox.addChild(null, try zl.widget.Label.init(alloc, "Use the buttons below to change the theme!")); + try vbox.addChild(null, try zl.widget.Spacer.init(alloc, .{ .fixed = .{ .width = 0, .height = 20 } })); + const label = try zl.widget.Label.init(alloc, "Current Theme: Mocha"); + try vbox.addChild(null, label); + try vbox.addChild(null, try zl.widget.Spacer.init(alloc, .{ .fixed = .{ .width = 0, .height = 20 } })); + + const hbox = try zl.widget.Box.init(alloc, .horizontal); + + const latte_btn = try zl.widget.Button.init(alloc, "Latte"); + const frappe_btn = try zl.widget.Button.init(alloc, "Frappe"); + const macchiato_btn = try zl.widget.Button.init(alloc, "Macchiato"); + const mocha_btn = try zl.widget.Button.init(alloc, "Mocha"); + + try hbox.addChild(null, latte_btn); + try hbox.addChild(null, try zl.widget.Spacer.init(alloc, .{ .flex = 1 })); + try hbox.addChild(null, frappe_btn); + try hbox.addChild(null, try zl.widget.Spacer.init(alloc, .{ .flex = 1 })); + try hbox.addChild(null, macchiato_btn); + try hbox.addChild(null, try zl.widget.Spacer.init(alloc, .{ .flex = 1 })); + try hbox.addChild(null, mocha_btn); + + try vbox.addChild(null, hbox); + + const self = Root{ + .box = vbox, + + .label = label, + .latte_btn = latte_btn, + .frappe_btn = frappe_btn, + .macchiato_btn = macchiato_btn, + .mocha_btn = mocha_btn, + }; + + return try zl.widget.Widget.init(alloc, self); + } + + /// This is called by Zenolith whenever a treevent is fired on our widget tree. + /// We'll handle a few ones, and simply call the default dispatcher on others. + pub fn treevent(self: *Root, selfw: *zl.widget.Widget, tv: anytype) !void { + switch (@TypeOf(tv)) { + // The LayoutSize treevent is fired when a layout path is being done on the Widget tree. + // We'll simply let the Box do it's layout and use the exact same size. + // We force box to use the maximum available size (= the window's size), + // so it takes up all space. + zl.treevent.LayoutSize => { + var tvv = tv; + tvv.constraints = zl.layout.Constraints.tight(tv.constraints.max); + try self.box.treevent(tvv); + selfw.data.size = self.box.data.size; + }, + + else => try tv.dispatch(selfw), + } + } + + /// Called upon this widget receiving a backevent. Backevents are events which travel the + /// widget tree upwards. Buttons emit the ButtonActivated backevent upon being clicked. + pub fn backevent(self: *Root, selfw: *zl.widget.Widget, be: zl.backevent.Backevent) !void { + if (be.downcast(zl.backevent.ButtonActivated)) |ba| { + const theme = if (ba.btn_widget == self.latte_btn) + zl.Theme.catppuccin_latte + else if (ba.btn_widget == self.frappe_btn) + zl.Theme.catppuccin_frappe + else if (ba.btn_widget == self.macchiato_btn) + zl.Theme.catppuccin_macchiato + else if (ba.btn_widget == self.mocha_btn) + zl.Theme.catppuccin_mocha + else + return; + + try theme.apply(selfw.data.allocator, &selfw.data.attreebutes.?); + + const text = if (ba.btn_widget == self.latte_btn) + "Latte" + else if (ba.btn_widget == self.frappe_btn) + "Frappe" + else if (ba.btn_widget == self.macchiato_btn) + "Macchiato" + else if (ba.btn_widget == self.mocha_btn) + "Mocha" + else + return; + // update label text + var buf: [64]u8 = undefined; + try self.label.downcast(zl.widget.Label).?.span.?.updateGlyphs(.{ + .text = try std.fmt.bufPrint(&buf, "Active Theme: {s}", .{text}), + }); + } else { + // Dispatch other backevents. + try be.dispatch(selfw); + } + } + + /// This function is required so Zenolith can operate on our child widgets. + pub fn children(self: *Root, selfw: *zl.widget.Widget) []const *zl.widget.Widget { + _ = selfw; + return @as([*]const *zl.widget.Widget, @ptrCast(&self.box))[0..1]; + } +}; + +pub fn main() !void { + const alloc = std.heap.c_allocator; + + // Initialize the SDL2 platform. + var platform = try zsdl2.Sdl2Platform.init(.{ .alloc = alloc }); + defer platform.deinit(); + + // Use the platform to create a font. Font discovery will be supported in a future version. + var font = zl.text.Font.create(try platform.createFont(.{ + .source = .{ .path = "/usr/share/fonts/liberation/LiberationSans-Regular.ttf" }, + }), {}); + defer font.deinit(); + + // Create statspatch-based platform type for use by platform-agnostic code. + var zplatf = zl.platform.Platform.create(platform, .{}); + + const root = try Root.init(alloc); + defer root.deinit(); + + // Create an AttreebuteMap for the root widget. + // Attreebutes are tree-bound attributes, which are inherited to child widgets. + // They're backed by a type-indexed map (think ECS). + { + var attrs = zl.attreebute.AttreebuteMap.init(); + errdefer attrs.deinit(alloc); + + // This is required by various widgets in order to render text. + (try attrs.mod(alloc, zl.attreebute.CurrentFont)).* = .{ .font = &font }; + + // This sets a variety of theming-related attributes to the builtin catppuccin mocha theme. + try zl.Theme.catppuccin_mocha.apply(alloc, &attrs); + + root.data.attreebutes = attrs; + } + + // This links the widget tree (ie. correctly sets parent pointers and updates the platform). + // Some widgets also do initialization here. + try root.link(null, &zplatf); + + // Run the main event loop until the application is closed; + try platform.run(root); +}