diff --git a/app/Madeira/ConfigCatalog.generated.swift b/app/Madeira/ConfigCatalog.generated.swift index 3e88905389..138d0ca34b 100644 --- a/app/Madeira/ConfigCatalog.generated.swift +++ b/app/Madeira/ConfigCatalog.generated.swift @@ -595,6 +595,7 @@ extension ConfigCatalog { ConfigOption(key: "env.MADEIRA_DOWNLOAD_NOTIFICATIONS", title: "", kind: .bool, defaultValue: "1", category: "Steam & Dock", note: "Local notifications when a Steam download finishes, fails or pauses while Madeira is in the background (permission asked at the first download). 0: none, and no permission request.", choices: [], sources: ["app/Madeira/SteamDownloadBackground.swift"]), ConfigOption(key: "env.MADEIRA_INSTALL_DEFAULT_KEY", title: "", kind: .bool, defaultValue: "1", category: "Steam & Dock", note: "The install scripts' programs of a game: its own folder (one level down) and the shared redistributables Steam keeps next to it, and how many scripts were read.", choices: [], sources: ["app/Madeira/DockInstallers.swift"]), ConfigOption(key: "env.MADEIRA_LIBRARY_COLLAPSE", title: "", kind: .bool, defaultValue: "1", category: "Steam & Dock", note: "MADEIRA_LIBRARY_COLLAPSE=0: the section titles do not collapse.", choices: [], sources: ["app/Madeira/SteamGames.swift"]), + ConfigOption(key: "env.MADEIRA_LIBRARY_TABS", title: "", kind: .bool, defaultValue: "1", category: "Steam & Dock", note: "MADEIRA_LIBRARY_TABS=0: no store tabs, every section on one page.", choices: [], sources: ["app/Madeira/Library.swift"]), ConfigOption(key: "env.MADEIRA_ONBOARDING", title: "First-run Steam setup", kind: .bool, defaultValue: "1", category: "Steam & Dock", note: "`env.MADEIRA_ONBOARDING = 0` (madeira.cfg or the environment) never opens setup and hides \"Run setup again\". On by default.", choices: [], sources: ["app/Madeira/Onboarding.swift"]), ConfigOption(key: "env.MADEIRA_STEAM_ALLOW_CRASHPAD", title: "Steam web helper: allow its crash reporter", kind: .bool, defaultValue: "0", category: "Steam & Dock", note: "ml2210: 1 (the Steam Big Picture list) lets steamwebhelper.exe start exactly one kind of child, --type=crashpad-handler, with its command line unchanged; without it the 32-bit web helper ends itself at start (crash server failed to launch). Every other --type= child stays refused. Off (default): all are refused.", choices: [], sources: ["build/ntdll-unix/process_ios.c"]), ConfigOption(key: "env.MADEIRA_STEAM_CLOUD", title: "", kind: .bool, defaultValue: "1", category: "Steam & Dock", note: "Whether Steam Cloud saves sync: Settings › Steam Cloud saves, kept in madeira.cfg as env.MADEIRA_STEAM_CLOUD (on unless it is 0; docs/STEAM_CLOUD.md). Turning it back on syncs the installed games at once.", choices: [], sources: ["app/Madeira/SteamGames.swift", "app/Madeira/SteamOwnedLibrary.swift"]), diff --git a/app/Madeira/Library.swift b/app/Madeira/Library.swift index f54dc4abf0..04528cd4e4 100644 --- a/app/Madeira/Library.swift +++ b/app/Madeira/Library.swift @@ -2950,6 +2950,91 @@ struct LibrarySectionHeader: View { } } +/// The library's store tabs: with Group by Platform, a segmented control at the top of the +/// page shows one store's section at a time (Steam, the games you added, GOG, itch.io, Epic +/// Games, EA app) or All of them. Only the sections the page would show get a tab, and with +/// fewer than two there are none. env.MADEIRA_LIBRARY_TABS = 0: no tabs, every section on one page. +enum LibraryStoreTab: String, CaseIterable, Identifiable { + case all, steam, other, gog, itch, epic, ea + var id: String { rawValue } + static let key = "madeiraLibraryStoreTab" + static var enabled: Bool { MadeiraConfig.flag("MADEIRA_LIBRARY_TABS") } + + /// Short, so every segment fits across a phone. + var title: String { + switch self { + case .all: return L("All") + case .steam: return "Steam" + case .other: return L("Other") + case .gog: return "GOG" + case .itch: return "itch.io" + case .epic: return "Epic" + case .ea: return "EA" + } + } + + /// All, then a tab for each section the page shows; none with fewer than two sections. + /// `steam` is whether the page has a Steam section. + @MainActor static func available(steam: Bool) -> [LibraryStoreTab] { + guard enabled else { return [] } + var tabs: [LibraryStoreTab] = [] + if steam { tabs.append(.steam) } + tabs.append(.other) + if GOGLibrary.enabled { tabs.append(.gog) } + if ItchLibrary.enabled { tabs.append(.itch) } + if EpicLibrary.enabled { tabs.append(.epic) } + if EALibrary.enabled { tabs.append(.ea) } + return tabs.count < 2 ? [] : [.all] + tabs + } + + /// The stored tab, or All when it is not offered (its store was turned off). + static func current(_ stored: String, in tabs: [LibraryStoreTab]) -> LibraryStoreTab { + guard let tab = LibraryStoreTab(rawValue: stored), tabs.contains(tab) else { return .all } + return tab + } + + func shows(_ section: LibraryStoreTab) -> Bool { self == .all || self == section } +} + +/// The store tabs' control: the system segmented control, or, when its segments do not fit +/// across, a row of capsules that scrolls sideways. +struct LibraryStoreTabBar: View { + let tabs: [LibraryStoreTab] + @Binding var selection: String + + private var picked: Binding { + Binding(get: { LibraryStoreTab.current(selection, in: tabs) }, + set: { tab in withAnimation(UIAccessibility.isReduceMotionEnabled ? nil : .easeInOut(duration: 0.2)) { selection = tab.rawValue } }) + } + + var body: some View { + ViewThatFits(in: .horizontal) { + Picker("Store", selection: picked) { + ForEach(tabs) { Text($0.title).tag($0) } + } + .pickerStyle(.segmented) + .fixedSize() + .frame(maxWidth: .infinity) + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 8) { + ForEach(tabs) { tab in + let on = picked.wrappedValue == tab + Button { picked.wrappedValue = tab } label: { + Text(tab.title).font(.subheadline.weight(.semibold)) + .padding(.horizontal, 14).padding(.vertical, 7) + .foregroundStyle(on ? Color.white : Color.primary) + .background(Capsule().fill(on ? Color.accentColor : Color(uiColor: .tertiarySystemFill))) + } + .buttonStyle(.plain) + .accessibilityAddTraits(on ? .isSelected : []) + } + } + } + } + .accessibilityLabel("Store") + } +} + /// A section's cards or rows in the library's layout ("cards", "compact", /// "list" or "compactList"): every section of the library page uses it. struct LibraryCells: View { @@ -3255,6 +3340,12 @@ struct LibraryView: View { @AppStorage("madeiraLibraryCollapsedGroups") private var collapsedGroups = "" // Collapsed state of the Other games section (MADEIRA_LIBRARY_COLLAPSE=0: no collapsing). @AppStorage("madeiraLibraryHideOthers") private var hideOthers = false + // The store tab shown with Group by Platform (LibraryStoreTab). + @AppStorage(LibraryStoreTab.key) private var storeTab = LibraryStoreTab.all.rawValue + private var storeTabs: [LibraryStoreTab] { + group == "platform" ? LibraryStoreTab.available(steam: MadeiraDock.enabled && (SteamGamesSection.shown || SteamGamesSection.hasInstalled)) : [] + } + private var currentTab: LibraryStoreTab { LibraryStoreTab.current(storeTab, in: storeTabs) } // The sections follow the Steam section's games and sign-in (SteamGames.swift). @ObservedObject private var steamGames = SteamGamesModel.shared @ObservedObject private var steamLibrary = SteamOwnedLibrary.shared @@ -3272,7 +3363,7 @@ struct LibraryView: View { /// and sorting go game by game, so their order among the Steam games is the /// order without them. private var focusOrder: [LibraryEntry] { - guard group != "platform" else { return entries } + guard group != "platform" else { return currentTab.shows(.other) ? entries : [] } // A library file edited by hand (or written by a fork) may repeat an id: keep the first. let positions = Dictionary(model.entries.enumerated().map { ($1.id, $0) }, uniquingKeysWith: { first, _ in first }) let byID = Dictionary(entries.map { ($0.id, $0) }, uniquingKeysWith: { first, _ in first }) @@ -3529,6 +3620,7 @@ struct LibraryView: View { ScrollViewReader { reader in ScrollView { VStack(alignment: .leading, spacing: 24) { + if !storeTabs.isEmpty { LibraryStoreTabBar(tabs: storeTabs, selection: $storeTab) } desktopButton // The library's sections, as in the fork: Steam (installed and downloading // Steam games, then Not installed), then Other games, the games you added. @@ -3546,31 +3638,37 @@ struct LibraryView: View { libraryItem(entry, list: list, dense: dense) } } else { + // A store tab shows only its own section (LibraryStoreTab). + let tab = currentTab let steamFirst = MadeiraDock.enabled && SteamGamesSection.hasInstalled - if steamFirst { + if steamFirst && tab.shows(.steam) { SteamGamesSection(search: search, layout: layout, sort: sort, width: viewport.size.width, part: .installed, open: { selected = $0 }) } if SteamGamesSection.shown { - VStack(alignment: .leading, spacing: 14) { - // Games are added with the + in the navigation bar. - LibrarySectionHeader(title: L("Other games"), count: entries.count, - collapsed: SteamGamesSection.collapsible ? $hideOthers : nil) { EmptyView() } - if hideOthers && SteamGamesSection.collapsible { - EmptyView() - } else if entries.isEmpty { - Text(search.isEmpty - ? "Copy a game's folder into Madeira › wine › drive_c with the Files app, then tap + and choose its .exe." - : L("No other games match your search.")) - .foregroundStyle(.secondary) - } else { - cells(entries, width: viewport.size.width) + if tab.shows(.other) { + VStack(alignment: .leading, spacing: 14) { + // Games are added with the + in the navigation bar. + LibrarySectionHeader(title: L("Other games"), count: entries.count, + collapsed: SteamGamesSection.collapsible ? $hideOthers : nil) { EmptyView() } + if hideOthers && SteamGamesSection.collapsible { + EmptyView() + } else if entries.isEmpty { + Text(search.isEmpty + ? "Copy a game's folder into Madeira › wine › drive_c with the Files app, then tap + and choose its .exe." + : L("No other games match your search.")) + .foregroundStyle(.secondary) + } else { + cells(entries, width: viewport.size.width) + } } } - if MadeiraDock.enabled { + if MadeiraDock.enabled && tab.shows(.steam) { SteamGamesSection(search: search, layout: layout, sort: sort, width: viewport.size.width, part: steamFirst ? .notInstalled : .all, open: { selected = $0 }) } + } else if !tab.shows(.other) { + EmptyView() } else if model.entries.filter({ $0.desktop != true && $0.listedInLibrary && $0.steamAppID == nil && $0.epicAppName == nil && $0.gogProductID == nil && $0.itchGameID == nil && $0.eaOfferID == nil }).isEmpty { ContentUnavailableView("Make yourself at home", systemImage: "gamecontroller", description: Text("Copy a game's folder into Madeira › wine › drive_c with the Files app, then tap + and choose its .exe.")) } else { @@ -3578,21 +3676,21 @@ struct LibraryView: View { } // The account's GOG games (GOGGames.swift): installed ones open their // Game details page, the others their download sheet. - if GOGLibrary.enabled { + if GOGLibrary.enabled && tab.shows(.gog) { GOGGamesSection(search: search, layout: layout, width: viewport.size.width, open: { selected = $0 }) } // The account's itch.io games (ItchGames.swift), the same way. - if ItchLibrary.enabled { + if ItchLibrary.enabled && tab.shows(.itch) { ItchGamesSection(search: search, layout: layout, width: viewport.size.width, open: { selected = $0 }) } // The account's Epic Games games (EpicGames.swift): installed ones open their // Game details page, the others their download sheet. - if EpicLibrary.enabled { + if EpicLibrary.enabled && tab.shows(.epic) { EpicGamesSection(search: search, layout: layout, width: viewport.size.width, open: { selected = $0 }) } // The account's EA app games (EAGames.swift): installed ones open their // Game details page, the others their download sheet. - if EALibrary.enabled { + if EALibrary.enabled && tab.shows(.ea) { EAGamesSection(search: search, layout: layout, width: viewport.size.width, open: { selected = $0 }) } } diff --git a/app/Madeira/LibraryGridPage.swift b/app/Madeira/LibraryGridPage.swift index 6aadc07b06..570d7dd28c 100644 --- a/app/Madeira/LibraryGridPage.swift +++ b/app/Madeira/LibraryGridPage.swift @@ -38,6 +38,8 @@ enum LibraryGridCard: Hashable { /// An element of the page that is not a card: full width, its own height. enum LibraryGridFixed: Hashable { + /// The store tabs (LibraryStoreTab), above the Desktop button. + case storeTabs case desktop /// A section or group title; `collapse` names its stored collapsed state /// ("others", "installed", "group:"), nil when it does not collapse. @@ -53,6 +55,7 @@ enum LibraryGridFixed: Hashable { var id: String { switch self { + case .storeTabs: return "storeTabs" case .desktop: return "desktop" case .header(let id, _, _, _, _): return "header." + id case .signIn: return "signIn" @@ -71,6 +74,15 @@ enum LibraryGridFixed: Hashable { enum LibraryGridStore: String, CaseIterable { case gog, itch, epic, ea + var tab: LibraryStoreTab { + switch self { + case .gog: return .gog + case .itch: return .itch + case .epic: return .epic + case .ea: return .ea + } + } + @MainActor var enabled: Bool { switch self { case .gog: return GOGLibrary.enabled @@ -197,19 +209,27 @@ struct LibraryGridPageModel: Equatable { var collapsedGroups: String /// The shown store sections and their signatures (LibraryGridStore.signatures). var stores: [LibraryGridStore: String] = [:] + /// The store tabs offered (none: every section) and the one shown (LibraryStoreTab). + var tabs: [LibraryStoreTab] = [] + var tab: LibraryStoreTab = .all } @MainActor static func build(_ inputs: Inputs) -> LibraryGridPageModel { var page = LibraryGridPageModel() - page.append(.fixed(.desktop), spacing: 0) + if inputs.group == "platform" && !inputs.tabs.isEmpty { page.append(.fixed(.storeTabs), spacing: 0) } + page.append(.fixed(.desktop), spacing: LibraryGridGeometry.sectionSpacing) if inputs.group != "platform" { page.grouped(inputs) } else { + // A store tab shows only its own section, as on the original page. + let tab = inputs.tab let steamFirst = MadeiraDock.enabled && LibraryGridSteamItems.hasInstalled - if steamFirst { page.steamSection(inputs, part: .installed) } + if steamFirst && tab.shows(.steam) { page.steamSection(inputs, part: .installed) } if LibraryGridSteamItems.shown { - page.othersSection(inputs) - if MadeiraDock.enabled { page.steamSection(inputs, part: steamFirst ? .notInstalled : .all) } + if tab.shows(.other) { page.othersSection(inputs) } + if MadeiraDock.enabled && tab.shows(.steam) { page.steamSection(inputs, part: steamFirst ? .notInstalled : .all) } + } else if !tab.shows(.other) { + // Another store's tab: no games you added. } else if LibraryModel.shared.entries.filter({ $0.desktop != true && $0.listedInLibrary && $0.steamAppID == nil && $0.epicAppName == nil && $0.gogProductID == nil && $0.itchGameID == nil && $0.eaOfferID == nil }).isEmpty { page.append(.fixed(.emptyLibrary), spacing: LibraryGridGeometry.blockSpacing) @@ -217,7 +237,7 @@ struct LibraryGridPageModel: Equatable { page.grid("others", inputs.others, spacing: LibraryGridGeometry.blockSpacing) } // The stores' sections follow, as on the original page. - for store in LibraryGridStore.allCases { + for store in LibraryGridStore.allCases where tab.shows(store.tab) { if let signature = inputs.stores[store] { page.append(.fixed(.store(store, signature: signature)), spacing: LibraryGridGeometry.blockSpacing) } @@ -430,6 +450,7 @@ struct LibraryGridInputsKey: Hashable { var hideOthers: Bool, hideInstalled: Bool, showUninstalled: Bool var collapsedGroups: String var stores: [LibraryGridStore: String] + var tabs: [LibraryStoreTab], tab: LibraryStoreTab var otherIDs: [UUID] var switches: [Bool] /// Sort by size reads the metadata, and sort by added orders by the entries a metadata change can create, so a @@ -441,6 +462,7 @@ struct LibraryGridInputsKey: Hashable { hideOthers = i.hideOthers; hideInstalled = i.hideInstalled; showUninstalled = i.showUninstalled collapsedGroups = i.collapsedGroups stores = i.stores + tabs = i.tabs; tab = i.tab otherIDs = i.others.map(\.id) switches = [SteamGamesSection.collapsible, MadeiraDock.enabled, LibraryGridSteamItems.shown, SteamOwnedLibrary.enabled] metadataMovesCards = i.sort == "size" || i.sort == "added" @@ -495,16 +517,23 @@ struct LibraryGridPage: View { @AppStorage("madeiraSteamShowUninstalled") private var showUninstalled = true @AppStorage("madeiraLibraryCollapsedGroups") private var collapsedGroups = "" @AppStorage(LibraryGridSize.key) private var position: Double = -1 + @AppStorage(LibraryStoreTab.key) private var storeTab = LibraryStoreTab.all.rawValue @State private var steamSheet: SteamSheetID? @State private var showSignIn = false private struct SteamSheetID: Identifiable { let id: Int } + private var storeTabs: [LibraryStoreTab] { + group == "platform" ? LibraryStoreTab.available(steam: MadeiraDock.enabled && (LibraryGridSteamItems.shown || LibraryGridSteamItems.hasInstalled)) : [] + } + var body: some View { + let tabs = storeTabs let inputs = LibraryGridPageModel.Inputs(search: search, sort: sort, group: group, others: others, hideOthers: hideOthers, hideInstalled: hideInstalled, showUninstalled: showUninstalled, collapsedGroups: collapsedGroups, - stores: group == "platform" ? LibraryGridStore.signatures(search: search, layout: layout) : [:]) + stores: group == "platform" ? LibraryGridStore.signatures(search: search, layout: layout) : [:], + tabs: tabs, tab: LibraryStoreTab.current(storeTab, in: tabs)) LibraryGridHostView( build: { LibraryGridPageModel.build(inputs) }, inputsKey: LibraryGridInputsKey(inputs), otherRevision: LibraryGridOtherChanges.shared.revision, layout: layout, position: position, focused: focused, @@ -556,6 +585,8 @@ struct LibraryGridPage: View { private func fixedView(_ fixed: LibraryGridFixed, width: CGFloat) -> AnyView { switch fixed { + case .storeTabs: + return AnyView(LibraryStoreTabBar(tabs: storeTabs, selection: $storeTab)) case .desktop: return desktop() case .header(_, let title, let count, let collapse, _): diff --git a/docs/LIBRARY.md b/docs/LIBRARY.md index f22ffc5c24..5b8e71a8a9 100644 --- a/docs/LIBRARY.md +++ b/docs/LIBRARY.md @@ -57,6 +57,11 @@ Removing an entry never removes the game's files or saves. installed games, Sort by apply to every section. Pull down to read the Steam install records and the account's library again. Without Madeira Dock the games you added are one grid. +- Store tabs: with Group by Platform and two or more sections, a segmented control at + the top of the page shows **All** of them or one at a time (**Steam**, **Other** for the + games you added, **GOG**, **itch.io**, **Epic**, **EA**). Only the sections the page shows + get a tab; the choice is remembered, and a tab whose store is turned off shows All. When + the segments do not fit across, they become capsules that scroll sideways. - Grid cards: a game that is not installed shows its artwork darkened, with a download glyph on a soft circle of blur. Every grid card throws ambient light on the page around it, like an LED strip behind a TV: its artwork's own @@ -549,6 +554,7 @@ menu owns input, the game sees a connected pad at rest. | `MADEIRA_FRONTEND_KEYBOARD` | on | Keyboard opens the game view's own keyboard instead of the key window | | `MADEIRA_ONBOARDING` | on | first-run setup never opens, and Settings › JIT/Steam have no **Run setup again** | | `MADEIRA_LIBRARY_COLLAPSE` | on | the **Steam** and **Other games** titles do not collapse (**Not installed** still folds) | +| `MADEIRA_LIBRARY_TABS` | on | no store tabs: every section on one page | | `MADEIRA_LIBRARY_AMBIENT` | on | no ambient light around the library's grid cards | Opt-in (`env.NAME = 1`), off by default: diff --git a/tests/host/check-library-groups.py b/tests/host/check-library-groups.py index 3ffe8225fe..a5419595e2 100644 --- a/tests/host/check-library-groups.py +++ b/tests/host/check-library-groups.py @@ -51,7 +51,7 @@ def between(text, start, end): require(page.index('if group != "platform" {') < page.index('LibraryGroupedGames(grouping: group, search: search,') < page.index('let steamFirst = MadeiraDock.enabled && SteamGamesSection.hasInstalled'), 'page: the other choices replace the sections, Platform keeps them') -require('let items = focusOrder' in page and 'guard group != "platform" else { return entries }' in view, +require('let items = focusOrder' in page and 'guard group != "platform" else { return currentTab.shows(.other) ? entries : [] }' in view, "controller focus follows the grouped page's order") # ------------------------------------------------------------------ grouped view diff --git a/tests/host/check-library-sections.py b/tests/host/check-library-sections.py index 96703af0b1..63ba9615ce 100644 --- a/tests/host/check-library-sections.py +++ b/tests/host/check-library-sections.py @@ -54,7 +54,7 @@ def between(text, start, end): and 'if SteamGamesSection.shown {' in page[steam_at:others_at], 'Other games is a section exactly when the Steam section is shown; installed Steam games come first') require('part: steamFirst ? .notInstalled : .all, open: { selected = $0 })' in page[others_at:] - and 'if MadeiraDock.enabled {' in page[others_at:], + and 'if MadeiraDock.enabled && tab.shows(.steam) {' in page[others_at:], 'Not installed follows Other games; with nothing installed the whole Steam section does') single = page[others_at:] require('ContentUnavailableView("Make yourself at home"' in single and single.count('cells(entries, width: viewport.size.width)') == 2, @@ -113,6 +113,30 @@ def between(text, start, end): require(re.search(r'\[library-sections\][^"]*\\\((?!SteamOwnedLibrary\.enabled|SteamGamesSection\.)', view) is None, 'the log line has flags only') +# ------------------------------------------------------------------ store tabs +tabs = between(library, 'enum LibraryStoreTab: String, CaseIterable, Identifiable {', 'struct LibraryStoreTabBar: View {') +bar = between(library, 'struct LibraryStoreTabBar: View {', 'struct LibraryCells: View {') +grid_page = (root / 'app/Madeira/LibraryGridPage.swift').read_text() +grid_build = between(grid_page, '@MainActor static func build(_ inputs: Inputs)', 'private mutating func append(') +require('static var enabled: Bool { MadeiraConfig.flag("MADEIRA_LIBRARY_TABS") }' in tabs + and 'guard enabled else { return [] }' in tabs and 'return tabs.count < 2 ? [] : [.all] + tabs' in tabs, + 'store tabs: only the shown sections, none with fewer than two, MADEIRA_LIBRARY_TABS=0 turns them off') +require('guard let tab = LibraryStoreTab(rawValue: stored), tabs.contains(tab) else { return .all }' in tabs, + 'store tabs: a tab that is no longer offered shows All') +require('.pickerStyle(.segmented)' in bar and 'ViewThatFits(in: .horizontal) {' in bar + and 'ScrollView(.horizontal, showsIndicators: false) {' in bar, + 'store tabs: the system segmented control, capsules that scroll when it does not fit') +require('group == "platform" ? LibraryStoreTab.available(' in view + and page.index('LibraryStoreTabBar(tabs: storeTabs, selection: $storeTab)') < page.index('desktopButton'), + 'store tabs: at the top of the page, with Group by Platform only') +require(all(f'tab.shows(.{t})' in page for t in ('steam', 'other', 'gog', 'itch', 'epic', 'ea')), + 'store tabs: each section shows on its tab and on All') +require(grid_build.index('page.append(.fixed(.storeTabs), spacing: 0)') < grid_build.index('page.append(.fixed(.desktop)') + and all(f'tab.shows(.{t})' in grid_build for t in ('steam', 'other')) and 'where tab.shows(store.tab)' in grid_build + and 'tabs = i.tabs; tab = i.tab' in grid_page, + 'store tabs: the zoomable grid shows the same tabs and sections, and rebuilds when the tab changes') +require('MADEIRA_LIBRARY_TABS' in docs, 'docs/LIBRARY.md: the store tabs and their switch') + # ------------------------------------------------------------------ docs require('MADEIRA_LIBRARY_COLLAPSE' in docs and '**Other games**' in docs and '**Not installed**' in docs, 'docs/LIBRARY.md: the sections and the switch') diff --git a/tests/host/check-library-zoom.py b/tests/host/check-library-zoom.py index ec9dddb36f..61840cfaed 100644 --- a/tests/host/check-library-zoom.py +++ b/tests/host/check-library-zoom.py @@ -583,7 +583,7 @@ def between(text, start, end): # The store sections (GOG, itch.io, Epic Games, EA app) follow Steam and Other games on the grid as on the original page, each one # hosted whole; their sheets belong to the page, not to a cell the grid can reuse. build = between(page, '@MainActor static func build(_ inputs: Inputs)', 'private mutating func append(') -stores_ok = 'for store in LibraryGridStore.allCases {' in build and 'page.append(.fixed(.store(store, signature: signature))' in build +stores_ok = 'for store in LibraryGridStore.allCases where tab.shows(store.tab) {' in build and 'page.append(.fixed(.store(store, signature: signature))' in build for section, sheets, model in [('GOGGamesSection', 'GOGSheets', 'GOGGames'), ('ItchGamesSection', 'ItchSheets', 'ItchGames'), ('EpicGamesSection', 'EpicSheets', 'EpicGames'), ('EAGamesSection', 'EASheets', 'EAGames')]: source = (app / f'{model}.swift').read_text()