diff --git a/build.zig b/build.zig index cd3db8c..d930e64 100644 --- a/build.zig +++ b/build.zig @@ -465,6 +465,7 @@ pub fn build(b: *std.Build) void { "src/input/guide_chord.zig", "src/session/message_protocol.zig", "src/ui/keyboard.zig", + "src/ui/control_icons.zig", "src/ui/navigation_repeat.zig", "src/ui/persistent_settings.zig", "src/ui/stream_dimensions.zig", diff --git a/packaging/portmaster/greenovercast/screenshot.png b/packaging/portmaster/greenovercast/screenshot.png index a80b362..08e8b99 100644 Binary files a/packaging/portmaster/greenovercast/screenshot.png and b/packaging/portmaster/greenovercast/screenshot.png differ diff --git a/src/app/release.zig b/src/app/release.zig index 06d8021..0a18887 100644 --- a/src/app/release.zig +++ b/src/app/release.zig @@ -171,7 +171,7 @@ pub const Release = struct { return c.go_sdl_platform_controller(self.platform); } - fn drawLoading(self: *Release, heading: [*c]const u8, detail: [*c]const u8, action: [*c]const u8) void { + fn drawLoading(self: *Release, heading: [*c]const u8, detail: [*c]const u8, action: c.GoHandheldUiAction) void { c.go_handheld_ui_draw_loading(self.ui(), heading, detail, action); } @@ -198,12 +198,12 @@ pub const Release = struct { } pub fn refreshAuth(self: *Release) Result { - self.drawLoading("SIGNING IN", "REFRESHING XBOX SESSION", null); + self.drawLoading("SIGNING IN", "REFRESHING XBOX SESSION", c.GO_HANDHELD_UI_ACTION_NONE); debug("Refreshing auth\n", .{}); return switch (c.go_xbox_auth_refresh(self.auth)) { c.GO_XBOX_AUTH_OK => .ok, c.GO_XBOX_AUTH_REAUTH_REQUIRED => result: { - self.drawLoading("SIGN IN EXPIRED", "REQUESTING A NEW DEVICE CODE", null); + self.drawLoading("SIGN IN EXPIRED", "REQUESTING A NEW DEVICE CODE", c.GO_HANDHELD_UI_ACTION_NONE); c.SDL_Delay(700); break :result .reauth_required; }, @@ -231,7 +231,7 @@ pub const Release = struct { std.debug.print("Catalog service could not be initialized\n", .{}); return .failed; }; - self.drawLoading("LOADING LIBRARY", "CHECKING YOUR CLOUD GAMES", "B BACK"); + self.drawLoading("LOADING LIBRARY", "CHECKING YOUR CLOUD GAMES", c.GO_HANDHELD_UI_ACTION_BACK); debug("Loading cloud catalog\n", .{}); const result = self.catalog.?.load() catch |err| { if (err == error.EmptyCatalog) @@ -261,7 +261,7 @@ pub const Release = struct { } pub fn signOut(self: *Release) Result { - self.drawLoading("SIGNING OUT", "REMOVING XBOX CREDENTIALS", null); + self.drawLoading("SIGNING OUT", "REMOVING XBOX CREDENTIALS", c.GO_HANDHELD_UI_ACTION_NONE); if (c.go_xbox_auth_sign_out(self.auth) != 0) { std.debug.print("Xbox credentials could not be removed\n", .{}); return .failed; @@ -274,7 +274,7 @@ pub const Release = struct { pub fn createSession(self: *Release) Result { if (self.title_id[0] == 0) return .failed; - self.drawLoading("STARTING GAME", "ALLOCATING CLOUD SESSION", "B CANCEL"); + self.drawLoading("STARTING GAME", "ALLOCATING CLOUD SESSION", c.GO_HANDHELD_UI_ACTION_CANCEL); debug("Creating session ({s})\n", .{std.mem.sliceTo(&self.title_id, 0)}); if (c.go_cloud_session_start_game(self.cloud, @ptrCast(&self.title_id)) < 0) { std.debug.print("Session creation failed\n", .{}); diff --git a/src/auth/xbox_auth.zig b/src/auth/xbox_auth.zig index c6c9666..ffa1619 100644 --- a/src/auth/xbox_auth.zig +++ b/src/auth/xbox_auth.zig @@ -168,7 +168,7 @@ fn deviceSignIn(auth: *Auth, ui: *c.GoHandheldUi) !bool { .{ .key = "client_id", .value = cString(&auth.client_id) }, .{ .key = "scope", .value = oauth_scope }, }); - c.go_handheld_ui_draw_loading(ui, "XBOX SIGN IN", "REQUESTING A DEVICE CODE", "B BACK"); + c.go_handheld_ui_draw_loading(ui, "XBOX SIGN IN", "REQUESTING A DEVICE CODE", c.GO_HANDHELD_UI_ACTION_BACK); var response = c.go_http_request( "POST", device_code_url, @@ -248,7 +248,7 @@ fn deviceSignIn(auth: *Auth, ui: *c.GoHandheldUi) !bool { ui, "SIGNED IN", "OPENING YOUR CLOUD LIBRARY", - null, + c.GO_HANDHELD_UI_ACTION_NONE, ); c.SDL_Delay(700); return true; diff --git a/src/catalog/service.zig b/src/catalog/service.zig index 6d02807..184b6a9 100644 --- a/src/catalog/service.zig +++ b/src/catalog/service.zig @@ -255,7 +255,7 @@ pub const Service = struct { self.uiHandle(), "LOADING LIBRARY", progress.ptr, - "B BACK", + c.GO_HANDHELD_UI_ACTION_BACK, ); if (c.go_handheld_ui_cancel_requested(self.uiHandle()) != 0) return; } diff --git a/src/ui/control_icons.zig b/src/ui/control_icons.zig new file mode 100644 index 0000000..cf3050c --- /dev/null +++ b/src/ui/control_icons.zig @@ -0,0 +1,294 @@ +const font = @import("pixel_font.zig"); +const settings = @import("persistent_settings.zig"); +const style = @import("view_style.zig"); + +const c = @cImport({ + @cInclude("SDL2/SDL.h"); +}); + +pub const Face = enum { + a, + b, + x, + y, +}; + +pub const Icon = enum { + a, + b, + x, + y, + dpad, + left_stick, + right_stick, + left_bumper, + right_bumper, + left_trigger, + right_trigger, + start, + select, + guide, +}; + +pub const Prompt = struct { + icons: [2]Icon, + icon_count: u8, + label: [*:0]const u8, + + pub fn one(icon: Icon, label: [*:0]const u8) Prompt { + return .{ .icons = .{ icon, icon }, .icon_count = 1, .label = label }; + } + + pub fn two(first: Icon, second: Icon, label: [*:0]const u8) Prompt { + return .{ .icons = .{ first, second }, .icon_count = 2, .label = label }; + } +}; + +const label_scale = 2; +const icon_gap = 3; +const label_gap = 5; +const prompt_gap = 12; + +pub fn face(mode: settings.FaceButtonMode, semantic: Face) Icon { + return switch (mode) { + .system => switch (semantic) { + .a => .a, + .b => .b, + .x => .x, + .y => .y, + }, + .swapped => switch (semantic) { + .a => .b, + .b => .a, + .x => .y, + .y => .x, + }, + }; +} + +pub fn rowWidth(prompts: []const Prompt) c_int { + var width: c_int = 0; + for (prompts, 0..) |prompt, index| { + width += promptWidth(prompt); + if (index + 1 < prompts.len) width += prompt_gap; + } + return width; +} + +pub fn drawRow( + renderer_pointer: *anyopaque, + initial_x: c_int, + y: c_int, + prompts: []const Prompt, + color: style.Color, +) void { + var x = initial_x; + for (prompts, 0..) |prompt, index| { + drawPrompt(renderer_pointer, x, y, prompt, color); + x += promptWidth(prompt); + if (index + 1 < prompts.len) x += prompt_gap; + } +} + +pub fn drawCenteredRow( + renderer_pointer: *anyopaque, + y: c_int, + prompts: []const Prompt, + color: style.Color, +) void { + drawRow( + renderer_pointer, + @divTrunc(style.display_width - rowWidth(prompts), 2), + y, + prompts, + color, + ); +} + +pub fn drawIcon( + renderer_pointer: *anyopaque, + x: c_int, + y: c_int, + icon: Icon, + color: style.Color, +) void { + const renderer: *c.SDL_Renderer = @ptrCast(@alignCast(renderer_pointer)); + style.setColor(renderer, color); + switch (icon) { + .a, .b, .x, .y => drawFace(renderer, x, y, icon, color), + .dpad => drawDpad(renderer, x, y), + .left_stick, .right_stick => drawStick(renderer, x, y, icon, color), + .guide => drawGuide(renderer, x, y), + else => drawPill(renderer, x, y, icon, color), + } +} + +pub fn drawSwapPair( + renderer_pointer: *anyopaque, + x: c_int, + y: c_int, + first: Icon, + second: Icon, + color: style.Color, +) void { + const renderer: *c.SDL_Renderer = @ptrCast(@alignCast(renderer_pointer)); + drawIcon(renderer, x, y, first, color); + drawIcon(renderer, x + 56, y, second, color); + style.setColor(renderer, color); + fill(renderer, x + 25, y + 8, 24, 2); + fill(renderer, x + 25, y + 6, 2, 6); + fill(renderer, x + 47, y + 6, 2, 6); + fill(renderer, x + 27, y + 4, 2, 2); + fill(renderer, x + 27, y + 12, 2, 2); + fill(renderer, x + 45, y + 4, 2, 2); + fill(renderer, x + 45, y + 12, 2, 2); +} + +fn promptWidth(prompt: Prompt) c_int { + var width: c_int = 0; + for (0..prompt.icon_count) |index| { + width += iconWidth(prompt.icons[index]); + if (index + 1 < prompt.icon_count) width += icon_gap; + } + if (prompt.label[0] != 0) width += label_gap + font.textWidth(prompt.label, label_scale); + return width; +} + +fn drawPrompt( + renderer_pointer: *anyopaque, + initial_x: c_int, + y: c_int, + prompt: Prompt, + color: style.Color, +) void { + var x = initial_x; + for (0..prompt.icon_count) |index| { + const icon = prompt.icons[index]; + drawIcon(renderer_pointer, x, y, icon, color); + x += iconWidth(icon); + if (index + 1 < prompt.icon_count) x += icon_gap; + } + if (prompt.label[0] == 0) return; + font.text(renderer_pointer, x + label_gap, y + 2, label_scale, prompt.label, color); +} + +fn iconWidth(icon: Icon) c_int { + return switch (icon) { + .a, .b, .x, .y, .dpad, .left_stick, .right_stick, .guide => 18, + .left_bumper, .right_bumper, .left_trigger, .right_trigger => 24, + .start => 37, + .select => 43, + }; +} + +fn drawFace( + renderer: *c.SDL_Renderer, + x: c_int, + y: c_int, + icon: Icon, + color: style.Color, +) void { + drawCircle(renderer, x, y); + font.text(renderer, x + 6, y + 5, 1, iconLabel(icon), color); +} + +fn drawCircle(renderer: *c.SDL_Renderer, x: c_int, y: c_int) void { + fill(renderer, x + 5, y, 8, 2); + fill(renderer, x + 2, y + 2, 3, 3); + fill(renderer, x + 13, y + 2, 3, 3); + fill(renderer, x, y + 5, 2, 8); + fill(renderer, x + 16, y + 5, 2, 8); + fill(renderer, x + 2, y + 13, 3, 3); + fill(renderer, x + 13, y + 13, 3, 3); + fill(renderer, x + 5, y + 16, 8, 2); +} + +fn drawDpad(renderer: *c.SDL_Renderer, x: c_int, y: c_int) void { + fill(renderer, x + 6, y, 6, 6); + fill(renderer, x, y + 6, 18, 6); + fill(renderer, x + 6, y + 12, 6, 6); +} + +fn drawStick( + renderer: *c.SDL_Renderer, + x: c_int, + y: c_int, + icon: Icon, + color: style.Color, +) void { + drawCircle(renderer, x, y); + font.text(renderer, x + 4, y + 5, 1, iconLabel(icon), color); +} + +fn drawGuide(renderer: *c.SDL_Renderer, x: c_int, y: c_int) void { + drawCircle(renderer, x, y); + fill(renderer, x + 5, y + 5, 3, 3); + fill(renderer, x + 10, y + 5, 3, 3); + fill(renderer, x + 7, y + 8, 4, 3); + fill(renderer, x + 5, y + 11, 3, 3); + fill(renderer, x + 10, y + 11, 3, 3); +} + +fn drawPill( + renderer: *c.SDL_Renderer, + x: c_int, + y: c_int, + icon: Icon, + color: style.Color, +) void { + const width = iconWidth(icon); + fill(renderer, x + 2, y, width - 4, 2); + fill(renderer, x, y + 2, 2, 14); + fill(renderer, x + width - 2, y + 2, 2, 14); + fill(renderer, x + 2, y + 16, width - 4, 2); + const label = iconLabel(icon); + const width_label = font.textWidth(label, 1); + font.text(renderer, x + @divTrunc(width - width_label, 2), y + 5, 1, label, color); +} + +fn iconLabel(icon: Icon) [*:0]const u8 { + return switch (icon) { + .a => "A", + .b => "B", + .x => "X", + .y => "Y", + .left_stick => "L3", + .right_stick => "R3", + .left_bumper => "LB", + .right_bumper => "RB", + .left_trigger => "LT", + .right_trigger => "RT", + .start => "START", + .select => "SELECT", + .dpad, .guide => "", + }; +} + +fn fill(renderer: *c.SDL_Renderer, x: c_int, y: c_int, width: c_int, height: c_int) void { + var rect = c.SDL_Rect{ .x = x, .y = y, .w = width, .h = height }; + _ = c.SDL_RenderFillRect(renderer, &rect); +} + +test "swapped face buttons describe the physical button" { + try @import("std").testing.expectEqual(Icon.a, face(.system, .a)); + try @import("std").testing.expectEqual(Icon.b, face(.swapped, .a)); + try @import("std").testing.expectEqual(Icon.a, face(.swapped, .b)); + try @import("std").testing.expectEqual(Icon.y, face(.swapped, .x)); + try @import("std").testing.expectEqual(Icon.x, face(.swapped, .y)); +} + +test "library prompts fit the display" { + const primary = [_]Prompt{ + Prompt.one(.a, "PLAY"), + Prompt.one(.y, "FAVORITE"), + Prompt.one(.x, "SEARCH"), + Prompt.one(.b, "BACK"), + }; + const secondary = [_]Prompt{ + Prompt.two(.left_bumper, .right_bumper, "TAB"), + Prompt.two(.left_trigger, .right_trigger, "LETTER"), + Prompt.one(.start, "SETTINGS"), + }; + try @import("std").testing.expect(rowWidth(&primary) <= style.display_width - 32); + try @import("std").testing.expect(rowWidth(&secondary) <= style.display_width - 32); +} diff --git a/src/ui/handheld_ui.h b/src/ui/handheld_ui.h index 81ce281..8f53382 100644 --- a/src/ui/handheld_ui.h +++ b/src/ui/handheld_ui.h @@ -18,12 +18,19 @@ enum { GO_HANDHELD_UI_PICK_SIGN_OUT = -2, }; +typedef enum { + GO_HANDHELD_UI_ACTION_NONE = 0, + GO_HANDHELD_UI_ACTION_BACK, + GO_HANDHELD_UI_ACTION_CANCEL, + GO_HANDHELD_UI_ACTION_RETRY_BACK, +} GoHandheldUiAction; + GoHandheldUi* go_handheld_ui_create(SDL_Renderer* renderer, GoControllerInput* controller, GoUiStopRequested stop_requested, void* stop_context); void go_handheld_ui_destroy(GoHandheldUi* ui); void go_handheld_ui_draw_loading(GoHandheldUi* ui, const char* heading, const char* detail, - const char* action); + GoHandheldUiAction action); void go_handheld_ui_draw_device_code(GoHandheldUi* ui, const char* user_code, const char* status, unsigned int seconds_remaining); int go_handheld_ui_wait(GoHandheldUi* ui, Uint32 milliseconds); diff --git a/src/ui/handheld_ui.zig b/src/ui/handheld_ui.zig index 73f6644..1348151 100644 --- a/src/ui/handheld_ui.zig +++ b/src/ui/handheld_ui.zig @@ -1,5 +1,6 @@ const std = @import("std"); const artwork = @import("artwork_loader.zig"); +const controls = @import("control_icons.zig"); const keyboard = @import("keyboard.zig"); const library = @import("library_view.zig"); const navigation = @import("navigation_repeat.zig"); @@ -87,7 +88,7 @@ fn cancelRequested(ui: *Ui) bool { return false; } -fn drawLoading(ui: *Ui, heading: [*c]const u8, detail: [*c]const u8, action: [*c]const u8) void { +fn drawLoading(ui: *Ui, heading: [*c]const u8, detail: [*c]const u8, action: c.GoHandheldUiAction) void { if (heading == null or detail == null) return; style.setColor(ui.renderer, style.background()); _ = c.SDL_RenderClear(ui.renderer); @@ -98,13 +99,31 @@ fn drawLoading(ui: *Ui, heading: [*c]const u8, detail: [*c]const u8, action: [*c font.text(ui.renderer, @divTrunc(style.display_width - heading_width, 2), 198, 4, heading, style.bright()); const detail_width = font.textWidth(detail, 2); font.text(ui.renderer, @divTrunc(style.display_width - detail_width, 2), 270, 2, detail, style.accent()); - if (action != null and action[0] != 0) { - const action_width = font.textWidth(action, 2); - font.text(ui.renderer, @divTrunc(style.display_width - action_width, 2), 444, 2, action, style.bright()); - } + drawLoadingAction(ui, action); c.SDL_RenderPresent(ui.renderer); } +fn drawLoadingAction(ui: *Ui, action: c.GoHandheldUiAction) void { + const mode = ui.settings.face_buttons; + if (action == c.GO_HANDHELD_UI_ACTION_BACK) { + const prompts = [_]controls.Prompt{ + controls.Prompt.one(controls.face(mode, .b), "BACK"), + }; + controls.drawCenteredRow(ui.renderer, 439, &prompts, style.bright()); + } else if (action == c.GO_HANDHELD_UI_ACTION_CANCEL) { + const prompts = [_]controls.Prompt{ + controls.Prompt.one(controls.face(mode, .b), "CANCEL"), + }; + controls.drawCenteredRow(ui.renderer, 439, &prompts, style.bright()); + } else if (action == c.GO_HANDHELD_UI_ACTION_RETRY_BACK) { + const prompts = [_]controls.Prompt{ + controls.Prompt.one(controls.face(mode, .a), "RETRY"), + controls.Prompt.one(controls.face(mode, .b), "BACK"), + }; + controls.drawCenteredRow(ui.renderer, 439, &prompts, style.bright()); + } +} + fn signInAction(ui: *Ui) c_int { var event: c.SDL_Event = undefined; while (c.SDL_PollEvent(&event) != 0) { @@ -206,8 +225,18 @@ fn drawKeyboard( x += width + keyboard.gap_width; } } - font.text(ui.renderer, 16, 408, 2, "A TYPE X DELETE Y CLEAR B CANCEL", style.bright()); - font.text(ui.renderer, 16, 440, 2, "DPAD MOVE START APPLY", style.accent()); + const primary = [_]controls.Prompt{ + controls.Prompt.one(controls.face(ui.settings.face_buttons, .a), "TYPE"), + controls.Prompt.one(controls.face(ui.settings.face_buttons, .x), "DELETE"), + controls.Prompt.one(controls.face(ui.settings.face_buttons, .y), "CLEAR"), + controls.Prompt.one(controls.face(ui.settings.face_buttons, .b), "CANCEL"), + }; + const secondary = [_]controls.Prompt{ + controls.Prompt.one(.dpad, "MOVE"), + controls.Prompt.one(.start, "APPLY"), + }; + controls.drawRow(ui.renderer, 16, 405, &primary, style.bright()); + controls.drawRow(ui.renderer, 16, 437, &secondary, style.accent()); c.SDL_RenderPresent(ui.renderer); } @@ -544,7 +573,7 @@ pub export fn go_handheld_ui_destroy(ui: ?*Ui) void { std.heap.c_allocator.destroy(handle); } -pub export fn go_handheld_ui_draw_loading(ui: ?*Ui, heading: [*c]const u8, detail: [*c]const u8, action: [*c]const u8) void { +pub export fn go_handheld_ui_draw_loading(ui: ?*Ui, heading: [*c]const u8, detail: [*c]const u8, action: c.GoHandheldUiAction) void { drawLoading(ui orelse return, heading, detail, action); } @@ -582,7 +611,10 @@ pub export fn go_handheld_ui_draw_device_code( pointerString(status).?, seconds_remaining / 60, seconds_remaining % 60, }) catch return; font.text(handle.renderer, @divTrunc(style.display_width - font.textWidth(state.ptr, 2), 2), 378, 2, state.ptr, style.accent()); - font.text(handle.renderer, 16, 444, 2, "B CANCEL", style.bright()); + const prompts = [_]controls.Prompt{ + controls.Prompt.one(controls.face(handle.settings.face_buttons, .b), "CANCEL"), + }; + controls.drawCenteredRow(handle.renderer, 439, &prompts, style.bright()); c.SDL_RenderPresent(handle.renderer); } @@ -606,7 +638,7 @@ pub export fn go_handheld_ui_sign_in_action(ui: ?*Ui) c_int { pub export fn go_handheld_ui_wait_for_retry(ui: ?*Ui, heading: [*c]const u8, detail: [*c]const u8) c_int { const handle = ui orelse return 0; - drawLoading(handle, heading, detail, "A RETRY B BACK"); + drawLoading(handle, heading, detail, c.GO_HANDHELD_UI_ACTION_RETRY_BACK); while (true) { const action = signInAction(handle); if (action != 0) return @intFromBool(action > 0); diff --git a/src/ui/library_view.zig b/src/ui/library_view.zig index 6603499..a01ecf8 100644 --- a/src/ui/library_view.zig +++ b/src/ui/library_view.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const controls = @import("control_icons.zig"); const search = @import("catalog_search"); const font = @import("pixel_font.zig"); const settings = @import("persistent_settings.zig"); @@ -152,8 +153,16 @@ pub fn draw( if (view.count == 0) { const empty = if (view.collection == .favorites) "NO FAVORITES YET" else "NO MATCHING GAMES"; font.text(renderer, 28, 206, 3, empty, style.muted()); - font.text(renderer, 16, 434, 2, "LB/RB TAB X SEARCH START SETTINGS", style.bright()); - font.text(renderer, 16, 458, 2, "B BACK", style.accent()); + const primary = [_]controls.Prompt{ + controls.Prompt.two(.left_bumper, .right_bumper, "TAB"), + controls.Prompt.one(controls.face(store.face_buttons, .x), "SEARCH"), + controls.Prompt.one(.start, "SETTINGS"), + }; + const secondary = [_]controls.Prompt{ + controls.Prompt.one(controls.face(store.face_buttons, .b), "BACK"), + }; + controls.drawRow(renderer, 16, 431, &primary, style.bright()); + controls.drawRow(renderer, 16, 455, &secondary, style.accent()); c.SDL_RenderPresent(renderer); return; } @@ -196,8 +205,19 @@ pub fn draw( else font.text(renderer, 452, 218, 2, "LOADING ART", style.muted()); } - font.text(renderer, 16, 434, 2, "A PLAY Y FAVORITE X SEARCH B BACK", style.bright()); - font.text(renderer, 16, 458, 2, "LB/RB TAB LT/RT LETTER START SETTINGS", style.accent()); + const primary = [_]controls.Prompt{ + controls.Prompt.one(controls.face(store.face_buttons, .a), "PLAY"), + controls.Prompt.one(controls.face(store.face_buttons, .y), "FAVORITE"), + controls.Prompt.one(controls.face(store.face_buttons, .x), "SEARCH"), + controls.Prompt.one(controls.face(store.face_buttons, .b), "BACK"), + }; + const secondary = [_]controls.Prompt{ + controls.Prompt.two(.left_bumper, .right_bumper, "TAB"), + controls.Prompt.two(.left_trigger, .right_trigger, "LETTER"), + controls.Prompt.one(.start, "SETTINGS"), + }; + controls.drawRow(renderer, 16, 431, &primary, style.bright()); + controls.drawRow(renderer, 16, 455, &secondary, style.accent()); c.SDL_RenderPresent(renderer); } diff --git a/src/ui/settings_view.zig b/src/ui/settings_view.zig index cbe7daf..c669f67 100644 --- a/src/ui/settings_view.zig +++ b/src/ui/settings_view.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const controls = @import("control_icons.zig"); const navigation = @import("navigation_repeat.zig"); const font = @import("pixel_font.zig"); const settings = @import("persistent_settings.zig"); @@ -46,7 +47,7 @@ pub fn run( c.SDLK_UP => selected = previousRow(selected), c.SDLK_DOWN => selected = nextRow(selected), c.SDLK_RETURN => if (activate(selected, controller, store)) { - const confirmation = confirmSignOut(renderer, controller, stop_requested, stop_context); + const confirmation = confirmSignOut(renderer, controller, store.face_buttons, stop_requested, stop_context); if (confirmation != .back) return confirmation; dirty = true; }, @@ -60,7 +61,7 @@ pub fn run( switch (button) { c.SDL_CONTROLLER_BUTTON_B => return .back, c.SDL_CONTROLLER_BUTTON_A => if (activate(selected, controller, store)) { - const confirmation = confirmSignOut(renderer, controller, stop_requested, stop_context); + const confirmation = confirmSignOut(renderer, controller, store.face_buttons, stop_requested, stop_context); if (confirmation != .back) return confirmation; dirty = true; }, @@ -168,7 +169,12 @@ fn draw( drawMappingExplanation(renderer, store.face_buttons); font.text(renderer, 18, 340, 2, "USE SWAPPED ONLY IF BUTTONS ARE REVERSED", style.muted()); - font.text(renderer, 16, 438, 2, "A CHANGE DPAD MOVE B BACK", style.bright()); + const prompts = [_]controls.Prompt{ + controls.Prompt.one(controls.face(store.face_buttons, .a), "CHANGE"), + controls.Prompt.one(.dpad, "MOVE"), + controls.Prompt.one(controls.face(store.face_buttons, .b), "BACK"), + }; + controls.drawRow(renderer, 16, 435, &prompts, style.bright()); c.SDL_RenderPresent(renderer); } @@ -194,18 +200,22 @@ fn drawMappingExplanation(renderer: *c.SDL_Renderer, mode: settings.FaceButtonMo font.text(renderer, 390, 96, 2, "INPUT MAPPING", style.muted()); if (mode == .system) { font.text(renderer, 390, 142, 2, "SYSTEM", style.bright()); - font.text(renderer, 390, 180, 2, "USE CFW", style.muted()); - font.text(renderer, 390, 208, 2, "BUTTON MAP", style.muted()); + controls.drawIcon(renderer, 390, 176, .a, style.accent()); + controls.drawIcon(renderer, 416, 176, .b, style.accent()); + controls.drawIcon(renderer, 442, 176, .x, style.accent()); + controls.drawIcon(renderer, 468, 176, .y, style.accent()); + font.text(renderer, 390, 208, 2, "CFW BUTTON MAP", style.muted()); } else { font.text(renderer, 390, 142, 2, "SWAPPED", style.bright()); - font.text(renderer, 390, 180, 2, "A / B", style.accent()); - font.text(renderer, 390, 208, 2, "X / Y", style.accent()); + controls.drawSwapPair(renderer, 390, 176, .a, .b, style.accent()); + controls.drawSwapPair(renderer, 390, 208, .x, .y, style.accent()); } } fn confirmSignOut( renderer: *c.SDL_Renderer, controller: *c.GoControllerInput, + face_buttons: settings.FaceButtonMode, stop_requested: StopRequested, stop_context: ?*anyopaque, ) Result { @@ -213,7 +223,11 @@ fn confirmSignOut( _ = c.SDL_RenderClear(renderer); font.text(renderer, 164, 174, 4, "SIGN OUT?", style.bright()); font.text(renderer, 110, 242, 2, "YOU WILL NEED TO LINK XBOX AGAIN", style.muted()); - font.text(renderer, 192, 324, 2, "A YES B NO", style.accent()); + const prompts = [_]controls.Prompt{ + controls.Prompt.one(controls.face(face_buttons, .a), "YES"), + controls.Prompt.one(controls.face(face_buttons, .b), "NO"), + }; + controls.drawCenteredRow(renderer, 319, &prompts, style.accent()); c.SDL_RenderPresent(renderer); while (true) { var event: c.SDL_Event = undefined;