mirror of
https://github.com/sebastiandine/Card-Collection-Manager-3.git
synced 2026-08-28 22:01:12 +00:00
d3b4762b76
* several functions. fixes #1 and #2 * multi selection functionality
27 KiB
27 KiB
ui_wx/AGENTS.md
ccm_ui_wx static library — wxWidgets adapter. The only target that may include wx/... headers. Read the root AGENTS.md first.
Layer pointers
include/ccm/ui/AppContext.hpp— the boundary type. A struct of references to shared core services + per-game modules and astd::vector<IGameView*>of all UI bundles. UI code talks to core only through this struct (and the typed pointers go throughIGameView, never directly).include/ccm/ui/IGameView.hpp— abstract base class for per-game UI bundles.MainFrameonly ever seesIGameViewreferences; this is the seam that lets the frame swap between Magic, Pokemon, and any future TCG without knowing their card types. OptionalcontentPanel/hostsOwnLayout/contentPanelIfCreatedlet Digimon, Yu-Gi-Oh!, and Pokemon own a tabbed layout without changing Magic’s splitter mounting.attachSharedToolbarEditlets Magic wire MainFrame’s Edit button for multi-select hide/show; hostsOwnLayout games manage their own Edit button viasetToolbarEditVisible.include/ccm/ui/MainFrame.hpp+src/MainFrame.cpp— top-level window (default size1210×770), menu strip (File/Game/Sets/Help), shared toolbar (Add / Edit / Delete + filter input; hidden viatoolbarPanel_whenhostsOwnLayout()), and acontentHost_that either shows the shared splitter (Magic) or a game’sIGameView::contentPanel(Pokémon / Yu-Gi-Oh! / Digimon Digi-Battle notebooks). TheGameandSetsmenus are built dynamically fromAppContext::gameViewsso adding a new game lights up its menu entries automatically. Filter and toolbar actions forward toactiveView(). Edit is hidden when more than one list row is selected.EVT_PREVIEW_STATUS(preview fetch outcome → status label; empty string resets to"Ready") is the only event the frame binds;EVT_CARD_SELECTEDis bound per view (eachIGameViewconnects its typed list panel to its typed selected panel internally). About is a custom themed dialog (notwxAboutBox) so dark mode behavior stays consistent.include/ccm/ui/BaseCardListPanel.hpp— header-only templateBaseCardListPanel<TCard, TSortColumn>that owns ALL the non-game-specificwxListCtrlmachinery: hidden zero-width spacer column (legacy of the MSW comctl32 image-list gutter workaround, kept to preserve column-index math), themed header row (clickable to sort, edge-drag to resize, divider double-click to autosize), per-icon-column cachedwxBitmappairs (normal + selected color) consumed byIconListCtrl::MSWOnNotifyso row icons are pixel-perfect centered under the themed-header icons, rebuild guard so DESELECTED/SELECTED storms collapse into a single bubbledEVT_CARD_SELECTED, case-insensitive substring filter viasetFilter(...), per-column toggle-direction sort. Multi-select is enabled (nowxLC_SINGLE_SEL): Ctrl toggles rows, Shift selects a range;selected()/selectedCount()/selectedCards()expose the selection; rebuild/sort/filter preserve all selected ids; Ctrl+C copies every selected row as TSV (one line per card). Subclasses fill in column descriptors + per-row text + per-icon-column flag predicates + dispatch hooks (sortBy,matchesFilter).include/ccm/ui/IconListCtrl.hpp+src/IconListCtrl.cpp— smallwxListCtrlsubclass that interceptsNM_CUSTOMDRAWon Windows and paints flag-icon sub-items at the exact center of each cell. It owns aHIMAGELIST(built from the cachedwxBitmappairs via straight-RGBA 32 bpp DIB sections) and draws each cell's icon withImageList_Draw(ILD_TRANSPARENT)onto the nativeHDCfromNMLVCUSTOMDRAW. This is the same low-level pixel pathwxImageListuses internally, which is the only rendering path that has reliably preserved SVG transparency + correct fill color across light/dark themes on MSW. Two earlier attempts —wxGraphicsContext::DrawBitmapand a manually-premultiplied-DIBAlphaBlend— both rendered runtime-fill SVG icons as solid white in light mode and were abandoned (see convention 11). The custom-draw is purely about positioning; pixel format handling is delegated to comctl32.include/ccm/ui/BaseSelectedCardPanel.hpp— header-only templateBaseSelectedCardPanel<TCard>that owns the right-hand-side detail panel: preview image fetched viaCardPreviewService(with theshared_ptr<State>+std::atomic alive/currentGencancellation pattern), 2-column detail grid, flag-icon strip that collapses when no flags are set, image list with double-click viewer. If preview lookup fails or returns empty bytes, the panel loads a per-game card-back fallback: Magic and Pokémon West use fixed HTTPS URLs (fallbackImageUrlForGame, CCM2-aligned); Pokémon Asia uses the Japanese TCG back viapreviewGameFor(card)→Game::JapanesePokemon; Yu-Gi-Oh! tries Yugipedia thumbnail URL, then fullBack-EN.pngonms.yugipedia.com, then reads<exeDir>/assets/ygo_card_back.png; Digimon Digi-Battle reads<exeDir>/assets/digibattle99_card_back.png(both bundled assets copied byapp/CMakeLists.txton link). The constructor caches<exeDir>/for that disk path. Subclasses describe the detail rows / flag icons / preview lookup(name, setId, setNo)and own aGameconstant; overridepreviewGameForwhen preview routing differs from collectiongameId()(Pokemon West/Asia).include/ccm/ui/BaseCardEditDialog.hpp— header-only templateBaseCardEditDialog<TCard>that owns the standard Add/Edit form: Name, optionalappendPreSetRows(Pokemon West/Asia region), Set picker (read-onlywxComboBoxwith typeahead — prefix first, then substring, ASCII-fold soPokemon/JunglematchPokémon Jungle— and case-insensitive id matching for legacy data), Amount spin, Language and Condition choices (languagesForChoice()hook; Pokemon filters by region), Note, image management (Add multiple viawxFD_MULTIPLE, Remove, double-click to view), OK/Cancel + validation. The Set row is built on a hostwxPanelwith a horizontalwxBoxSizer; games may overridecustomizeSetPickerRow(row, combo)to wrap the combo (default: combo only). After a programmatic selection,applySetSelectionByIndexupdatescard_.setand callsonSetSelectionApplied()(default no-op). AfterbuildAndPopulate(), the template snapshots the loaded card intoopeningSnapshot_; inEditMode::Edit, OK asks Yes/No (“Save changes to this card?”) only when the card differs from that snapshot (dirty-only confirm). Create mode never prompts. Subclasses build the flags row (buildFlagsRow), append game-specific extra rows (e.g. Pokemon'sSet #) viaappendExtraRows, and copy values in/out of the typed card (readExtraFromCard/writeExtraToCard). The template bindsEVT_TEXTon Name and invokesonCardLookupContextChanged()so games can drop stale keyed metadata when the user edits the lookup identity (Yu-Gi-Oh! clears its YGOPRODeck print-variant cache here).YuGiOhCardEditDialogoverridescustomizeSetPickerRowto add aSwitchCtrlpill switch plus a hint label (Set name/Set code), a text field, and Auto detect (resolvesSet.idviaccm/util/YuGiOhSetLookup.hppagainstavailableSets(), then returns to the dropdown on success); it overridesonSetSelectionAppliedto match manual set-change behavior. It additionallyCallAfters a silentdetectPrintVariantswhen opening Edit (and after changing Set) so multi-print Next buttons can appear without pressing Auto detect first, as long as name + display set are populated. The base also exposes helpers to sync current control values and inspect the currently-selected set when a subclass needs derived-field UI.include/ccm/ui/SwitchCtrl.hpp+src/SwitchCtrl.cpp— custom pill-track + thumb switch for small modal rows (Yu-Gi-Oh! set picker); firesEVT_CCM_SWITCHon user toggle and reads colors frominferThemeFromWindow/paletteForTheme.include/ccm/ui/Magic*.hpp+src/Magic*.cpp— Magic implementations:MagicCardListPanel,MagicSelectedCardPanel,MagicCardEditDialog,MagicGameView. Each is ~50–100 lines of hook overrides on top of the matching base template.include/ccm/ui/Pokemon*.hpp+src/Pokemon*.cpp— Pokemon implementations:PokemonCardListPanel,PokemonSelectedCardPanel,PokemonCardEditDialog,PokemonGameView,PokemonSetCompletionPanel. Same Add/Edit shape as Magic for the card form; the game view hosts Single Cards | Set Completion viacontentPanel/hostsOwnLayout(like Digimon/Yu-Gi-Oh!). Catalog fromPokemonSetCatalogService(set-catalog-west.json/set-catalog-asia.json), filled on Update Pokemon. The Add/Edit/Delete + filter toolbar lives inside the Single Cards tab; MainFrame hides its shared toolbar while Pokemon is active.include/ccm/ui/DigiBattle99*.hpp+src/DigiBattle99*.cpp— Digimon Digi-Battle: list/selected/edit plusDigiBattle99GameViewviacontentPanelwith a palette-painted tab strip +wxSimplebook(Single Cards | Set Completion) — not nativewxNotebook, which stays light on MSW dark mode — andDigiBattle99SetCompletionPanel(pack progress tiles + greyed checklist). Catalog fromDigiBattle99SetCatalogService(set-catalog.json), filled on Update Sets. The Add/Edit/Delete + filter toolbar lives inside the Single Cards page; MainFrame hides its shared toolbar while Digimon is active (hostsOwnLayout).include/ccm/ui/YuGiOh*.hpp+src/YuGiOh*.cpp— Yu-Gi-Oh!: list/selected/edit plusYuGiOhGameViewnotebook (Single Cards | Set Completion) via the samehostsOwnLayout/contentPanelpattern as Digimon, andYuGiOhSetCompletionPanel. Catalog fromYuGiOhSetCatalogService(yugioh/set-catalog.json), filled on Update Sets from YGOPRODeckcardinfo.php.include/ccm/ui/YuGiOhBandai*.hpp+src/YuGiOhBandai*.cpp— Yu-Gi-Oh! (Bandai): same notebook layout as Digimon/YGO; dual auto-detect (name or Bandai number) via Yugipedia SMW ask; catalog fromYuGiOhBandaiSetCatalogService(yugiohbandai/set-catalog.json).include/ccm/ui/SvgIcons.hpp+src/SvgIcons.cpp— embedded SVG templates with a@FILL@placeholder. Magic flags:kSvgFoil/kSvgSigned/kSvgAltered. Pokemon flags:kSvgHolo(sparkle, mirroring the originalIconHolofromPokemonTable.tsx) andkSvgFirstEdition(themed "1" inside an outlined badge, rebuilt from the originalIconPokemonFirstEdition.tsx— every fill/stroke uses@FILL@so the icon themes alongside the others). Toolbar glyphs:kSvgToolbarAdd/kSvgToolbarEdit/kSvgToolbarDelete(vscode-codicons).svgIconBitmap/paddedSvgIconhelpers backed bywxBitmapBundle::FromSVG. Bitmaps fromsvgIconBitmapgo straight towxStaticBitmap/wxBitmapButton::SetBitmapcleanly; for the row-icon pathIconListCtrlpacks them into a private premultiplied-BGRAHIMAGELISTand draws withImageList_Draw. See convention 11 for the full pitfall write-up.src/BaseEvents.cpp— single-translation-unit definitions forEVT_CARD_SELECTEDandEVT_PREVIEW_STATUS. Both events are template-instantiation-agnostic so all per-game panels share the same event types.include/ccm/ui/SettingsDialog.hpp+src/SettingsDialog.cpp— editsConfigurationviaConfigService::store.include/ccm/ui/ImageViewerDialog.hpp+src/ImageViewerDialog.cpp— full-size viewer with prev/next.include/ccm/ui/Theme.hpp+src/Theme.cpp— shared theme helpers and popup helpers (showThemedMessageDialog,showThemedConfirmDialog,setToolbarEditVisible,deleteCardsConfirmMessage) for consistent dark/light dialogs and multi-select toolbar/delete UX.applyThemeToWindowTreepaintswxButton,wxBitmapButton, andwxToggleButtonin dark mode (customwxEVT_PAINT+ hover/focus) so native Win32 theming cannot flash a light hover plate; light mode leaves buttons native where possible.SwitchCtrlis palette-driven and self-painted (not nativewxToggleButton).
Conventions
- Only consume core through
AppContext. Do not include any header fromccm/infra/here. The set of allowedccm/...includes isdomain/,services/,games/IGameModule.hpp,ports/ICardPreviewSource.hpp, andutil/headers that remain UI-agnostic (for exampleutil/Result.hpp,util/YuGiOhPrintingSlot.hpp,util/YuGiOhSetLookup.hpp). Do not pull arbitraryutil/orgames/implementation headers beyond what a panel/dialog already needs for display or small shared helpers. - Image decoding lives here, not in core. Use
wxImage::LoadFile(path.string())against the path returned byIImageStore::resolvePath. Core stays free of any image library. - Ownership: dialogs and panels are heap-allocated and parented to a
wxWindow. wxWidgets owns the lifetime — do not wrap them inunique_ptr.IGameViewinstances themselves are owned byapp/main.cpp(std::unique_ptr<>); the panels owned by the views become children of theMainFramesplitter on first mount. - Custom events:
EVT_CARD_SELECTEDis fired by the list panel on itself (not its parent). EachIGameViewbinds it on its typed list panel inside the panel's first construction so the typed selection flows directly into the typed selected panel —MainFramenever sees aMagicCardor aPokemonCard. Do not move that binding back intoMainFrame. - wxFont modifications mutate in place:
font.MakeBold().MakeLarger()— do not callScale(it does not exist on wxFont 3.2; useMakeLarger/SetPointSize). - Single-active-game UX.
MainFrameonly ever shows one game's panels at a time; the content host swaps either the sharedlistPanel()/selectedPanel()splitter or a game’scontentPanel()when the user picks a differentGamemenu entry. Do not stand up parallel side-by-side tabs for different games. Digimon’s, Yu-Gi-Oh!’s, and Pokémon’s Single Cards / Set Completion switch is an in-game mode switch (themed tab strip +wxSimplebook), not multi-game tabs. - No
ccm_warnings. This target intentionally does not link the strict warning interface — wxWidgets headers trip-Wpedantic/-Wshadow. Keep it that way; do not add the link. - Async background work must not capture
thisraw. Use the pattern fromBaseSelectedCardPanel: astd::shared_ptr<State>holdingstd::atomic<bool> alive,std::atomic<unsigned> currentGen, and a back-pointer to the panel; spawn a detachedstd::thread, then deliver the result withwxTheApp->CallAfter([state, gen, ...]() { if (!state->alive) return; if (state->currentGen != gen) return; ... }). Flipalive=falsein the panel destructor so late callbacks become no-ops. - Icons come from
SvgIcons.hpp. Don't inline new SVG strings in panel sources; add them toSvgIcons.{hpp,cpp}so all panels stay in sync. Always pass a runtime fill color (wxSystemSettings::GetColour(...).GetAsString(wxC2S_HTML_SYNTAX)); never bake one into the SVG. - Sort key != display key. When you add a new column to a list panel, follow the existing pattern: the
wxListCtrlcell text is one thing; the sort comparator lives inccm::services::CardSorterand may key off a different field (the canonical case isset.nameshown butset.releaseDatesorted, so collections list chronologically). New columns must extendMagicSortColumn/PokemonSortColumnand add a correspondingcaseinsortMagicCards/sortPokemonCards. wxListCtrl+ flag-icon centering (MSW comctl32):- Native
LVS_REPORTsub-item image rendering on MSW left-anchors the bitmap with a small built-in inset, regardless ofwxLIST_FORMAT_CENTER. It can never align pixel-perfect with our wx-sizer-centered themed header icons, especially after column resize. Don't try to compensate by padding the image-list bitmap or nudging it horizontally — that path was tried and abandoned. - Authoritative path: flag-icon sub-items go through
IconListCtrl::MSWOnNotify(NM_CUSTOMDRAW). It computes the live sub-item rect viaLVM_GETSUBITEMRECT(LVIR_BOUNDS)and composites the cell's icon at the rect center withAlphaBlend(... AC_SRC_OVER | AC_SRC_ALPHA)straight ontocd->nmcd.hdc. Each (icon, selection-state) pair has its own pre-built premultiplied 32 bpp BGRA DIB section indibBitmaps_; indexiholds the normal variant and indexi + iconColCountholds the selected variant. The cache rebuilds whenever the theme changes (viasetIconBitmaps(...)fromBaseCardListPanel::rebuildIconBitmaps). - We deliberately do not route through
ImageList_Draw/HIMAGELISThere. On the verified MinGW-w64 + comctl32 v6 stack,ImageList_Drawon anILC_COLOR32list withILD_TRANSPARENTignored the alpha channel of the bitmap and the "transparent" canvas around each glyph painted as opaque black behind the icon — every row flag rendered as a black rectangle with a white glyph regardless of theme.AlphaBlenddirectly on the listctrl's HDC works in every case we've tested. - Bitmap format pitfall —
AlphaBlendrequires PREMULTIPLIED BGRA, not straight alpha. With straight RGBA the function returnsFALSE(or, depending on the driver, paints garbage).makePremultipliedDibinIconListCtrl.cppdoes the per-pixel premultiply with the rounded form(c * a + 127) / 255. Do not simplify that toc * a / 255(loss of precision onc=0xFF, a=0xFF) and do not skip the divide-by-255 entirely (c * aoverflows the byte and renders the icon as solid white — that was the original failure mode that made an earlier dev abandon premultiplication for a while). Hardcoded-fill SVGs (e.g. baked-in black/white badges) happen to look correct on every path and are not a useful sanity check on their own — always verify rendering against a runtime-fill icon (foil / signed / altered / holo) on both light and dark themes. - The hidden zero-width spacer column at index 0 stays. It's no longer load-bearing for any image-list gutter, but it keeps every other column index stable across the codebase. Start real columns at index 1.
- Insert each row through the spacer column with a
wxListItemwhose mask includeswxLIST_MASK_IMAGEand image-1so MSW doesn't try to render an item icon for column 0 if a public image list ever gets attached again. AlphaBlendlives inmsimg32.lib;ui_wx/CMakeLists.txtlinksmsimg32onWIN32. Don't rely ongdi32being enough —AlphaBlend@44is not ingdi32.
- Native
- Startup/dialog responsiveness rules:
- Keep first paint fast: avoid heavy synchronous work in window/dialog constructors.
- In
MainFrame, defer initial collection load withCallAfter(...)so the frame paints before I/O/parsing. - Keep startup's "first row selected" behavior, but schedule initial selection with
CallAfter(...)inBaseCardListPanelto avoid blocking first render. - Avoid reloading/reparsing sets on each Add/Edit open: each
IGameViewcaches its own set list and passes it into the dialog by pointer. - Pass preloaded sets into
BaseCardEditDialogby pointer/reference (not by value) to avoid vector copies per open. - For heavy dialog setup, wrap constructor-time UI population in
Freeze()/Thaw()and append choice items in bulk viawxArrayString(BaseCardEditDialog::buildAndPopulatedoes this).
- String encoding on Windows (avoid mojibake):
- Domain/service strings are UTF-8
std::string. Do not rely on implicitstd::string <-> wxStringconversions on Windows; those can route through the active ANSI codepage and renderPokémonasPokémon. - UI display path (
std::string-> wx control): always convert withwxString::FromUTF8(str.c_str())beforeSetLabelText,SetItem,Append, control constructors, etc. - UI write-back path (wx control ->
std::string): always convert withToStdString(wxConvUTF8)so persisted/domain text stays UTF-8. - Apply this rule consistently in shared templates (
BaseCardListPanel,BaseSelectedCardPanel,BaseCardEditDialog) because a single implicit conversion in those bases affects every game view.
- Domain/service strings are UTF-8
- Theme consistency rules (Windows):
- Treat dialog roots as
panelBg, not a separate shade, otherwise label rows can look like mismatched darker boxes. - Theme dialogs before
ShowModal()withapplyThemeToWindowTree(...)(and root background/foreground colors as needed); this includes Settings, image viewer, About, and custom popup dialogs. Per-game Add/Edit flows usethemeModalDialog(wxDialog*, Theme)fromTheme.hppsoMagicGameView/PokemonGameView/YuGiOhGameViewshare one path instead of duplicating palette wiring. - Do not use native
wxMessageBox/wxAboutBoxfor app-facing flows that must match dark mode. Use themed popup helpers (or a custom themedwxDialog) so body/buttons stay in sync with the app palette. - Center popup dialogs on the app window (
CentreOnParent()) so confirmations/info boxes open relative to the current app window. - Include
wxSpinCtrlin themed input controls (Amount field) or it will keep a mismatched native background. - Do not call
applyNativeClassTheme(..., "DarkMode_Explorer", "Explorer")forwxTextCtrl; on some Windows builds this causes black typed text in dark mode. Keep text inputs palette-driven (SetThemeEnabled(false)in dark/high-contrast as needed). - Text inputs are hardened in
Theme.cppviaapplyPaletteToTextCtrl/hardenTextCtrlNativeTheme: opt the EDIT HWND out of immersive dark mode, clear its visual style, and subclass the parent to answerWM_CTLCOLOREDIT(that message goes to the parent, not the frame — an earlier frame-level handler never ran for the toolbar filter). - Keep toolbar button behavior stable under dark/high-contrast: avoid changes that break click/tooltip affordances while experimenting with hover contrast fixes.
- For dark/high-contrast button readability, do not trust native hover/pressed rendering on Windows; custom state painting in
Theme.cppis allowed when native visuals ignore configured colors. - Button event handlers must use per-button state that is refreshed when theme changes. Avoid one-time captures of theme colors/mode in lambdas; these can leak dark-mode behavior into light mode.
- In High Contrast, use stronger hover/pressed deltas than regular dark mode and keep the button border in the foreground/text color for visibility (currently yellow in this palette).
- When validating UI theming changes, rebuild and run
ccm(the executable), not justccm_ui_wx.
- Treat dialog roots as
- Preview fallback behavior (CCM2 parity where applicable):
- Keep unresolved external previews user-visible by showing a per-game card-back image in
BaseSelectedCardPanelinstead of a blank/transparent bitmap. - Magic / Pokémon use single fixed HTTPS URLs (
Magic_card_back.jpg, BulbagardenCardback.jpg). Japanese Pokémon uses the Japanese TCG Bulbagarden back (TCG_Card_Back_Japanese.jpg). Yu-Gi-Oh! uses Yugipedia-hosted backs plus a bundled PNG beside the exe (assets/ygo_card_back.png) when the network path fails. Digimon Digi-Battle uses a bundled PNG (assets/digibattle99_card_back.png) — keep those chains working when touching preview code. - If you change fallback sourcing (URLs or bundled asset), keep the "always show a reasonable card-back fallback" behavior intact for every game with remote previews.
- Keep unresolved external previews user-visible by showing a per-game card-back image in
- Per-game auto-detect controls:
- Auto-detect actions in edit dialogs (e.g. detect set print number / rarity from API) are opt-in per game.
- Keep shared templates game-agnostic: put buttons and detection behavior in
<Name>CardEditDialog, not inBaseCardEditDialog. Yu-Gi-Oh!'s Set code entry (SwitchCtrl+ text + Auto detect against cached sets) is wired through the template hookcustomizeSetPickerRowso Magic/Pokemon keep the default single-combo row unchanged. - For games that use composed print IDs (prefix + numeric suffix), allow user editing on the numeric portion and render the full code as a read-only derived label beside the input.
- Bidirectional identify (Yu-Gi-Oh!, Bandai, Pokémon West/Asia, Digi-Battle): Set is always required. Set # Auto detect fills set number from Name, or fills Name from Set #. When both fields are filled, the field the user last typed is the lookup key (
CardLookupEditField/preferDetectBySetNoinccm/util/CardLookupDetect.hpp, tracked byBaseCardEditDialog::markNameLookupEdited/markSetNoLookupEdited). ProgrammaticChangeValuefrom a detect result does not flip the key.
Required follow-ups
- If you replace
ui_wx/assets/ygo_card_back.pngorui_wx/assets/digibattle99_card_back.png, rebuild theccmtarget soapp/CMakeLists.txt'sPOST_BUILDcopy refreshes<exeDir>/assets/; do not remove an asset without updatingBaseSelectedCardPanel/docs/assets-and-info-apis.md. - After adding a new dialog/panel
.cppyou must add it toui_wx/CMakeLists.txt. - After adding a new menu action you must allocate an
Ids::*value inMainFrame.hpp(don't reusewxID_HIGHESTmath inline) andBindit inbuildMenuBar. The dynamic Game / Sets menus consume theIdGameMenuBase/IdSetsMenuBaseranges; do not stomp on those id ranges. - After changing
AppContextyou must updateapp/main.cppso the composition root populates the new field. - After adding a new icon to
SvgIcons.{hpp,cpp}you must keep the@FILL@placeholder so both light- and dark-variant rendering keeps working, and add a small unit-test-equivalent visual check by running the binary (no automated UI tests in this repo). - After changing one of the
Base*template hooks (or adding a new one) you must keepdocs/adding-a-new-game.mdin sync — the per-game derived classes are the readers of that contract and the doc is what onboarding agents read first.
Adding a new game UI
- Implement three derived classes under
include/ccm/ui/mirroring the Magic / Pokemon trio:<Name>CardListPanel : public BaseCardListPanel<<Name>Card, <Name>SortColumn>— overridedeclareTextColumns(),declareIconColumns(),renderTextCell(),isIconColumnSet(),sortBy(),matchesFilter().<Name>SelectedCardPanel : public BaseSelectedCardPanel<<Name>Card>— overridedeclareDetailRows(),declareFlagIcons(),detailValueFor(),isFlagSet(),previewKey(),gameId(). Define a localenumofDetailKeyconstants for clarity.<Name>CardEditDialog : public BaseCardEditDialog<<Name>Card>— overridebuildFlagsRow(), optionallyappendExtraRows(),readExtraFromCard(),writeExtraToCard(),updateMenuName(), and optionallyvalidateExtraFields()(Bandai requires set number).
- Add a
<Name>GameView : public IGameViewthat owns those panels and the typedCollectionService<<Name>Card>&. BindEVT_CARD_SELECTEDon the list panel insidelistPanel(parent)to push the typed selection into the selected panel. TheMagicGameView/PokemonGameViewpair is the canonical reference. - Re-add the new view to
AppContext::gameViewsin the composition root (app/main.cpp). TheGameandSetsmenus pick it up automatically. - Add SVG glyphs for any new flag columns to
SvgIcons.{hpp,cpp}(with the@FILL@placeholder). - Register all new
.cppfiles inui_wx/CMakeLists.txt.
Commands
Build UI only: cmake --build build --target ccm_ui_wx