Kenneth Bruen
9 months ago
commit
e0a61196ce
8 changed files with 529 additions and 0 deletions
@ -0,0 +1,18 @@ |
|||||||
|
# This file is for zig-specific build artifacts. |
||||||
|
# If you have OS-specific or editor-specific files to ignore, |
||||||
|
# such as *.swp or .DS_Store, put those in your global |
||||||
|
# ~/.gitignore and put this in your ~/.gitconfig: |
||||||
|
# |
||||||
|
# [core] |
||||||
|
# excludesfile = ~/.gitignore |
||||||
|
# |
||||||
|
# Cheers! |
||||||
|
# -andrewrk |
||||||
|
|
||||||
|
zig-cache/ |
||||||
|
zig-out/ |
||||||
|
/release/ |
||||||
|
/debug/ |
||||||
|
/build/ |
||||||
|
/build-*/ |
||||||
|
/docgen_tmp/ |
@ -0,0 +1,90 @@ |
|||||||
|
const std = @import("std"); |
||||||
|
|
||||||
|
// Although this function looks imperative, note that its job is to |
||||||
|
// declaratively construct a build graph that will be executed by an external |
||||||
|
// runner. |
||||||
|
pub fn build(b: *std.Build) void { |
||||||
|
// Standard target options allows the person running `zig build` to choose |
||||||
|
// what target to build for. Here we do not override the defaults, which |
||||||
|
// means any target is allowed, and the default is native. Other options |
||||||
|
// for restricting supported target set are available. |
||||||
|
const target = b.standardTargetOptions(.{}); |
||||||
|
|
||||||
|
// Standard optimization options allow the person running `zig build` to select |
||||||
|
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not |
||||||
|
// set a preferred release mode, allowing the user to decide how to optimize. |
||||||
|
const optimize = b.standardOptimizeOption(.{}); |
||||||
|
|
||||||
|
const exe = b.addExecutable(.{ |
||||||
|
.name = "raylib-test", |
||||||
|
// In this case the main source file is merely a path, however, in more |
||||||
|
// complicated build scripts, this could be a generated file. |
||||||
|
.root_source_file = .{ .path = "src/main.zig" }, |
||||||
|
.target = target, |
||||||
|
.optimize = optimize, |
||||||
|
}); |
||||||
|
|
||||||
|
// Link Raylib |
||||||
|
exe.addLibraryPath(.{ |
||||||
|
.cwd_relative = "/opt/homebrew/Cellar/raylib/5.0/lib", |
||||||
|
}); |
||||||
|
exe.linkSystemLibrary("raylib"); |
||||||
|
exe.linkSystemLibrary("curl"); |
||||||
|
// exe.addObjectFile(.{ |
||||||
|
// .cwd_relative = "/opt/homebrew/Cellar/raylib/5.0/lib/libraylib.a", |
||||||
|
// }); |
||||||
|
exe.addIncludePath(.{ |
||||||
|
.cwd_relative = "/opt/homebrew/Cellar/raylib/5.0/include", |
||||||
|
}); |
||||||
|
// Raylib dependencies |
||||||
|
exe.linkFramework("Foundation"); |
||||||
|
exe.linkFramework("CoreVideo"); |
||||||
|
exe.linkFramework("IOKit"); |
||||||
|
exe.linkFramework("Cocoa"); |
||||||
|
exe.linkFramework("GLUT"); |
||||||
|
exe.linkFramework("OpenGL"); |
||||||
|
|
||||||
|
// This declares intent for the executable to be installed into the |
||||||
|
// standard location when the user invokes the "install" step (the default |
||||||
|
// step when running `zig build`). |
||||||
|
b.installArtifact(exe); |
||||||
|
|
||||||
|
// This *creates* a Run step in the build graph, to be executed when another |
||||||
|
// step is evaluated that depends on it. The next line below will establish |
||||||
|
// such a dependency. |
||||||
|
const run_cmd = b.addRunArtifact(exe); |
||||||
|
|
||||||
|
// By making the run step depend on the install step, it will be run from the |
||||||
|
// installation directory rather than directly from within the cache directory. |
||||||
|
// This is not necessary, however, if the application depends on other installed |
||||||
|
// files, this ensures they will be present and in the expected location. |
||||||
|
run_cmd.step.dependOn(b.getInstallStep()); |
||||||
|
|
||||||
|
// This allows the user to pass arguments to the application in the build |
||||||
|
// command itself, like this: `zig build run -- arg1 arg2 etc` |
||||||
|
if (b.args) |args| { |
||||||
|
run_cmd.addArgs(args); |
||||||
|
} |
||||||
|
|
||||||
|
// This creates a build step. It will be visible in the `zig build --help` menu, |
||||||
|
// and can be selected like this: `zig build run` |
||||||
|
// This will evaluate the `run` step rather than the default, which is "install". |
||||||
|
const run_step = b.step("run", "Run the app"); |
||||||
|
run_step.dependOn(&run_cmd.step); |
||||||
|
|
||||||
|
// Creates a step for unit testing. This only builds the test executable |
||||||
|
// but does not run it. |
||||||
|
const unit_tests = b.addTest(.{ |
||||||
|
.root_source_file = .{ .path = "src/main.zig" }, |
||||||
|
.target = target, |
||||||
|
.optimize = optimize, |
||||||
|
}); |
||||||
|
|
||||||
|
const run_unit_tests = b.addRunArtifact(unit_tests); |
||||||
|
|
||||||
|
// Similar to creating the run step earlier, this exposes a `test` step to |
||||||
|
// the `zig build --help` menu, providing a way for the user to request |
||||||
|
// running the unit tests. |
||||||
|
const test_step = b.step("test", "Run unit tests"); |
||||||
|
test_step.dependOn(&run_unit_tests.step); |
||||||
|
} |
@ -0,0 +1,73 @@ |
|||||||
|
pub const c_api = @cImport({ |
||||||
|
@cInclude("curl/curl.h"); |
||||||
|
}); |
||||||
|
|
||||||
|
pub const Option = enum(c_api.CURLoption) { |
||||||
|
write_data = c_api.CURLOPT_WRITEDATA, |
||||||
|
url = c_api.CURLOPT_URL, |
||||||
|
port = c_api.CURLOPT_PORT, |
||||||
|
proxy = c_api.CURLOPT_PROXY, |
||||||
|
userpwd = c_api.CURLOPT_USERPWD, |
||||||
|
proxy_userpwd = c_api.CURLOPT_PROXYUSERPWD, |
||||||
|
range = c_api.CURLOPT_RANGE, |
||||||
|
read_data = c_api.CURLOPT_READDATA, |
||||||
|
error_buffer = c_api.CURLOPT_ERRORBUFFER, |
||||||
|
write_function = c_api.CURLOPT_WRITEFUNCTION, |
||||||
|
read_function = c_api.CURLOPT_READFUNCTION, |
||||||
|
timeout = c_api.CURLOPT_TIMEOUT, |
||||||
|
in_file_size = c_api.CURLOPT_INFILESIZE, |
||||||
|
post_fields = c_api.CURLOPT_POSTFIELDS, |
||||||
|
referer = c_api.CURLOPT_REFERER, |
||||||
|
ftp_port = c_api.CURLOPT_FTPPORT, |
||||||
|
user_agent = c_api.CURLOPT_USERAGENT, |
||||||
|
low_speed_limit = c_api.CURLOPT_LOW_SPEED_LIMIT, |
||||||
|
low_speed_time = c_api.CURLOPT_LOW_SPEED_TIME, |
||||||
|
resume_from = c_api.CURLOPT_RESUME_FROM, |
||||||
|
cookie = c_api.CURLOPT_COOKIE, |
||||||
|
http_header = c_api.CURLOPT_HTTPHEADER, |
||||||
|
// ... |
||||||
|
}; |
||||||
|
|
||||||
|
handle: *c_api.CURL, |
||||||
|
|
||||||
|
pub fn init() ?@This() { |
||||||
|
if (c_api.curl_easy_init()) |handle| { |
||||||
|
return .{ |
||||||
|
.handle = handle, |
||||||
|
}; |
||||||
|
} else { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void { |
||||||
|
c_api.curl_easy_cleanup(self.handle); |
||||||
|
} |
||||||
|
|
||||||
|
pub fn reset(self: *@This()) void { |
||||||
|
c_api.curl_easy_reset(self.handle); |
||||||
|
} |
||||||
|
|
||||||
|
pub fn perform(self: *@This()) c_api.CURLcode { |
||||||
|
return c_api.curl_easy_perform(self.handle); |
||||||
|
} |
||||||
|
|
||||||
|
pub fn setopt_raw( |
||||||
|
self: *@This(), |
||||||
|
option: c_api.CURLoption, |
||||||
|
args: anytype, |
||||||
|
) c_api.CURLcode { |
||||||
|
return @call( |
||||||
|
.auto, |
||||||
|
c_api.curl_easy_setopt, |
||||||
|
.{ self.handle, option } ++ args, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
pub fn setopt( |
||||||
|
self: *@This(), |
||||||
|
option: Option, |
||||||
|
args: anytype, |
||||||
|
) c_api.CURLcode { |
||||||
|
return self.setopt_raw(@intFromEnum(option), args); |
||||||
|
} |
@ -0,0 +1,22 @@ |
|||||||
|
const raylib = @import("raylib.zig"); |
||||||
|
const rl = raylib.rl; |
||||||
|
const stateMod = @import("state.zig"); |
||||||
|
|
||||||
|
pub fn render(state: *stateMod.AppState) !void { |
||||||
|
while (raylib.GetKeyPressed()) |key| { |
||||||
|
switch (key) { |
||||||
|
rl.KEY_LEFT => { |
||||||
|
state.screen = .home; |
||||||
|
}, |
||||||
|
else => {}, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
rl.BeginDrawing(); |
||||||
|
defer rl.EndDrawing(); |
||||||
|
|
||||||
|
rl.ClearBackground(raylib.ColorInt(0x18226f)); |
||||||
|
rl.DrawText(state.departure_screen_state.station_id.items.ptr, 16, 16, 32, rl.WHITE); |
||||||
|
|
||||||
|
state.close_app = rl.WindowShouldClose(); |
||||||
|
} |
@ -0,0 +1,192 @@ |
|||||||
|
const std = @import("std"); |
||||||
|
const raylib = @import("raylib.zig"); |
||||||
|
const rl = raylib.rl; |
||||||
|
const state_mod = @import("state.zig"); |
||||||
|
const curl_mod = @import("curl.zig"); |
||||||
|
|
||||||
|
fn curlWriteHandler(ptr: [*]u8, size: usize, nmemb: usize, userdata: *std.ArrayList(u8)) callconv(.C) usize { |
||||||
|
_ = size; |
||||||
|
userdata.appendSlice(ptr[0..nmemb]) catch return 0; |
||||||
|
return nmemb; |
||||||
|
} |
||||||
|
|
||||||
|
fn fetchThread(state: *state_mod.AppState) !void { |
||||||
|
std.debug.print("Started fetchThread\n", .{}); |
||||||
|
defer std.debug.print("Ended fetchThread\n", .{}); |
||||||
|
defer state.home_screen_state.fetch_thread = null; |
||||||
|
const allocator = state.allocator; |
||||||
|
var station_name_buf = std.BoundedArray(u8, 200){}; |
||||||
|
var curl = curl_mod.init() orelse return; |
||||||
|
defer curl.deinit(); |
||||||
|
const locations_base = "https://v6.db.transport.rest/locations"; |
||||||
|
var locations_uri = std.Uri.parse(locations_base) catch unreachable; |
||||||
|
|
||||||
|
while (state.home_screen_state.fetch_thread != null) { |
||||||
|
if (std.mem.eql(u8, station_name_buf.slice(), state.home_screen_state.station_name.items)) { |
||||||
|
std.time.sleep(100 * 1000); |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
station_name_buf.resize(state.home_screen_state.station_name.items.len) catch continue; |
||||||
|
std.mem.copyForwards(u8, station_name_buf.slice(), state.home_screen_state.station_name.items); |
||||||
|
|
||||||
|
std.debug.print("[fetchThread] Detected update: {s}\n", .{station_name_buf.slice()}); |
||||||
|
|
||||||
|
curl.reset(); |
||||||
|
|
||||||
|
const query = try std.fmt.allocPrint(allocator, "query={s}&results=10&addresses=false&poi=false&pretty=false", .{station_name_buf.slice()}); |
||||||
|
defer allocator.free(query); |
||||||
|
locations_uri.query = query; |
||||||
|
defer locations_uri.query = null; |
||||||
|
std.debug.print("[fetchThread] Making request to: {}\n", .{locations_uri}); |
||||||
|
|
||||||
|
const url = try std.fmt.allocPrintZ(allocator, "{}", .{locations_uri}); |
||||||
|
defer allocator.free(url); |
||||||
|
_ = curl.setopt(.url, .{url.ptr}); |
||||||
|
|
||||||
|
var result = std.ArrayList(u8).init(allocator); |
||||||
|
defer result.deinit(); |
||||||
|
_ = curl.setopt(.write_function, .{curlWriteHandler}); |
||||||
|
_ = curl.setopt(.write_data, .{&result}); |
||||||
|
|
||||||
|
const code = curl.perform(); |
||||||
|
std.debug.print("[fetchThread] cURL Code: {}\n", .{code}); |
||||||
|
if (code != 0) continue; |
||||||
|
|
||||||
|
std.debug.print("[fetchThread] Fetched data: <redacted>(len: {})\n", .{result.items.len}); |
||||||
|
const parsed = std.json.parseFromSlice([]const std.json.Value, allocator, result.items, .{}) catch |err| { |
||||||
|
std.debug.print("[fetchThread] JSON parse error: {}\n", .{err}); |
||||||
|
continue; |
||||||
|
}; |
||||||
|
defer parsed.deinit(); |
||||||
|
|
||||||
|
var results = std.ArrayList(state_mod.HSSuggestion).init(allocator); |
||||||
|
for (parsed.value) |station| { |
||||||
|
if (station.object.get("name")) |nameValue| { |
||||||
|
const name = nameValue.string; |
||||||
|
if (station.object.get("id")) |idValue| { |
||||||
|
const id = idValue.string; |
||||||
|
|
||||||
|
results.append(.{ |
||||||
|
.id = std.fmt.allocPrintZ(allocator, "{s}", .{id}) catch continue, |
||||||
|
.name = std.fmt.allocPrintZ(allocator, "{s}", .{name}) catch continue, |
||||||
|
}) catch continue; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
if (state.home_screen_state.suggestions.len > 0) { |
||||||
|
for (state.home_screen_state.suggestions) |suggestion| { |
||||||
|
allocator.free(suggestion.id); |
||||||
|
allocator.free(suggestion.name); |
||||||
|
} |
||||||
|
allocator.free(state.home_screen_state.suggestions); |
||||||
|
} |
||||||
|
state.home_screen_state.suggestions = results.toOwnedSlice() catch continue; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub fn render(state: *state_mod.AppState) !void { |
||||||
|
var hs = &state.home_screen_state; |
||||||
|
|
||||||
|
if (hs.fetch_thread == null) { |
||||||
|
hs.fetch_thread = std.Thread.spawn(.{}, fetchThread, .{state}) catch null; |
||||||
|
} |
||||||
|
if (hs.suggestions.len > 0 and hs.selection_idx > hs.suggestions.len - 1) { |
||||||
|
hs.selection_idx = @intCast(hs.suggestions.len - 1); |
||||||
|
} |
||||||
|
|
||||||
|
while (raylib.GetCharPressed()) |char| { |
||||||
|
hs.station_name.appendAssumeCapacity(@intCast(char)); |
||||||
|
} |
||||||
|
while (raylib.GetKeyPressed()) |key| { |
||||||
|
switch (key) { |
||||||
|
rl.KEY_BACKSPACE => { |
||||||
|
if (hs.station_name.items.len > 0) { |
||||||
|
hs.station_name.items[hs.station_name.items.len - 1] = 0; |
||||||
|
_ = hs.station_name.pop(); |
||||||
|
} |
||||||
|
}, |
||||||
|
rl.KEY_UP => { |
||||||
|
hs.selection_idx -= 1; |
||||||
|
if (hs.suggestions.len > 0 and hs.selection_idx < 0) { |
||||||
|
hs.selection_idx = @intCast(hs.suggestions.len - 1); |
||||||
|
} |
||||||
|
}, |
||||||
|
rl.KEY_DOWN => { |
||||||
|
hs.selection_idx += 1; |
||||||
|
if (hs.suggestions.len > 0 and hs.selection_idx > hs.suggestions.len - 1) { |
||||||
|
hs.selection_idx = 0; |
||||||
|
} |
||||||
|
}, |
||||||
|
rl.KEY_ENTER => { |
||||||
|
if (hs.suggestions.len > 0 and hs.selection_idx < hs.suggestions.len) { |
||||||
|
state.departure_screen_state.station_id.clearRetainingCapacity(); |
||||||
|
state.departure_screen_state.station_id.appendSliceAssumeCapacity(hs.suggestions[@intCast(hs.selection_idx)].id); |
||||||
|
state.screen = .departure; |
||||||
|
hs.fetch_thread = null; |
||||||
|
} |
||||||
|
}, |
||||||
|
else => {}, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
rl.BeginDrawing(); |
||||||
|
defer rl.EndDrawing(); |
||||||
|
|
||||||
|
var x: c_int = 16; |
||||||
|
var y: c_int = 16; |
||||||
|
|
||||||
|
const title_size: c_int = 32; |
||||||
|
const body_size: c_int = 28; |
||||||
|
|
||||||
|
rl.ClearBackground(rl.BLACK); |
||||||
|
x += raylib.DrawAndMeasureText("Station: ", x, y, title_size, rl.WHITE).width + 8; |
||||||
|
rl.DrawLine(x, y + title_size + 2, rl.GetScreenWidth() - 16, y + title_size + 2, rl.WHITE); |
||||||
|
if (state.db_font) |db_font| { |
||||||
|
rl.DrawTextEx(db_font, hs.station_name.items.ptr, rl.Vector2{ .x = @floatFromInt(x), .y = @floatFromInt(y) }, title_size, 0.9, rl.WHITE); |
||||||
|
} else { |
||||||
|
rl.DrawText(hs.station_name.items.ptr, x, y, title_size, rl.WHITE); |
||||||
|
} |
||||||
|
|
||||||
|
y += title_size + 2 + 16; |
||||||
|
|
||||||
|
for (hs.suggestions, 0..) |suggestion, idx| { |
||||||
|
var color = if (hs.selection_idx == idx) rl.YELLOW else rl.WHITE; |
||||||
|
|
||||||
|
// Draw arrow for selection |
||||||
|
if (hs.selection_idx == idx) { |
||||||
|
const arrow_margin: c_int = 16; |
||||||
|
rl.DrawLine(x - 10 - arrow_margin, y + body_size / 4, x - arrow_margin, y + body_size / 2, color); |
||||||
|
rl.DrawLine(x - arrow_margin, y + body_size / 2, x - 10 - arrow_margin, y + body_size * 3 / 4, color); |
||||||
|
} |
||||||
|
|
||||||
|
// Check if mouse is hovering |
||||||
|
if (rl.CheckCollisionPointRec(rl.GetMousePosition(), rl.Rectangle{ |
||||||
|
.x = @floatFromInt(x), |
||||||
|
.y = @floatFromInt(y), |
||||||
|
.width = @floatFromInt(rl.GetScreenWidth() - 16 - x), |
||||||
|
.height = @floatFromInt(body_size), |
||||||
|
})) { |
||||||
|
color = rl.BLUE; |
||||||
|
|
||||||
|
if (rl.IsMouseButtonPressed(rl.MOUSE_BUTTON_LEFT)) { |
||||||
|
// Select |
||||||
|
state.departure_screen_state.station_id.clearRetainingCapacity(); |
||||||
|
state.departure_screen_state.station_id.appendSliceAssumeCapacity(suggestion.id); |
||||||
|
state.screen = .departure; |
||||||
|
hs.fetch_thread = null; |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (state.db_font) |db_font| { |
||||||
|
rl.DrawTextEx(db_font, suggestion.name.ptr, rl.Vector2{ .x = @floatFromInt(x), .y = @floatFromInt(y) }, body_size, 0.9, color); |
||||||
|
} else { |
||||||
|
rl.DrawText(suggestion.name.ptr, x, y, body_size, color); |
||||||
|
} |
||||||
|
|
||||||
|
y += body_size + 2; |
||||||
|
} |
||||||
|
|
||||||
|
state.close_app = rl.WindowShouldClose(); |
||||||
|
} |
@ -0,0 +1,46 @@ |
|||||||
|
const std = @import("std"); |
||||||
|
const raylib = @import("raylib.zig"); |
||||||
|
const rl = raylib.rl; |
||||||
|
const stateMod = @import("state.zig"); |
||||||
|
const home = @import("home.zig"); |
||||||
|
const departure = @import("departure.zig"); |
||||||
|
|
||||||
|
pub fn main() !void { |
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; |
||||||
|
const allocator = gpa.allocator(); |
||||||
|
|
||||||
|
rl.SetConfigFlags(rl.FLAG_WINDOW_RESIZABLE | rl.FLAG_VSYNC_HINT); |
||||||
|
rl.SetTargetFPS(60); |
||||||
|
rl.InitWindow(800, 600, "Testing Raylib"); |
||||||
|
defer rl.CloseWindow(); |
||||||
|
|
||||||
|
// const font = blk: { |
||||||
|
// const maybeFont = rl.LoadFontEx("./db.ttf", 64, null, 0); |
||||||
|
// if (std.meta.eql(maybeFont, rl.GetFontDefault())) { |
||||||
|
// break :blk null; |
||||||
|
// } |
||||||
|
// break :blk maybeFont; |
||||||
|
// }; |
||||||
|
|
||||||
|
var station_name_buffer: [100]u8 = .{0} ** 100; |
||||||
|
var platform_buffer: [20]u8 = .{0} ** 20; |
||||||
|
var station_id_buffer: [10]u8 = .{0} ** 10; |
||||||
|
var appState = stateMod.AppState{ |
||||||
|
.allocator = allocator, |
||||||
|
// .db_font = font, |
||||||
|
.home_screen_state = .{ |
||||||
|
.station_name = std.ArrayListUnmanaged(u8).initBuffer(&station_name_buffer), |
||||||
|
}, |
||||||
|
.departure_screen_state = .{ |
||||||
|
.platform = std.ArrayListUnmanaged(u8).initBuffer(&platform_buffer), |
||||||
|
.station_id = std.ArrayListUnmanaged(u8).initBuffer(&station_id_buffer), // 7 digit id |
||||||
|
.departure_date = std.time.Instant.now() catch @panic("Idk buddy, hook a wall clock to your CPU ig"), |
||||||
|
}, |
||||||
|
}; |
||||||
|
while (!appState.close_app) { |
||||||
|
switch (appState.screen) { |
||||||
|
.home => try home.render(&appState), |
||||||
|
.departure => try departure.render(&appState), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,52 @@ |
|||||||
|
pub const rl = @cImport({ |
||||||
|
@cInclude("raylib.h"); |
||||||
|
}); |
||||||
|
|
||||||
|
pub fn Color(r: u8, g: u8, b: u8, a: u8) rl.Color { |
||||||
|
return .{ |
||||||
|
.r = r, |
||||||
|
.g = g, |
||||||
|
.b = b, |
||||||
|
.a = a, |
||||||
|
}; |
||||||
|
} |
||||||
|
pub fn ColorInt(whole: u24) rl.Color { |
||||||
|
return ColorIntA(@as(u32, whole) << 8 | 0xFF); |
||||||
|
} |
||||||
|
pub fn ColorIntA(whole: u32) rl.Color { |
||||||
|
return .{ |
||||||
|
// zig fmt: off |
||||||
|
.r = @truncate(whole >> 24), |
||||||
|
.g = @truncate(whole >> 16), |
||||||
|
.b = @truncate(whole >> 8), |
||||||
|
.a = @truncate(whole >> 0), |
||||||
|
// zig fmt: on |
||||||
|
}; |
||||||
|
} |
||||||
|
pub fn DrawAndMeasureText( |
||||||
|
text: [*c]const u8, |
||||||
|
pos_x: c_int, |
||||||
|
pos_y: c_int, |
||||||
|
font_size: c_int, |
||||||
|
color: rl.Color, |
||||||
|
) struct { width: c_int, height: c_int } { |
||||||
|
rl.DrawText(text, pos_x, pos_y, font_size, color); |
||||||
|
return .{ |
||||||
|
.width = rl.MeasureText(text, font_size), |
||||||
|
.height = 10, |
||||||
|
}; |
||||||
|
} |
||||||
|
pub fn GetKeyPressed() ?c_int { |
||||||
|
const result = rl.GetKeyPressed(); |
||||||
|
return if (result == 0) |
||||||
|
null |
||||||
|
else |
||||||
|
result; |
||||||
|
} |
||||||
|
pub fn GetCharPressed() ?c_int { |
||||||
|
const result = rl.GetCharPressed(); |
||||||
|
return if (result == 0) |
||||||
|
null |
||||||
|
else |
||||||
|
result; |
||||||
|
} |
@ -0,0 +1,36 @@ |
|||||||
|
const std = @import("std"); |
||||||
|
const raylib = @import("raylib.zig"); |
||||||
|
const rl = raylib.rl; |
||||||
|
|
||||||
|
pub const Screen = enum { |
||||||
|
home, |
||||||
|
departure, |
||||||
|
}; |
||||||
|
|
||||||
|
pub const HSSuggestion = struct { |
||||||
|
id: [:0]u8, |
||||||
|
name: [:0]u8, |
||||||
|
}; |
||||||
|
|
||||||
|
pub const HomeScreenState = struct { |
||||||
|
station_name: std.ArrayListUnmanaged(u8), |
||||||
|
fetch_thread: ?std.Thread = null, |
||||||
|
suggestions: []HSSuggestion = &.{}, |
||||||
|
selection_idx: i8 = 0, |
||||||
|
}; |
||||||
|
|
||||||
|
pub const DepartureScreenState = struct { |
||||||
|
station_id: std.ArrayListUnmanaged(u8), |
||||||
|
platform: std.ArrayListUnmanaged(u8), |
||||||
|
departure_date: std.time.Instant, |
||||||
|
loading: bool = false, |
||||||
|
}; |
||||||
|
|
||||||
|
pub const AppState = struct { |
||||||
|
allocator: std.mem.Allocator, |
||||||
|
close_app: bool = false, |
||||||
|
db_font: ?rl.Font = null, |
||||||
|
screen: Screen = .home, |
||||||
|
home_screen_state: HomeScreenState, |
||||||
|
departure_screen_state: DepartureScreenState, |
||||||
|
}; |
Loading…
Reference in new issue