From 99d0b733d882b7c7d009dd4cec54c3cedfb3a8aa Mon Sep 17 00:00:00 2001 From: Kenny Van de Maele Date: Fri, 23 Oct 2026 20:39:32 +0200 Subject: [PATCH] fix: the pointer stays over what scripts draw on the menus The front end's own screens and the in-game menus drew their pointer inside their own drawing, before the scripts drew over the screen, so a script that drew where the pointer was covered it. The rooms and a mod's screen already drew theirs after the scripts. Each screen now leaves the pointer out, and its driver draws it after the scripts' drawing (canvas.Canvas.drawPointer): the front end for each screen it shows, the rooms' driver for the menus over the rooms, and flight for the pause menu. The restart screen keeps its pointer at the screen's brightness and hides it once a choice is made. The pause menu keeps the settings screen it leaves until the next frame, so that frame's pointer can still be drawn. --- docs/guide/scripting.md | 3 +- src/engine/game/hudoptions.zig | 88 +++++++++++++------ src/engine/game/interface/canvas.zig | 7 ++ src/engine/game/interface/game_modes.zig | 5 +- src/engine/game/interface/game_options.zig | 5 +- src/engine/game/interface/in_game_options.zig | 5 +- src/engine/game/interface/main_menu.zig | 12 ++- src/engine/game/interface/mod_catalogue.zig | 7 +- src/engine/game/interface/mod_manager.zig | 7 +- src/engine/game/interface/mod_options.zig | 5 +- src/engine/game/interface/pilot_roster.zig | 8 +- src/engine/game/interface/restart.zig | 52 +++++++++-- src/engine/game/interface/saved_games.zig | 5 +- src/engine/game/interface/settings.zig | 5 +- src/engine/genilib/interf.zig | 36 +++++--- src/openreliant/main.zig | 13 +-- src/openreliant/rooms.zig | 49 +++++++---- 17 files changed, 207 insertions(+), 105 deletions(-) diff --git a/docs/guide/scripting.md b/docs/guide/scripting.md index d00f4a8b..bff7ea68 100644 --- a/docs/guide/scripting.md +++ b/docs/guide/scripting.md @@ -2170,7 +2170,8 @@ return { } ``` -The rooms draw their pointer over what scripts draw, as the front end does over a mod's screen. +The pointer goes over what scripts draw, in the rooms and their places, on the front end's screens +and a mod's, and on the menus over the rooms and over a mission. [`examples/mods/cd-player-pages`](../../examples/mods/cd-player-pages) gives the CD player its pages, with the front end's arrows under its screen. diff --git a/src/engine/game/hudoptions.zig b/src/engine/game/hudoptions.zig index 2347a426..200dbdb2 100644 --- a/src/engine/game/hudoptions.zig +++ b/src/engine/game/hudoptions.zig @@ -140,8 +140,14 @@ pub const PauseMenu = struct { view_setting: camera.CockpitSetting = .cockpit, /// The archive the fonts came from, which the settings screen's shapes are read from. archive: ?bigfile.Hog = null, - /// The settings screen, while it is shown. + /// The settings screen, while it is shown, and on the frame that leaves it. settings: ?SettingsScreen = null, + /// The pointer that this frame's drawing ends with, which `drawPointer` draws: the main + /// screen's or the settings screen's; none once the pause has an outcome, or where the + /// settings screen's shapes are left out. + pointer_shown: ?PointerShown = null, + + const PointerShown = enum { main, settings }; /// `optfnt.fnt` and `smlfnt2.fnt`, as `hog_load` read them and `font_open` opened them /// (`menu_font_large`, `menu_font_small`), and what holds them and their glyphs' images. @@ -195,24 +201,23 @@ pub const PauseMenu = struct { /// `pause_menu_draw` (`0x004906F0`), the overlay while paused, with the pointer brought up to /// date first as `mission_paused_frame` does (`menu_mouse_update`). A screen shown for the /// first time runs its enter routine; if its choice goes elsewhere, it runs its leave routine. - /// The pointer is drawn last. + /// The game draws the pointer last, which OpenReliant draws after what scripts draw over the + /// menu (`drawPointer`). pub fn draw(pause_menu: *PauseMenu, frame: Frame) menu.Error!void { + pause_menu.pointer_shown = null; const fonts = if (pause_menu.fonts) |*open_fonts| open_fonts else return; pause_menu.pointer.update(frame.devices.mouse, frame.screen); - const ui: menu.Ui = .{ - .gpa = fonts.gpa, - .target = frame.target, - .screen = frame.screen, - .scale = frame.ui_scale.of(frame.screen), - .art = frame.art, - .fonts = .{ .display = frame.font, .small = &fonts.small.font, .large = &fonts.large.font }, - .strings = frame.strings, - }; + const ui = uiOf(frame, fonts); const screen = switch (pause_menu.at) { .screen => |shown| shown, .outcome => return, }; const tab = SettingsScreen.tabOf(screen); + // The settings screen the frame before left goes, now that its pointer has been drawn. + if (tab == null) if (pause_menu.settings) |*shown| { + shown.close(fonts.gpa); + pause_menu.settings = null; + }; if (pause_menu.entered != screen) { if (tab) |shown| pause_menu.enterSettings(frame, shown); pause_menu.entered = screen; @@ -222,14 +227,51 @@ pub const PauseMenu = struct { .pointer = pause_menu.pointer, .escaped = frame.devices.keyboard.pressed(input.scan.escape, .none, true), }); - if (next) |going| if (!std.meta.eql(going, pause_menu.at)) { - if (pause_menu.settings) |*shown| shown.close(fonts.gpa); - pause_menu.settings = null; - pause_menu.at = going; - }; + if (next) |going| pause_menu.at = going; if (tab != null) return; if (frame.version) |version| try hud.drawVersion(ui.fonts.small, ui.gpa, ui.target, ui.screen, version, ui.scale); - try ui.drawPointer(pause_menu.pointer); + pause_menu.pointer_shown = .main; + } + + /// The end of `pause_menu_draw`: the pointer of the screen drawn, which OpenReliant draws after + /// what the scripts draw over the menu, so that it stays on top + /// ([#1171](https://github.com/OpenReliant/openreliant/issues/1171)). + pub fn drawPointer(pause_menu: *PauseMenu, frame: Frame) menu.Error!void { + const fonts = if (pause_menu.fonts) |*open_fonts| open_fonts else return; + switch (pause_menu.pointer_shown orelse return) { + .main => try uiOf(frame, fonts).drawPointer(pause_menu.pointer), + .settings => { + const shown = if (pause_menu.settings) |*kept| kept else return; + const shapes = if (shown.shapes) |*read| &read.art else return; + try settingsCanvas(frame, fonts).drawPointer(shapes, shown.pointer); + }, + } + } + + /// What the main screen draws with. + fn uiOf(frame: Frame, fonts: *Fonts) menu.Ui { + return .{ + .gpa = fonts.gpa, + .target = frame.target, + .screen = frame.screen, + .scale = frame.ui_scale.of(frame.screen), + .art = frame.art, + .fonts = .{ .display = frame.font, .small = &fonts.small.font, .large = &fonts.large.font }, + .strings = frame.strings, + }; + } + + /// What the settings screen draws on. + fn settingsCanvas(frame: Frame, fonts: *Fonts) canvas.Canvas { + return .{ + .gpa = fonts.gpa, + .target = frame.target, + .window = frame.screen, + .share = frame.ui_scale.share(), + .fonts = .{ .large = &fonts.large.font, .small = &fonts.small.font }, + .strings = frame.strings, + .version = frame.version, + }; } /// Opens the settings screen on `tab`: the front end's shapes and its dialog's read, and the @@ -261,16 +303,8 @@ pub const PauseMenu = struct { hud.drawFilled(frame.target, .{ .left = 0, .top = 0, .right = @floatFromInt(frame.screen[0]), .bottom = @floatFromInt(frame.screen[1]) }, shade); const shapes = if (shown.shapes) |*read| &read.art else return .{ .screen = .main }; const dialog_art = if (shown.dialog) |*read| &read.art else return .{ .screen = .main }; - const drawn: canvas.Canvas = .{ - .gpa = fonts.gpa, - .target = frame.target, - .window = frame.screen, - .share = frame.ui_scale.share(), - .fonts = .{ .large = &fonts.large.font, .small = &fonts.small.font }, - .strings = frame.strings, - .version = frame.version, - }; - try shown.screen.draw(drawn, shapes, dialog_art, .{ .devices = frame.devices, .sound = frame.settings.sound, .video = frame.settings.video }, shown.pointer); + try shown.screen.draw(settingsCanvas(frame, fonts), shapes, dialog_art, .{ .devices = frame.devices, .sound = frame.settings.sound, .video = frame.settings.video }); + pause_menu.pointer_shown = .settings; return switch (end orelse return null) { .back, .main_menu => .{ .screen = .main }, .continue_mission => .{ .outcome = .continue_mission }, diff --git a/src/engine/game/interface/canvas.zig b/src/engine/game/interface/canvas.zig index 9f12e70f..7b6ae988 100644 --- a/src/engine/game/interface/canvas.zig +++ b/src/engine/game/interface/canvas.zig @@ -216,6 +216,13 @@ pub const Canvas = struct { } }; + /// Draws `pointer` with its shape in `art`. The screens of the front end and the menus draw + /// their pointer last, so OpenReliant draws it after what the scripts draw over a screen, which + /// keeps it on top ([#1171](https://github.com/OpenReliant/openreliant/issues/1171)). + pub fn drawPointer(canvas: Canvas, art: *hud.Art, pointer: Pointer) Error!void { + try canvas.onScreen().shape(art, pointer.shape(), pointer.at); + } + /// **Improvement:** OpenReliant's version, where the canvas has one, in the window's bottom /// right corner as the pause menu writes it (`hud.drawVersion`), as large as the screen's text. /// A screen writes it before its pointer. diff --git a/src/engine/game/interface/game_modes.zig b/src/engine/game/interface/game_modes.zig index 262a02a5..c6b82702 100644 --- a/src/engine/game/interface/game_modes.zig +++ b/src/engine/game/interface/game_modes.zig @@ -181,8 +181,8 @@ pub const GameModes = struct { } /// The screen's drawing, over the background: the title, the list and the panel, the buttons, - /// the one under the pointer lit, OpenReliant's version, then the pointer. - pub fn draw(screen: GameModes, canvas: Canvas, art: *hud.Art, pointer: Pointer) canvas_module.Error!void { + /// the one under the pointer lit, and OpenReliant's version. The pointer goes over it all, after what scripts draw (`canvas.Canvas.drawPointer`). + pub fn draw(screen: GameModes, canvas: Canvas, art: *hud.Art) canvas_module.Error!void { const modes = screen.modes; try title.write(canvas, canvas.fonts.large, canvas_module.white); mod_manager.list_frame.draw(canvas); @@ -195,7 +195,6 @@ pub const GameModes = struct { try shown.draw(canvas.dimmedUnless(playable), art, settings.button_shapes, std.meta.eql(screen.lit, Item{ .button = button })); } try canvas.drawVersion(); - try canvas.onScreen().shape(art, pointer.shape(), pointer.at); } /// The rows shown, each a mode's label, the chosen one white, and the arrows, the one under the diff --git a/src/engine/game/interface/game_options.zig b/src/engine/game/interface/game_options.zig index 3327d97e..c21d2713 100644 --- a/src/engine/game/interface/game_options.zig +++ b/src/engine/game/interface/game_options.zig @@ -131,9 +131,9 @@ pub const GameOptions = struct { /// `game_options_draw` (`0x0042AFB0`), over the menu's background: the buttons, the item under /// the pointer lit, the title and the labels, ABOUT OPENRELIANT's box or QUIT's question where - /// either is up, OpenReliant's version, then the pointer. The game writes the label under the + /// either is up, and OpenReliant's version. The pointer goes over it all, after what scripts draw (`canvas.Canvas.drawPointer`). The game writes the label under the /// pointer white before the labels, which write over it in blue, so it stays blue. - pub fn draw(menu: GameOptions, canvas: Canvas, art: *hud.Art, dialog_art: *hud.Art, about_art: *hud.Art, pointer: Pointer) canvas_module.Error!void { + pub fn draw(menu: GameOptions, canvas: Canvas, art: *hud.Art, dialog_art: *hud.Art, about_art: *hud.Art) canvas_module.Error!void { for ([_]Item{ .about, .mods, .main_menu, .quit }) |item| { const rect = rects.get(item); try canvas.shape(art, button_shape, .{ rect.x, rect.y }); @@ -155,7 +155,6 @@ pub const GameOptions = struct { if (menu.about) |about| try about.draw(canvas, about_art); if (menu.confirm) |confirm| try confirm.draw(canvas, dialog_art); try canvas.drawVersion(); - try canvas.onScreen().shape(art, pointer.shape(), pointer.at); } }; diff --git a/src/engine/game/interface/in_game_options.zig b/src/engine/game/interface/in_game_options.zig index 15b30cc6..6278a85d 100644 --- a/src/engine/game/interface/in_game_options.zig +++ b/src/engine/game/interface/in_game_options.zig @@ -243,8 +243,8 @@ pub const InGameOptions = struct { /// `in_game_options_draw` (`0x004398B0`), over the menu's background: the buttons and their /// labels, the item under the pointer lit, the panels' labels, ABOUT OPENRELIANT's box or - /// QUIT's question where either is up, OpenReliant's version, then the pointer. - pub fn draw(menu: InGameOptions, canvas: Canvas, art: *hud.Art, dialog_art: *hud.Art, about_art: ?*hud.Art, pointer: Pointer) canvas_module.Error!void { + /// QUIT's question where either is up, and OpenReliant's version. The pointer goes over it all, after what scripts draw (`canvas.Canvas.drawPointer`). + pub fn draw(menu: InGameOptions, canvas: Canvas, art: *hud.Art, dialog_art: *hud.Art, about_art: ?*hud.Art) canvas_module.Error!void { for (std.enums.values(Item)) |item| { const shown = button(item) orelse continue; try shown.draw(canvas, art, button_shapes, menu.under == item); @@ -257,7 +257,6 @@ pub const InGameOptions = struct { if (menu.about) |about| if (about_art) |shapes| try about.draw(canvas, shapes); if (menu.confirm) |confirm| try confirm.draw(canvas, dialog_art); try canvas.drawVersion(); - try canvas.onScreen().shape(art, pointer.shape(), pointer.at); } }; diff --git a/src/engine/game/interface/main_menu.zig b/src/engine/game/interface/main_menu.zig index 0b9121a3..68f04f31 100644 --- a/src/engine/game/interface/main_menu.zig +++ b/src/engine/game/interface/main_menu.zig @@ -341,9 +341,14 @@ pub const MainMenu = struct { /// `main_menu_draw`: the panels' labels in blue, QUIT's and INSTANT ACTION's buttons and labels, /// the item under the pointer lit and a panel's labels in gold, QUIT's question or the notice - /// of a newer release where it is up, then the pointer, and with the developers' keys on the - /// mission's number. The menu's background is drawn behind it all (`background_set`). - pub fn draw(menu: MainMenu, canvas: Canvas, art: *hud.Art, dialog_art: *hud.Art, pointer: Pointer, developer_font: ?*hud.Opened) canvas_module.Error!void { + /// of a newer release where it is up, and with the developers' keys on the mission's number. + /// The menu's background is drawn behind it all (`background_set`), and the pointer over it + /// all, after what scripts draw (`canvas.Canvas.drawPointer`). + /// + /// **Improvement:** the game draws the mission's number over the pointer. OpenReliant draws + /// the pointer last, over what scripts draw too + /// ([#1171](https://github.com/OpenReliant/openreliant/issues/1171)). + pub fn draw(menu: MainMenu, canvas: Canvas, art: *hud.Art, dialog_art: *hud.Art, developer_font: ?*hud.Opened) canvas_module.Error!void { const large = canvas.fonts.large; const small = canvas.fonts.small; for (std.enums.values(Item)) |item| { @@ -376,7 +381,6 @@ pub const MainMenu = struct { try asked.draw(canvas, dialog_art); } try canvas.drawVersion(); - try canvas.onScreen().shape(art, pointer.shape(), pointer.at); if (menu.developer) if (developer_font) |font| { var buffer: [16]u8 = undefined; const number = std.mem.print(&buffer, "M{d}", .{menu.mission}) catch return; diff --git a/src/engine/game/interface/mod_catalogue.zig b/src/engine/game/interface/mod_catalogue.zig index 832f51f6..88b98572 100644 --- a/src/engine/game/interface/mod_catalogue.zig +++ b/src/engine/game/interface/mod_catalogue.zig @@ -789,9 +789,9 @@ pub const ModCatalogue = struct { } /// Draws the screen over the background: the title, the list, the panel, the buttons with the one - /// under the pointer highlighted and the ones that do nothing dimmed, OpenReliant's version, and - /// the pointer. - pub fn draw(screen: ModCatalogue, canvas: Canvas, art: *hud.Art, pointer: Pointer) canvas_module.Error!void { + /// under the pointer highlighted and the ones that do nothing dimmed, and OpenReliant's version. + /// The pointer goes over it all, after what scripts draw (`canvas.Canvas.drawPointer`). + pub fn draw(screen: ModCatalogue, canvas: Canvas, art: *hud.Art) canvas_module.Error!void { try title.write(canvas, canvas.fonts.large, canvas_module.white); list_frame.draw(canvas); details_frame.draw(canvas); @@ -802,7 +802,6 @@ pub const ModCatalogue = struct { try button.shown(doing).draw(canvas.dimmedUnless(screen.usable(button)), art, settings.button_shapes, std.meta.eql(screen.lit, Item{ .button = button })); } try canvas.drawVersion(); - try canvas.onScreen().shape(art, pointer.shape(), pointer.at); } /// Draws the visible rows: a group's heading in the large font, in blue, like the controls tab's diff --git a/src/engine/game/interface/mod_manager.zig b/src/engine/game/interface/mod_manager.zig index 5a77f3de..77c63082 100644 --- a/src/engine/game/interface/mod_manager.zig +++ b/src/engine/game/interface/mod_manager.zig @@ -630,9 +630,9 @@ pub const ModManager = struct { } /// The screen's drawing, over the background: the title, the note while the mods wait for the - /// next start, the list and the panel, the buttons, the one under the pointer lit, OpenReliant's - /// version, then the pointer. - pub fn draw(screen: ModManager, canvas: Canvas, art: *hud.Art, pointer: Pointer) canvas_module.Error!void { + /// next start, the list and the panel, the buttons, the one under the pointer lit, and + /// OpenReliant's version. The pointer goes over it all, after what scripts draw (`canvas.Canvas.drawPointer`). + pub fn draw(screen: ModManager, canvas: Canvas, art: *hud.Art) canvas_module.Error!void { try title.write(canvas, canvas.fonts.large, canvas_module.white); if (screen.waits()) try restart_note.write(canvas, canvas.fonts.small, canvas_module.gold); list_frame.draw(canvas); @@ -646,7 +646,6 @@ pub const ModManager = struct { } try movers.draw(canvas, art, screen.litArrow(.move)); try canvas.drawVersion(); - try canvas.onScreen().shape(art, pointer.shape(), pointer.at); } /// The rows whose mods load, as the rows stand: the mods that are on, aren't damaged, and have diff --git a/src/engine/game/interface/mod_options.zig b/src/engine/game/interface/mod_options.zig index e3124560..2368dfdf 100644 --- a/src/engine/game/interface/mod_options.zig +++ b/src/engine/game/interface/mod_options.zig @@ -486,8 +486,8 @@ pub const ModOptions = struct { } /// The screen's drawing, over the background: the title, the list, the buttons, the one under the - /// pointer lit, the text of the option under it, OpenReliant's version, then the pointer. - pub fn draw(screen: *const ModOptions, canvas: Canvas, art: *hud.Art, pointer: Pointer) canvas_module.Error!void { + /// pointer lit, the text of the option under it, and OpenReliant's version. The pointer goes over it all, after what scripts draw (`canvas.Canvas.drawPointer`). + pub fn draw(screen: *const ModOptions, canvas: Canvas, art: *hud.Art) canvas_module.Error!void { try (Label{ .text = .{ .words = screen.page.title }, .at = title_at, .alignment = .centre }).write(canvas, canvas.fonts.large, canvas_module.white); var buffer: [max_options]Line.Shown = undefined; var numbers: [max_options][number_words]u8 = undefined; @@ -505,7 +505,6 @@ pub const ModOptions = struct { try canvas.wrapped(canvas.fonts.small, descriptionAt(screen.pane), description, canvas_module.blue, .centre, description_lines); }; try canvas.drawVersion(); - try canvas.onScreen().shape(art, pointer.shape(), pointer.at); } }; diff --git a/src/engine/game/interface/pilot_roster.zig b/src/engine/game/interface/pilot_roster.zig index a7657645..ed446295 100644 --- a/src/engine/game/interface/pilot_roster.zig +++ b/src/engine/game/interface/pilot_roster.zig @@ -468,9 +468,10 @@ pub const Roster = struct { /// `pilot_roster_draw`: the pilot chosen, lit; the labels in blue; the call sign's frame and /// the call sign, white while it is typed, with its cursor; the buttons and the list's arrow, /// and the button under the pointer lit with its label in white; the list, where it is open; - /// SET GAME DIFFICULTY or QUIT's question, where either is up; then the pointer. The roster's - /// background is drawn behind it all (`background_set`). - pub fn draw(roster: Roster, canvas: Canvas, art: *hud.Art, dialog_art: *hud.Art, pointer: Pointer, pilot: Pilot) canvas_module.Error!void { + /// SET GAME DIFFICULTY or QUIT's question, where either is up. The roster's background is drawn + /// behind it all (`background_set`), and the pointer over it all, after what scripts draw + /// (`canvas.Canvas.drawPointer`). + pub fn draw(roster: Roster, canvas: Canvas, art: *hud.Art, dialog_art: *hud.Art, pilot: Pilot) canvas_module.Error!void { const small = canvas.fonts.small; if (pilot.female) try canvas.shape(art, female_shape, female_at) else try canvas.shape(art, male_shape, male_at); for (labels.values) |held| if (held) |label| try writeLabel(canvas, label, canvas_module.blue); @@ -497,7 +498,6 @@ pub const Roster = struct { if (roster.difficulty) |box| try box.draw(canvas, art, pilot.difficulty); if (roster.confirm) |confirm| try confirm.draw(canvas, dialog_art); try canvas.drawVersion(); - try canvas.onScreen().shape(art, pointer.shape(), pointer.at); } }; diff --git a/src/engine/game/interface/restart.zig b/src/engine/game/interface/restart.zig index 9726978c..755f0662 100644 --- a/src/engine/game/interface/restart.zig +++ b/src/engine/game/interface/restart.zig @@ -119,15 +119,25 @@ pub const Restart = struct { } /// `restart_screen_draw` (`0x0043ED90`), at the screen's brightness over a cleared frame: its - /// picture, the button under the pointer lit, the three labels in the small font, and the - /// pointer until a choice is made. - pub fn draw(screen: Restart, canvas: Canvas, art: *hud.Art, pointer: Pointer) canvas_module.Error!void { - var faded = canvas; - faded.brightness = screen.brightness; - try faded.shape(art, picture_shape, picture_at); - if (screen.under) |under| try faded.shape(art, lit_shape, lit_at.get(under)); - for (labels.values) |label| try label.write(faded, faded.fonts.small, label_colour); - if (screen.chosen == null) try faded.onScreen().shape(art, pointer.shape(), pointer.at); + /// picture, the button under the pointer lit, and the three labels in the small font. The + /// pointer goes over it, after what scripts draw (`drawPointer`). + pub fn draw(screen: Restart, canvas: Canvas, art: *hud.Art) canvas_module.Error!void { + try screen.faded(canvas).shape(art, picture_shape, picture_at); + if (screen.under) |under| try screen.faded(canvas).shape(art, lit_shape, lit_at.get(under)); + for (labels.values) |label| try label.write(screen.faded(canvas), canvas.fonts.small, label_colour); + } + + /// The end of `restart_screen_draw`: the pointer, at the screen's brightness, until a choice is + /// made (`canvas.Canvas.drawPointer`). + pub fn drawPointer(screen: Restart, canvas: Canvas, art: *hud.Art, pointer: Pointer) canvas_module.Error!void { + if (screen.chosen == null) try screen.faded(canvas).drawPointer(art, pointer); + } + + /// `canvas` at the screen's brightness. + fn faded(screen: Restart, canvas: Canvas) Canvas { + var at_brightness = canvas; + at_brightness.brightness = screen.brightness; + return at_brightness; } }; @@ -151,3 +161,27 @@ test Restart { _ = screen.frame(.{ .at = .{ 250, 380 }, .down = true }, &keyboard, 1); try std.testing.expectEqual(null, screen.chosen); } + +test "Restart.drawPointer" { + const gpa = std.testing.allocator; + const device = @import("../../surrender/srd3d/device.zig"); + const language = @import("../language.zig"); + const spr = @import("../../../formats/spr.zig"); + var recorder: device.testing.Recorder = .{ .gpa = gpa }; + defer recorder.deinit(); + const strings: language.Language = .{ .strings = &.{} }; + var font: hud.Opened = undefined; + const drawn: Canvas = .{ .gpa = gpa, .target = recorder.interface(), .window = .{ 640, 480 }, .fonts = .{ .large = &font, .small = &font }, .strings = &strings }; + // A set with a shape for each of the pointer's. + const bytes = try spr.testing.paletteAndShapes(gpa, 17); + defer gpa.free(bytes); + var art: hud.Art = try .init(gpa, try .parse(bytes), null, null); + defer art.deinit(gpa); + // The pointer shows until a choice is made. + var screen: Restart = .{}; + try screen.drawPointer(drawn, &art, .{ .at = .{ 100, 100 } }); + try std.testing.expectEqual(1, recorder.draws.items.len); + screen.chosen = .main_menu; + try screen.drawPointer(drawn, &art, .{ .at = .{ 100, 100 } }); + try std.testing.expectEqual(1, recorder.draws.items.len); +} diff --git a/src/engine/game/interface/saved_games.zig b/src/engine/game/interface/saved_games.zig index 6fd53e81..272d22de 100644 --- a/src/engine/game/interface/saved_games.zig +++ b/src/engine/game/interface/saved_games.zig @@ -560,8 +560,8 @@ pub const SavedGames = struct { /// title; saving, the name's frame and, while it is typed, the name with its cursor and OK; /// the labels and the date; the buttons, the one under the pointer lit with its label in white; /// the rows shown, the selected one white over a bar; the selected save's details; the arrows; - /// QUIT's question or the box saying a save failed; OpenReliant's version; then the pointer. - pub fn draw(screen: SavedGames, canvas: Canvas, art: *hud.Art, dialog_art: *hud.Art, pointer: Pointer, call_sign: []const u8) canvas_module.Error!void { + /// QUIT's question or the box saying a save failed; and OpenReliant's version. The pointer goes over it all, after what scripts draw (`canvas.Canvas.drawPointer`). + pub fn draw(screen: SavedGames, canvas: Canvas, art: *hud.Art, dialog_art: *hud.Art, call_sign: []const u8) canvas_module.Error!void { const large = canvas.fonts.large; const small = canvas.fonts.small; canvas.box(list_box_at, list_box_size); @@ -617,7 +617,6 @@ pub const SavedGames = struct { if (screen.confirm) |confirm| try confirm.draw(canvas, dialog_art); if (screen.save_error) |box| try box.draw(canvas, dialog_art); try canvas.drawVersion(); - try canvas.onScreen().shape(art, pointer.shape(), pointer.at); } }; diff --git a/src/engine/game/interface/settings.zig b/src/engine/game/interface/settings.zig index 6707ae7d..80e45219 100644 --- a/src/engine/game/interface/settings.zig +++ b/src/engine/game/interface/settings.zig @@ -566,8 +566,8 @@ pub const Settings = struct { /// The screen's drawing (`controls_screen_draw`, `0x0042CD30`, `audio_screen_draw`, /// `0x0042E2E0`, `video_screen_draw`, `0x0042F440`): the tabs' labels, the shown one white and /// one under the pointer gold, the tab, the buttons, the one under the pointer lit, Escape's - /// question where it is up, OpenReliant's version, then the pointer. - pub fn draw(screen: Settings, canvas: Canvas, art: *hud.Art, dialog_art: *hud.Art, shown: Shown, pointer: Pointer) canvas_module.Error!void { + /// question where it is up, and OpenReliant's version. The pointer goes over it all, after what scripts draw (`canvas.Canvas.drawPointer`). + pub fn draw(screen: Settings, canvas: Canvas, art: *hud.Art, dialog_art: *hud.Art, shown: Shown) canvas_module.Error!void { for (std.enums.values(Tab)) |tab| { const colour = if (tab == screen.tab) canvas_module.white else if (screen.lit_tab == tab) canvas_module.gold else canvas_module.blue; try tab.label().write(canvas, canvas.fonts.large, colour); @@ -580,7 +580,6 @@ pub const Settings = struct { } if (screen.question) |question| try question.draw(canvas, dialog_art); try canvas.drawVersion(); - try canvas.onScreen().shape(art, pointer.shape(), pointer.at); } }; diff --git a/src/engine/genilib/interf.zig b/src/engine/genilib/interf.zig index 7e3c5523..a69a62cd 100644 --- a/src/engine/genilib/interf.zig +++ b/src/engine/genilib/interf.zig @@ -763,23 +763,33 @@ pub const Interface = struct { // A mod's screen stands in for the screen's own, drawn by its scripts over this. if (front.scripted_shown != null) return; switch (front.screen) { - .main_menu => try front.main_menu.draw(drawn, art, &resources.dialog, front.pointer, &resources.developer.font), - .game_options => try front.game_options.draw(drawn, art, &resources.dialog, &resources.about, front.pointer), - .audio, .controls, .video => try front.settings.draw(drawn, art, &resources.dialog, shown, front.pointer), - .mods => try front.mod_manager.draw(drawn, art, front.pointer), - .mod_options => try front.mod_options.draw(drawn, art, front.pointer), - .mod_catalog => try front.mod_catalogue.draw(drawn, art, front.pointer), - .game_modes => try front.game_modes.draw(drawn, art, front.pointer), - .pilot_roster => try front.pilot_roster.draw(drawn, art, &resources.dialog, front.pointer, front.pilot), - .saved_games => try front.saved_games.draw(drawn, art, &resources.dialog, front.pointer, front.pilot.call_sign.slice()), + .main_menu => try front.main_menu.draw(drawn, art, &resources.dialog, &resources.developer.font), + .game_options => try front.game_options.draw(drawn, art, &resources.dialog, &resources.about), + .audio, .controls, .video => try front.settings.draw(drawn, art, &resources.dialog, shown), + .mods => try front.mod_manager.draw(drawn, art), + .mod_options => try front.mod_options.draw(drawn, art), + .mod_catalog => try front.mod_catalogue.draw(drawn, art), + .game_modes => try front.game_modes.draw(drawn, art), + .pilot_roster => try front.pilot_roster.draw(drawn, art, &resources.dialog, front.pilot), + .saved_games => try front.saved_games.draw(drawn, art, &resources.dialog, front.pilot.call_sign.slice()), else => {}, } } - /// The pointer over a mod's screen that stands in for the front end's own, which the front end - /// draws after the scripts' drawing, as each of its own screens draws the pointer last. + /// Whether the front end shows a screen, which `draw` draws, or a mod's screen in its place. + fn showsScreen(front: Interface) bool { + if (front.scripted_shown != null) return true; + return switch (front.screen) { + .main_menu, .game_options, .audio, .controls, .video, .mods, .mod_options, .mod_catalog, .game_modes, .pilot_roster, .saved_games => true, + else => false, + }; + } + + /// The pointer over the screen shown, its own or a mod's, which each of the front end's screens + /// draws last. The front end draws it after the scripts' drawing, so that it stays on top + /// (`canvas.Canvas.drawPointer`). pub fn drawPointer(front: Interface, resources: *Resources, target: device.Device, window: [2]u32, strings: *const language.Language) canvas.Error!void { - if (front.scripted_shown == null) return; + if (!front.showsScreen()) return; const art = if (resources.shapes) |*shapes| shapes else return; const drawn: canvas.Canvas = .{ .gpa = resources.gpa, @@ -788,7 +798,7 @@ pub const Interface = struct { .fonts = .{ .large = &resources.large.font, .small = &resources.small.font }, .strings = strings, }; - try drawn.onScreen().shape(art, front.pointer.shape(), front.pointer.at); + try drawn.drawPointer(art, front.pointer); } }; diff --git a/src/openreliant/main.zig b/src/openreliant/main.zig index 1c04cf3d..62f77811 100644 --- a/src/openreliant/main.zig +++ b/src/openreliant/main.zig @@ -2240,7 +2240,7 @@ const FrontEndDisplay = struct { const shown: *FrontEndDisplay = @ptrCast(@alignCast(context)); try shown.front.draw(shown.resources, shown.target, shown.window, shown.strings, shown.settings, version.string); if (shown.presentation) |scripts| try scripts.draw(.ui, shown.target, null); - // The pointer over a mod's screen that stands in for the front end's own. + // The pointer, over what the scripts draw. try shown.front.drawPointer(shown.resources, shown.target, shown.window, shown.strings); if (shown.console) |console| try console.draw(shown.target, shown.window, shown.strings, .menus); } @@ -2577,9 +2577,9 @@ const Display = struct { fn drawOverlay(display: *Display) !void { if (display.console) |console| if (console.isUp()) return console.draw(display.device, display.screen, display.strings, .mission); if (display.pause_menu.isOpen()) { - try display.drawPauseMenu(); + try display.pause_menu.draw(display.pauseFrame()); if (display.presentation) |scripts| try scripts.draw(.ui, display.device, null); - return; + return display.pause_menu.drawPointer(display.pauseFrame()); } try display.drawDisplay(); if (display.presentation) |scripts| { @@ -2588,8 +2588,9 @@ const Display = struct { } } - fn drawPauseMenu(display: *Display) !void { - return display.pause_menu.draw(.{ + /// What the pause menu draws with this frame. + fn pauseFrame(display: *Display) game.hudoptions.Frame { + return .{ .target = display.device, .screen = display.screen, .ui_scale = display.ui_scale, @@ -2600,7 +2601,7 @@ const Display = struct { .settings = display.settings, .version = version.string, .timer = platform.window.ticks(), - }); + }; } /// Draws the display from the drawn frame's copy of the game, then hands the game what the diff --git a/src/openreliant/rooms.zig b/src/openreliant/rooms.zig index c86ce8d2..9831dfd1 100644 --- a/src/openreliant/rooms.zig +++ b/src/openreliant/rooms.zig @@ -992,8 +992,18 @@ const Backdrop = struct { /// left out. fn draw(backdrop: *Backdrop, target: canvas.Canvas) ?*game.hud.Art { if (backdrop.background.image) |*shown| target.fill(shown); + return backdrop.art(); + } + + /// The shapes the screen draws with; null where they are left out. + fn art(backdrop: *Backdrop) ?*game.hud.Art { return if (backdrop.shapes) |*loaded| &loaded.art else null; } + + /// The screen's pointer, with its shapes (`canvas.Canvas.drawPointer`). + fn drawPointer(backdrop: *Backdrop, target: canvas.Canvas, pointer: canvas.Pointer) canvas.Error!void { + if (backdrop.art()) |shapes| try target.drawPointer(shapes, pointer); + } }; /// The in-game options, and what they draw with, ABOUT STARLANCER's shapes among it. @@ -1007,10 +1017,10 @@ const Menu = struct { if (menu.about_shapes) |*shapes| shapes.deinit(gpa); } - fn draw(menu: *Menu, target: canvas.Canvas, dialog: *game.hud.Art, pointer: canvas.Pointer) canvas.Error!void { + fn draw(menu: *Menu, target: canvas.Canvas, dialog: *game.hud.Art) canvas.Error!void { const shapes = menu.backdrop.draw(target) orelse return; const about = if (menu.about_shapes) |*loaded| &loaded.art else null; - try menu.state.draw(target, shapes, dialog, about, pointer); + try menu.state.draw(target, shapes, dialog, about); } }; @@ -1023,9 +1033,9 @@ const SavesScreen = struct { screen.backdrop.close(gpa); } - fn draw(screen: *SavesScreen, target: canvas.Canvas, dialog: *game.hud.Art, pointer: canvas.Pointer, call_sign: []const u8) canvas.Error!void { + fn draw(screen: *SavesScreen, target: canvas.Canvas, dialog: *game.hud.Art, call_sign: []const u8) canvas.Error!void { const shapes = screen.backdrop.draw(target) orelse return; - try screen.state.draw(target, shapes, dialog, pointer, call_sign); + try screen.state.draw(target, shapes, dialog, call_sign); } }; @@ -1038,9 +1048,9 @@ const SettingsScreen = struct { screen.backdrop.close(gpa); } - fn draw(screen: *SettingsScreen, target: canvas.Canvas, dialog: *game.hud.Art, shown: settings.Shown, pointer: canvas.Pointer) canvas.Error!void { + fn draw(screen: *SettingsScreen, target: canvas.Canvas, dialog: *game.hud.Art, shown: settings.Shown) canvas.Error!void { const shapes = screen.backdrop.draw(target) orelse return; - try screen.state.draw(target, shapes, dialog, shown, pointer); + try screen.state.draw(target, shapes, dialog, shown); } }; @@ -1049,9 +1059,14 @@ const Restarting = struct { state: restart_screen.Restart = .{}, shapes: ?canvas.Shapes = null, - fn draw(screen: *Restarting, target: canvas.Canvas, pointer: canvas.Pointer) canvas.Error!void { + fn draw(screen: *Restarting, target: canvas.Canvas) canvas.Error!void { + const shapes = if (screen.shapes) |*loaded| &loaded.art else return; + try screen.state.draw(target, shapes); + } + + fn drawPointer(screen: *Restarting, target: canvas.Canvas, pointer: canvas.Pointer) canvas.Error!void { const shapes = if (screen.shapes) |*loaded| &loaded.art else return; - try screen.state.draw(target, shapes, pointer); + try screen.state.drawPointer(target, shapes, pointer); } }; @@ -1124,11 +1139,11 @@ const Shown = struct { switch (shown.screen) { .induction => |tour| tour.draw(target), .rooms => |inside| try inside.draw(target, driver.clock.game_ticks), - .options => |menu| try menu.draw(target, &driver.front.dialog, driver.pointer), - .saved_games => |screen| try screen.draw(target, &driver.front.dialog, driver.pointer, driver.pilot.call_sign.slice()), - .settings => |screen| try screen.draw(target, &driver.front.dialog, .{ .devices = driver.movies.devices, .sound = driver.sound, .video = driver.video }, driver.pointer), + .options => |menu| try menu.draw(target, &driver.front.dialog), + .saved_games => |screen| try screen.draw(target, &driver.front.dialog, driver.pilot.call_sign.slice()), + .settings => |screen| try screen.draw(target, &driver.front.dialog, .{ .devices = driver.movies.devices, .sound = driver.sound, .video = driver.video }), .briefing => |meeting| try meeting.draw(target), - .restart => |screen| try screen.draw(target, driver.pointer), + .restart => |screen| try screen.draw(target), .credits => |screen| try screen.draw(target), .itac => |terminal| try terminal.draw(target), .pod => |pod| try pod.draw(target), @@ -1136,15 +1151,19 @@ const Shown = struct { .cd_player => |player| try player.draw(target), } if (driver.movies.scripts) |scripts| try scripts.drawUi(target.target); - // The pointer of the rooms and their places goes over what scripts draw, as the front - // end's does over a mod's screen. The menus draw theirs as the front end's own screens do. + // The pointer of the rooms, their places and the menus goes over what scripts draw, as the + // front end's does. switch (shown.screen) { .rooms => |inside| try inside.drawPointer(target), .itac => |terminal| try terminal.drawPointer(target), .pod => |pod| try pod.drawPointer(target), .locker => |case| try case.drawPointer(target), .cd_player => |player| try player.drawPointer(target), - .induction, .options, .saved_games, .settings, .briefing, .restart, .credits => {}, + .options => |menu| try menu.backdrop.drawPointer(target, driver.pointer), + .saved_games => |screen| try screen.backdrop.drawPointer(target, driver.pointer), + .settings => |screen| try screen.backdrop.drawPointer(target, driver.pointer), + .restart => |screen| try screen.drawPointer(target, driver.pointer), + .induction, .briefing, .credits => {}, } if (driver.console) |console| try console.shown.draw(target.target, shown.window, driver.strings, .menus); }