Immediate mode renderer for libvaxis
at main 6.5 kB view raw
1// This can contain internal events as well as Vaxis events. 2// Internal events can be posted into the same queue as vaxis events to allow 3// for a single event loop with exhaustive switching. Booya 4const Event = union(enum) { 5 key_press: vaxis.Key, 6 winsize: vaxis.Winsize, 7 mouse: vaxis.Mouse, 8 mouse_focus: vaxis.Mouse, 9}; 10 11const Widget = enum(u32) { 12 FileMenu, 13 AboutButton, 14 AboutWindow, 15 QuitButton, 16 CloseAboutButton, 17 ClickMe, 18 CounterModal, 19 ScrollDemoWindow, 20 ScrollDemoContent, 21}; 22 23const Vxim = vxim.Vxim(Event, Widget); 24 25const Menu = enum { 26 File, 27}; 28 29const Window = enum { 30 About, 31}; 32 33const State = struct { 34 mouse: ?vaxis.Mouse = null, 35 clicks: usize = 0, 36 open_menu: ?Menu = null, 37 open_window: ?Window = null, 38 about_window_pos: struct { x: u16, y: u16 } = .{ .x = 0, .y = 1 }, 39 counter_window_pos: struct { x: u16, y: u16 } = .{ .x = 30, .y = 10 }, 40 scroll_window_pos: struct { x: u16, y: u16 } = .{ .x = 4, .y = 2 }, 41 v_scroll_offset: usize = 0, 42 h_scroll_offset: usize = 0, 43}; 44 45var state: State = .{}; 46 47var debug_allocator: std.heap.DebugAllocator(.{}) = .init; 48 49pub fn main() !void { 50 const gpa, const is_debug = gpa: { 51 break :gpa switch (builtin.mode) { 52 .Debug, .ReleaseSafe => .{ debug_allocator.allocator(), true }, 53 .ReleaseFast, .ReleaseSmall => .{ std.heap.smp_allocator, false }, 54 }; 55 }; 56 defer if (is_debug) { 57 _ = debug_allocator.deinit(); 58 }; 59 60 var vx: Vxim = .init(gpa); 61 defer vx.deinit(gpa); 62 63 try vx.enterAltScreen(); 64 65 try vx.startLoop(gpa, update); 66} 67 68pub fn update(ctx: Vxim.UpdateContext) anyerror!Vxim.UpdateResult { 69 switch (ctx.current_event) { 70 .key_press => |key| { 71 if (key.matches('c', .{ .ctrl = true })) 72 return .stop; 73 }, 74 .mouse => |mouse| state.mouse = mouse, 75 76 .winsize => {}, 77 .mouse_focus => |_| {}, 78 } 79 80 ctx.root_win.clear(); 81 82 // Counter window. 83 { 84 const modal_width = @min(25, ctx.root_win.width); 85 const modal_height = @min(7, ctx.root_win.height); 86 const modal = ctx.vxim.window(.CounterModal, ctx.root_win, .{ 87 .width = modal_width, 88 .height = modal_height, 89 .x = &state.counter_window_pos.x, 90 .y = &state.counter_window_pos.y, 91 .title = "Counter demo", 92 }); 93 const button_text = "Click Me!"; 94 95 const button_x: u16 = 96 (modal_width / 2) -| ((@as(u16, @truncate(button_text.len)) + 2) / 2) -| 1; 97 const button_y: u16 = (modal_height / 2); 98 99 const button_action = 100 ctx.vxim.button( 101 .ClickMe, 102 modal, 103 .{ .x = button_x, .y = button_y, .text = button_text }, 104 ); 105 106 if (button_action == .clicked) state.clicks +|= 1; 107 108 const text = try std.fmt.allocPrint(ctx.vxim.arena(), "Clicks: {d}", .{state.clicks}); 109 const text_x: u16 = (modal_width / 2) -| (@as(u16, @truncate(text.len)) / 2) -| 1; 110 const text_y: u16 = (modal_height / 2) -| 2; 111 112 ctx.vxim.text(modal, .{ .text = text, .x = text_x, .y = text_y, .allow_selection = true }); 113 } 114 115 // Scroll demo 116 { 117 const scroll_window = ctx.vxim.window(.ScrollDemoWindow, ctx.root_win, .{ 118 .x = &state.scroll_window_pos.x, 119 .y = &state.scroll_window_pos.y, 120 .height = 12, 121 .width = 22, 122 .title = "Scroll demo", 123 }); 124 const content_height = 50; 125 const scroll_body = ctx.vxim.scrollArea(.ScrollDemoContent, scroll_window, .{ 126 .content_height = content_height, 127 .content_width = 30, 128 .v_content_offset = &state.v_scroll_offset, 129 .h_content_offset = &state.h_scroll_offset, 130 }); 131 132 // It's sufficient to draw from the top of the scroll area. 133 for (state.v_scroll_offset..content_height) |i| { 134 // No need to draw outside the scroll area. 135 if (i > state.v_scroll_offset + scroll_body.height) break; 136 137 const text = try std.fmt.allocPrint(ctx.vxim.arena(), "line: {d}", .{i + 1}); 138 139 if (text.len <= state.h_scroll_offset) continue; 140 141 ctx.vxim.text(scroll_body, .{ 142 .y = @as(u16, @intCast(i -| state.v_scroll_offset)), 143 .text = text[state.h_scroll_offset..], 144 }); 145 } 146 } 147 148 // About window 149 if (state.open_window) |open_window| { 150 if (open_window == .About) { 151 const about_win = ctx.vxim.window(.AboutWindow, ctx.root_win, .{ 152 .width = @min(ctx.root_win.width, 35), 153 .height = @min(ctx.root_win.height, 11), 154 .x = &state.about_window_pos.x, 155 .y = &state.about_window_pos.y, 156 .title = "About this program", 157 }); 158 159 const about_body = ctx.vxim.padding(about_win, .{ .all = 1 }); 160 161 const close = ctx.vxim.button( 162 .CloseAboutButton, 163 about_body, 164 .{ .x = about_body.width / 2 -| 3, .y = about_body.height -| 1, .text = "Close" }, 165 ); 166 if (close == .clicked) { 167 state.open_window = null; 168 } 169 170 ctx.vxim.text( 171 about_body, 172 // Extra space to center text in window. 173 .{ .text = " VXIM v0.0.0", .allow_selection = true }, 174 ); 175 ctx.vxim.text( 176 about_body, 177 .{ 178 .text = "Experimental immediate mode renderer for libvaxis", 179 .y = 2, 180 .allow_selection = true, 181 }, 182 ); 183 } 184 } 185 186 const menuAction = ctx.vxim.menuBar(ctx.root_win, &.{ 187 .{ 188 .name = "File", 189 .id = .FileMenu, 190 .items = &.{ 191 .{ .name = "About", .id = .AboutButton }, 192 .{ .name = "Quit", .id = .QuitButton }, 193 }, 194 }, 195 }); 196 197 if (menuAction) |action| { 198 if (action.id == .AboutButton and action.action == .clicked) state.open_window = .About; 199 if (action.id == .QuitButton and action.action == .clicked) return .stop; 200 } 201 202 return .keep_going; 203} 204 205const std = @import("std"); 206const builtin = @import("builtin"); 207 208const vaxis = @import("vaxis"); 209const vxim = @import("vxim");