Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 2 additions & 5 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,6 @@
pkgs.sdl3
pkgs.vulkan-loader
pkgs.vulkan-headers
pkgs.vulkan-validation-layers
cimgui
];

Expand Down Expand Up @@ -339,8 +338,7 @@
pkgs.sdl3
pkgs.vulkan-loader
pkgs.vulkan-headers
pkgs.vulkan-validation-layers
pkgs.mesa.drivers
pkgs.mesa
cimgui
];

Expand Down Expand Up @@ -385,8 +383,7 @@
pkgs.sdl3
pkgs.vulkan-loader
pkgs.vulkan-headers
pkgs.vulkan-validation-layers
pkgs.mesa.drivers
pkgs.mesa
cimgui
];

Expand Down
10 changes: 10 additions & 0 deletions modules/engine-core/src/window.zig
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@ pub const WindowManager = struct {
};
}

pub fn show(self: *WindowManager) void {
if (c.SDL_ShowWindow(self.window) == false) {
log.log.warn("SDL_ShowWindow failed: {s}", .{c.SDL_GetError()});
return;
}
if (c.SDL_SyncWindow(self.window) == false) {
log.log.warn("SDL_SyncWindow failed: {s}", .{c.SDL_GetError()});
}
}

const HyprlandMonitor = struct {
width: c_int,
height: c_int,
Expand Down
3 changes: 1 addition & 2 deletions modules/engine-graphics/src/render_graph.zig
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ pub const ShadowPass = struct {
const self: *ShadowPass = @ptrCast(@alignCast(ptr));
// Runtime verification to ensuring pointer safety in debug mode
std.debug.assert(self.cascade_index < rhi_pkg.SHADOW_CASCADE_COUNT);
if (!self.enabled or !ctx.shadow_draw_enabled) return;

const cascade_idx = self.cascade_index;
const shadow_resolution = ctx.shadow_ctx.getResolution();
Expand Down Expand Up @@ -299,8 +300,6 @@ pub const ShadowPass = struct {
});
}

if (!self.enabled or !ctx.shadow_draw_enabled) return;

// Keep cutout casters sampling the terrain atlas during the shadow pass.
// Without this, shadow.frag can alpha-clip against whatever texture was last bound.
self.material_system.bindTerrainMaterial(ctx.render_ctx, ctx.env_map_handle);
Expand Down
212 changes: 126 additions & 86 deletions modules/game-ui/src/menu_theme.zig

Large diffs are not rendered by default.

20 changes: 12 additions & 8 deletions modules/game-ui/src/menu_theme_tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,26 @@ const testing = @import("std").testing;

const Theme = @import("menu_theme.zig");

