Implement departures screen
This commit is contained in:
parent
eefb0a436f
commit
13944ace05
2 changed files with 274 additions and 3 deletions
|
@ -1,13 +1,101 @@
|
|||
const std = @import("std");
|
||||
const raylib = @import("raylib.zig");
|
||||
const rl = raylib.rl;
|
||||
const AppState = @import("state.zig");
|
||||
const Curl = @import("curl.zig");
|
||||
|
||||
fn fetchThread(state: *AppState) !void {
|
||||
std.debug.print("[departure/fetchThread] Started\n", .{});
|
||||
defer std.debug.print("[departure/fetchThread] Ended\n", .{});
|
||||
defer state.departure_screen_state.fetch_thread = null;
|
||||
const allocator = state.allocator;
|
||||
var station_id_buf = std.BoundedArray(u8, 10){};
|
||||
var include_tram = false;
|
||||
var curl = Curl.init() orelse return;
|
||||
defer curl.deinit();
|
||||
|
||||
while (state.departure_screen_state.fetch_thread != null) {
|
||||
const fetch_anyway = state.departure_screen_state.should_refresh;
|
||||
if (!fetch_anyway and std.mem.eql(u8, station_id_buf.slice(), state.departure_screen_state.station_id.items) and include_tram == state.departure_screen_state.include_tram) {
|
||||
std.time.sleep(100 * 1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
station_id_buf.resize(state.departure_screen_state.station_id.items.len) catch continue;
|
||||
std.mem.copyForwards(u8, station_id_buf.slice(), state.departure_screen_state.station_id.items);
|
||||
include_tram = state.departure_screen_state.include_tram;
|
||||
std.debug.print("[departure/fetchThread] Detected update: {s}\n", .{station_id_buf.slice()});
|
||||
|
||||
curl.reset();
|
||||
|
||||
const departures_base = std.fmt.allocPrintZ(
|
||||
allocator,
|
||||
"https://v6.db.transport.rest/stops/{s}/departures",
|
||||
.{state.departure_screen_state.station_id.items},
|
||||
) catch continue;
|
||||
defer allocator.free(departures_base);
|
||||
var departures_uri = std.Uri.parse(departures_base) catch unreachable;
|
||||
const query = std.fmt.allocPrint(allocator, "duration=300&bus=false&ferry=false&taxi=false&pretty=false{s}", .{if (include_tram) "" else "&tram=false&subway=false"}) catch continue;
|
||||
defer allocator.free(query);
|
||||
departures_uri.query = query;
|
||||
defer departures_uri.query = null;
|
||||
std.debug.print("[departure/fetchThread] Making request to: {}\n", .{departures_uri});
|
||||
|
||||
const url = try std.fmt.allocPrintZ(allocator, "{}", .{departures_uri});
|
||||
defer allocator.free(url);
|
||||
_ = curl.setopt(.url, .{url.ptr});
|
||||
|
||||
var result = std.ArrayList(u8).init(allocator);
|
||||
defer result.deinit();
|
||||
_ = curl.setopt(.write_function, .{Curl.Utils.array_list_append});
|
||||
_ = curl.setopt(.write_data, .{&result});
|
||||
|
||||
const code = curl.perform();
|
||||
std.debug.print("[departure/fetchThread] cURL Code: {}\n", .{code});
|
||||
if (code != 0) continue;
|
||||
|
||||
std.debug.print("[departure/fetchThread] Fetched data: <redacted>(len: {})\n", .{result.items.len});
|
||||
const parsed = std.json.parseFromSlice(std.json.Value, allocator, result.items, .{}) catch |err| {
|
||||
std.debug.print("[departure/fetchThread] JSON parse error: {}\n", .{err});
|
||||
continue;
|
||||
};
|
||||
if (state.departure_screen_state.fetch_result) |old_result| {
|
||||
old_result.deinit();
|
||||
}
|
||||
state.departure_screen_state.fetch_result = parsed;
|
||||
state.departure_screen_state.should_refresh = false;
|
||||
}
|
||||
if (state.departure_screen_state.fetch_result) |old_result| {
|
||||
old_result.deinit();
|
||||
state.departure_screen_state.fetch_result = null;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(state: *AppState) !void {
|
||||
const allocator = state.allocator;
|
||||
var ds = &state.departure_screen_state;
|
||||
|
||||
if (ds.fetch_thread == null) {
|
||||
ds.fetch_thread = std.Thread.spawn(.{}, fetchThread, .{state}) catch null;
|
||||
}
|
||||
|
||||
while (raylib.GetKeyPressed()) |key| {
|
||||
switch (key) {
|
||||
rl.KEY_LEFT => {
|
||||
state.screen = .home;
|
||||
},
|
||||
rl.KEY_R => {
|
||||
ds.should_refresh = true;
|
||||
},
|
||||
rl.KEY_MINUS, rl.KEY_KP_SUBTRACT => {
|
||||
ds.max_next_trains = @max(1, ds.max_next_trains - 1);
|
||||
},
|
||||
rl.KEY_EQUAL, rl.KEY_KP_EQUAL => {
|
||||
ds.max_next_trains = @min(ds.max_next_trains + 1, if (ds.fetch_result) |fr| @as(c_int, @intCast(fr.value.object.get("departures").?.array.items.len)) else 5);
|
||||
},
|
||||
rl.KEY_T => {
|
||||
ds.include_tram = !ds.include_tram;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
@ -15,8 +103,186 @@ pub fn render(state: *AppState) !void {
|
|||
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);
|
||||
const db_blue = raylib.ColorInt(0x18226f);
|
||||
rl.ClearBackground(if (ds.should_refresh) rl.ORANGE else db_blue);
|
||||
if (ds.fetch_result) |data| {
|
||||
if (data.value.object.get("departures")) |departures_raw| {
|
||||
const departures = departures_raw.array.items;
|
||||
var not_cancelled = std.ArrayList(std.json.Value).init(allocator);
|
||||
defer not_cancelled.deinit();
|
||||
for (departures) |d| {
|
||||
if (d.object.get("cancelled")) |c| {
|
||||
switch (c) {
|
||||
.bool => |b| {
|
||||
if (b) {
|
||||
continue;
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
not_cancelled.append(d) catch continue;
|
||||
}
|
||||
if (not_cancelled.items.len > 0) {
|
||||
var y: c_int = 16;
|
||||
// Info area
|
||||
y += 32 + 16;
|
||||
const first = not_cancelled.items[0].object;
|
||||
|
||||
station_name_blk: {
|
||||
const station_name = std.fmt.allocPrintZ(allocator, "{s}", .{first.get("stop").?.object.get("name").?.string}) catch break :station_name_blk;
|
||||
defer allocator.free(station_name);
|
||||
rl.SetWindowTitle(station_name.ptr);
|
||||
raylib.DrawRightAlignedText(station_name.ptr, rl.GetScreenWidth() - 4, 4, 14, rl.WHITE);
|
||||
}
|
||||
|
||||
const line = try std.fmt.allocPrintZ(allocator, "{s}", .{first.get("line").?.object.get("name").?.string});
|
||||
defer allocator.free(line);
|
||||
const destination = try std.fmt.allocPrintZ(allocator, "{s}", .{first.get("direction").?.string});
|
||||
defer allocator.free(destination);
|
||||
var next_y = y;
|
||||
if (state.db_font) |db_font| {
|
||||
next_y += @intFromFloat(raylib.DrawAndMeasureTextEx(db_font, line.ptr, 16, @floatFromInt(y), 32, 1, rl.WHITE).y);
|
||||
} else {
|
||||
rl.DrawText(line.ptr, 16, y, 32, rl.WHITE);
|
||||
next_y += 32;
|
||||
}
|
||||
next_y += 16;
|
||||
if (ds.platform.items.len == 0) blk: {
|
||||
if (first.get("platform")) |platform_raw| {
|
||||
switch (platform_raw) {
|
||||
.string => |p| {
|
||||
const platform = std.fmt.allocPrintZ(allocator, "{s}", .{p}) catch break :blk;
|
||||
defer allocator.free(platform);
|
||||
if (state.db_font) |db_font| {
|
||||
raylib.DrawRightAlignedTextEx(db_font, platform.ptr, @floatFromInt(rl.GetScreenWidth() - 16), @floatFromInt(y), 40, 1, rl.WHITE);
|
||||
} else {
|
||||
raylib.DrawRightAlignedText(platform.ptr, rl.GetScreenWidth() - 16, y, 40, rl.WHITE);
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
y = next_y;
|
||||
if (state.db_font) |db_font| {
|
||||
y += @intFromFloat(raylib.DrawAndMeasureTextEx(
|
||||
db_font,
|
||||
destination.ptr,
|
||||
16,
|
||||
@floatFromInt(y),
|
||||
56,
|
||||
1,
|
||||
rl.WHITE,
|
||||
).y);
|
||||
} else {
|
||||
rl.DrawText(destination.ptr, 16, y, 56, rl.WHITE);
|
||||
y += 56;
|
||||
}
|
||||
y += 16;
|
||||
}
|
||||
if (not_cancelled.items.len > 1) {
|
||||
var max_trains: c_int = @intCast(not_cancelled.items.len - 1);
|
||||
if (max_trains > ds.max_next_trains) max_trains = ds.max_next_trains;
|
||||
const font_size: c_int = 32;
|
||||
var x: c_int = 16;
|
||||
var y = rl.GetScreenHeight() - (font_size + 8) * max_trains - 4;
|
||||
rl.DrawRectangle(0, y, rl.GetScreenWidth(), rl.GetScreenHeight(), rl.WHITE);
|
||||
y += 8;
|
||||
const label_measurement_width = if (state.db_font) |db_font| @as(c_int, @intFromFloat(raylib.DrawAndMeasureTextEx(
|
||||
db_font,
|
||||
if (max_trains == 1) "Next train: " else "Next trains: ",
|
||||
@floatFromInt(x),
|
||||
@floatFromInt(y),
|
||||
@floatFromInt(font_size),
|
||||
1,
|
||||
db_blue,
|
||||
).x)) else raylib.DrawAndMeasureText(
|
||||
if (max_trains == 1) "Next train: " else "Next trains: ",
|
||||
x,
|
||||
y,
|
||||
font_size,
|
||||
db_blue,
|
||||
).width;
|
||||
x += label_measurement_width;
|
||||
|
||||
// Compute line name width
|
||||
var line_name_width: c_int = 0;
|
||||
for (not_cancelled.items, 0..) |dep_raw, idx| {
|
||||
if (idx == 0) continue;
|
||||
if (idx > max_trains) break;
|
||||
const second = dep_raw.object;
|
||||
const next_train_line = try std.fmt.allocPrintZ(
|
||||
allocator,
|
||||
"{s} ",
|
||||
.{
|
||||
second.get("line").?.object.get("name").?.string,
|
||||
},
|
||||
);
|
||||
defer allocator.free(next_train_line);
|
||||
if (state.db_font) |db_font| {
|
||||
line_name_width = @max(
|
||||
line_name_width,
|
||||
@as(c_int, @intFromFloat(rl.MeasureTextEx(db_font, next_train_line.ptr, @floatFromInt(font_size), 1).x)),
|
||||
);
|
||||
} else {
|
||||
line_name_width = @max(line_name_width, rl.MeasureText(next_train_line.ptr, font_size));
|
||||
}
|
||||
}
|
||||
const destionation_x = x + line_name_width;
|
||||
|
||||
for (not_cancelled.items, 0..) |dep_raw, idx| {
|
||||
if (idx == 0) continue;
|
||||
if (idx > max_trains) break;
|
||||
const second = dep_raw.object;
|
||||
const next_train_line = try std.fmt.allocPrintZ(
|
||||
allocator,
|
||||
"{s} ",
|
||||
.{
|
||||
second.get("line").?.object.get("name").?.string,
|
||||
},
|
||||
);
|
||||
defer allocator.free(next_train_line);
|
||||
const next_train_direction = try std.fmt.allocPrintZ(
|
||||
allocator,
|
||||
"{s}",
|
||||
.{
|
||||
second.get("direction").?.string,
|
||||
},
|
||||
);
|
||||
defer allocator.free(next_train_direction);
|
||||
if (state.db_font) |db_font| {
|
||||
rl.DrawTextEx(db_font, next_train_line.ptr, .{ .x = @floatFromInt(x), .y = @floatFromInt(y) }, font_size, 1, db_blue);
|
||||
rl.DrawTextEx(db_font, next_train_direction.ptr, .{ .x = @floatFromInt(destionation_x), .y = @floatFromInt(y) }, font_size, 1, db_blue);
|
||||
} else {
|
||||
rl.DrawText(next_train_line.ptr, x, y, font_size, db_blue);
|
||||
rl.DrawText(next_train_direction.ptr, destionation_x, y, font_size, db_blue);
|
||||
}
|
||||
if (ds.platform.items.len == 0) blk: {
|
||||
if (second.get("platform")) |platform_raw| {
|
||||
switch (platform_raw) {
|
||||
.string => |p| {
|
||||
const platform = std.fmt.allocPrintZ(allocator, "{s}", .{p}) catch break :blk;
|
||||
defer allocator.free(platform);
|
||||
if (state.db_font) |db_font| {
|
||||
raylib.DrawRightAlignedTextEx(db_font, platform.ptr, @floatFromInt(rl.GetScreenWidth() - 16), @floatFromInt(y), @floatFromInt(font_size), 1, db_blue);
|
||||
} else {
|
||||
raylib.DrawRightAlignedText(platform.ptr, rl.GetScreenWidth() - 16, y, font_size, db_blue);
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
y += font_size + 4;
|
||||
rl.DrawLine(x, y, rl.GetScreenWidth() - 8, y, db_blue);
|
||||
y += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rl.DrawText(state.departure_screen_state.station_id.items.ptr, 16, 16, 32, rl.WHITE);
|
||||
}
|
||||
|
||||
state.close_app = rl.WindowShouldClose();
|
||||
}
|
||||
|
|
|
@ -23,7 +23,12 @@ pub const DepartureScreenState = struct {
|
|||
station_id: std.ArrayListUnmanaged(u8),
|
||||
platform: std.ArrayListUnmanaged(u8),
|
||||
departure_date: std.time.Instant,
|
||||
loading: bool = false,
|
||||
fetch_thread: ?std.Thread = null,
|
||||
last_refresh_time: std.time.Instant = std.mem.zeroInit(std.time.Instant, .{}),
|
||||
fetch_result: ?std.json.Parsed(std.json.Value) = null,
|
||||
should_refresh: bool = false,
|
||||
max_next_trains: c_int = 5,
|
||||
include_tram: bool = false,
|
||||
};
|
||||
|
||||
allocator: std.mem.Allocator,
|
||||
|
|
Loading…
Add table
Reference in a new issue