test "scaleFor keeps 720p baseline at user scale" {
try testing.expectEqual(@as(f32, 1.0), Theme.scaleFor(720.0, 1.0));
test "scaleFor reduces layouts on short windows" {
try testing.expectEqual(@as(f32, 0.72), Theme.scaleFor(720.0, 1.0));
}

test "scaleFor does not shrink below baseline before user scale" {
try testing.expectEqual(@as(f32, 1.0), Theme.scaleFor(480.0, 1.0));
test "scaleFor has a readable lower bound" {
try testing.expectEqual(@as(f32, 0.72), Theme.scaleFor(480.0, 1.0));
}

test "scaleFor grows on taller displays" {
try testing.expectEqual(@as(f32, 2.0), Theme.scaleFor(1440.0, 1.0));
test "scaleFor caps growth on tall displays" {
try testing.expectApproxEqAbs(@as(f32, 4.0 / 3.0), Theme.scaleFor(1440.0, 1.0), 0.0001);
}

test "scaleFor applies manual user scale" {
try testing.expectEqual(@as(f32, 1.5), Theme.scaleFor(720.0, 1.5));
try testing.expectEqual(@as(f32, 1.08), Theme.scaleFor(720.0, 1.5));
}

test "scaleFor combines display and user scale" {
try testing.expectEqual(@as(f32, 3.0), Theme.scaleFor(1440.0, 1.5));
try testing.expectApproxEqAbs(@as(f32, 2.0), Theme.scaleFor(1440.0, 1.5), 0.0001);
}

test "scaleFor reaches two times scale at 4K" {
try testing.expectEqual(@as(f32, 2.0), Theme.scaleFor(2160.0, 1.0));
}
13 changes: 13 additions & 0 deletions modules/game-ui/src/screen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ pub const IScreen = struct {
onEnter: ?*const fn (ptr: *anyopaque) void = null,
onExit: ?*const fn (ptr: *anyopaque) void = null,
getWorldStats: ?*const fn (ptr: *anyopaque) ?WorldStats = null,
isReadyForPresentation: ?*const fn (ptr: *anyopaque) bool = null,
};

pub fn deinit(self: IScreen) void {
Expand Down Expand Up @@ -228,6 +229,13 @@ pub const IScreen = struct {
return null;
}

pub fn isReadyForPresentation(self: IScreen) bool {
if (self.vtable.isReadyForPresentation) |ready_fn| {
return ready_fn(self.ptr);
}
return true;
}

pub fn handle(self: IScreen) core_interfaces.ScreenHandle {
return .{
.ptr = self.ptr,
Expand Down Expand Up @@ -346,6 +354,11 @@ pub const ScreenManager = struct {
}
}

pub fn isReadyForPresentation(self: *const ScreenManager) bool {
if (self.stack.items.len == 0) return false;
return self.stack.items[self.stack.items.len - 1].isReadyForPresentation();
}

pub fn drawParentScreen(self: *ScreenManager, current_ptr: *anyopaque, ui: *UISystem) !void {
for (self.stack.items, 0..) |screen, i| {
if (screen.ptr == current_ptr) {
Expand Down
35 changes: 25 additions & 10 deletions modules/game-ui/src/screens/environment.zig
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const PANEL_HEIGHT_MAX = 760.0;
pub const EnvironmentScreen = struct {
context: EnvironmentContext,
environment_maps: std.ArrayListUnmanaged([]const u8),
scroll_offset: f32,

pub const vtable = IScreen.VTable{
.deinit = deinit,
Expand All @@ -36,6 +37,7 @@ pub const EnvironmentScreen = struct {
self.* = .{
.context = context.environmentContext(),
.environment_maps = .empty,
.scroll_offset = 0.0,
};
try self.refreshEnvironmentMaps();
return self;
Expand Down Expand Up @@ -81,30 +83,41 @@ pub const EnvironmentScreen = struct {
const panel_y = (screen_h - panel_h) * 0.5;
const shell = Theme.drawShell(ui, .{ .x = panel_x, .y = panel_y, .width = panel_w, .height = panel_h }, ui_scale, "LIGHT PROBE", "ENVIRONMENT", "Choose the sky probe that lights the scene.");

const preview_w = @min(260.0 * ui_scale, shell.content.width * 0.34);
const compact = shell.content.width < 640.0 * ui_scale;
const preview_w = if (compact) 0.0 else @min(260.0 * ui_scale, shell.content.width * 0.34);
const list_x = shell.content.x + preview_w + 24.0 * ui_scale;
const list_w = shell.content.width - preview_w - 24.0 * ui_scale;
drawSkyPreview(ui, shell.content.x, shell.content.y, preview_w, shell.content.height, settings.environment_map, ui_scale);
if (!compact) drawSkyPreview(ui, shell.content.x, shell.content.y, preview_w, shell.content.height, settings.environment_map, ui_scale);

Theme.drawListRail(ui, .{ .x = list_x, .y = shell.content.y, .width = list_w, .height = shell.content.height }, ui_scale);
var y = shell.content.y + 18.0 * ui_scale;
const row_x = list_x + 18.0 * ui_scale;
const row_w = list_w - 36.0 * ui_scale;
const row_h = 56.0 * ui_scale;
const btn_scale = 1.12 * ui_scale;
const content_h = @as(f32, @floatFromInt(self.environment_maps.items.len + 1)) * (row_h + 10.0 * ui_scale) + 26.0 * ui_scale;
const max_scroll = @max(0.0, content_h - shell.content.height);
self.scroll_offset -= ctx.input.getScrollDelta().y * 32.0 * ui_scale;
self.scroll_offset = @max(0.0, @min(self.scroll_offset, max_scroll));
var y = shell.content.y + 18.0 * ui_scale - self.scroll_offset;
Theme.drawScrollbar(ui, list_x + list_w - 10.0 * ui_scale, shell.content.y + 12.0 * ui_scale, shell.content.height - 24.0 * ui_scale, content_h, shell.content.height, self.scroll_offset, max_scroll, ui_scale);

const is_default = std.mem.eql(u8, settings.environment_map, "default");
if (drawEnvironmentButton(ui, row_x, y, row_w, row_h, "DEFAULT SKY", "Neutral white environment texture.", is_default, btn_scale, mouse_x, mouse_y, mouse_clicked, ui_scale)) {
if (!is_default) {
try settings_pkg.persistence.setEnvironmentMap(settings, ctx.allocator, "default");
try self.reloadEnvMap();
if (y + row_h >= shell.content.y and y <= shell.content.y + shell.content.height) {
if (drawEnvironmentButton(ui, row_x, y, row_w, row_h, "DEFAULT SKY", "Neutral white environment texture.", is_default, btn_scale, mouse_x, mouse_y, mouse_clicked, ui_scale)) {
if (!is_default) {
try settings_pkg.persistence.setEnvironmentMap(settings, ctx.allocator, "default");
try self.reloadEnvMap();
}
}
}
y += row_h + 10.0 * ui_scale;

var buffer: [160]u8 = undefined;
for (self.environment_maps.items) |environment_map| {
if (y + row_h > shell.content.y + shell.content.height) break;
if (y + row_h < shell.content.y or y > shell.content.y + shell.content.height) {
y += row_h + 10.0 * ui_scale;
continue;
}
const is_selected = std.mem.eql(u8, settings.environment_map, environment_map);
const label = std.fmt.bufPrint(&buffer, "{s}", .{environment_map}) catch "ENVIRONMENT";
if (drawEnvironmentButton(ui, row_x, y, row_w, row_h, label, "HDR/EXR sky probe from the working directory.", is_selected, btn_scale, mouse_x, mouse_y, mouse_clicked, ui_scale)) {
Expand Down Expand Up @@ -198,8 +211,10 @@ fn drawSkyPreview(ui: *UISystem, x: f32, y: f32, w: f32, h: f32, active_name: []
}

fn drawEnvironmentButton(ui: *UISystem, x: f32, y: f32, w: f32, h: f32, label: []const u8, description: []const u8, selected: bool, btn_scale: f32, mx: f32, my: f32, clicked: bool, scale: f32) bool {
Theme.drawOptionRow(ui, .{ .x = x, .y = y, .width = w, .height = h }, label, description, 1.02 * scale, selected, scale);
const row = Theme.Rect{ .x = x, .y = y, .width = w, .height = h };
Theme.drawOptionRow(ui, row, label, description, 1.02 * scale, selected, scale);
const action_w = 150.0 * scale;
const action_x = x + w - action_w - 12.0 * scale;
return Theme.drawButton(ui, .{ .x = action_x, .y = y + 9.0 * scale, .width = action_w, .height = h - 18.0 * scale }, if (selected) "ACTIVE" else "SELECT", btn_scale, mx, my, clicked, if (selected) .primary else .secondary, scale);
const action_clicked = Theme.drawButton(ui, .{ .x = action_x, .y = y + 9.0 * scale, .width = action_w, .height = h - 18.0 * scale }, if (selected) "ACTIVE" else "USE SKY", btn_scale, mx, my, clicked, if (selected) .primary else .secondary, scale);
return action_clicked or (clicked and row.contains(mx, my));
}
Loading
Loading