Minor: New Game Yu-Gi-Oh! Bandai (#22)

This commit is contained in:
Sebastian Dine
2026-07-30 14:51:53 +02:00
committed by GitHub
parent 7cf25d671f
commit 2eb7c59f78
65 changed files with 4142 additions and 75 deletions
+1
View File
@@ -27,6 +27,7 @@ struct AppContext {
IGameModule& pokemonModule;
IGameModule& yuGiOhModule;
IGameModule& digiBattle99Module;
IGameModule& yuGiOhBandaiModule;
// Asia Pokemon sets/preview backend (not a separate Game menu entry).
IGameModule& japanesePokemonModule;
// Active per-game UI bundles. The order is the order shown in the
@@ -136,6 +136,10 @@ protected:
// controls cannot outlive the lookup identity.
virtual void onCardLookupContextChanged() {}
// Extra validation after name/set checks and writeFromControls(). Return
// false to block OK (subclass should show its own themed dialog).
[[nodiscard]] virtual bool validateExtraFields() { return true; }
// Common helpers ----------------------------------------------------------
void appendRow(wxFlexGridSizer* grid, const wxString& label, wxWindow* ctrl) {
@@ -148,6 +152,8 @@ protected:
[[nodiscard]] const TCard& constCard() const noexcept { return card_; }
void syncCardFromControls() { writeFromControls(); }
[[nodiscard]] wxComboBox* setComboControl() const noexcept { return setCombo_; }
[[nodiscard]] wxTextCtrl* nameControl() const noexcept { return nameCtrl_; }
[[nodiscard]] wxChoice* languageChoiceControl() const noexcept { return languageChoice_; }
[[nodiscard]] const Set* selectedSetFromControls() const {
const auto& available = availableSets();
@@ -471,6 +477,7 @@ private:
"Add card", wxOK | wxICON_INFORMATION);
return;
}
if (!validateExtraFields()) return;
if (mode_ == EditMode::Edit && !(card_ == openingSnapshot_)) {
if (showThemedConfirmDialog(
this,
@@ -307,6 +307,8 @@ private:
case Game::DigiBattle99:
// No stable public Digi-Battle back URL; UI uses bundled PNG.
return {};
case Game::YuGiOhBandai:
return "https://ms.yugipedia.com//3/34/Back-BAN-JP-1999.png";
}
return {};
}
-4
View File
@@ -51,10 +51,6 @@ private:
[[nodiscard]] IGameView* activeView();
#ifdef __WXMSW__
WXLRESULT MSWWindowProc(WXUINT message, WXWPARAM wParam, WXLPARAM lParam) override;
#endif
AppContext& ctx_;
Game activeGame_{Game::Magic};
+3
View File
@@ -7,6 +7,7 @@
class wxDialog;
class wxWindow;
class wxString;
class wxTextCtrl;
namespace ccm::ui {
@@ -23,6 +24,8 @@ struct ThemePalette {
ThemePalette paletteForTheme(Theme theme);
Theme inferThemeFromWindow(const wxWindow* window);
void applyThemeToWindowTree(wxWindow* root, const ThemePalette& palette, Theme theme);
// Force palette colors onto a text input (incl. MSW dark-mode typed-text fix).
void applyPaletteToTextCtrl(wxTextCtrl* text, const ThemePalette& palette, Theme theme);
void themeModalDialog(wxDialog* dlg, Theme theme);
int showThemedMessageDialog(wxWindow* parent, const wxString& message, const wxString& caption, long style);
int showThemedConfirmDialog(wxWindow* parent, const wxString& message, const wxString& caption);
@@ -0,0 +1,86 @@
#pragma once
#include "ccm/domain/YuGiOhBandaiCard.hpp"
#include "ccm/ports/ICardPreviewSource.hpp"
#include "ccm/services/CardPreviewService.hpp"
#include "ccm/ui/BaseCardEditDialog.hpp"
#include <wx/button.h>
#include <wx/checkbox.h>
#include <wx/choice.h>
#include <atomic>
#include <memory>
#include <span>
#include <string>
#include <vector>
namespace ccm::ui {
class YuGiOhBandaiCardEditDialog final : public BaseCardEditDialog<YuGiOhBandaiCard> {
public:
YuGiOhBandaiCardEditDialog(wxWindow* parent,
ImageService& imageService,
SetService& setService,
CardPreviewService& cardPreview,
EditMode mode,
YuGiOhBandaiCard initial,
const std::vector<Set>* preloadedSets = nullptr);
~YuGiOhBandaiCardEditDialog() override;
protected:
void buildFlagsRow(wxBoxSizer* flagsBox) override;
void appendExtraRows(wxFlexGridSizer* grid) override;
void readExtraFromCard() override;
void writeExtraToCard() override;
[[nodiscard]] std::span<const Language> languagesForChoice() const override;
[[nodiscard]] std::string updateMenuName() const override {
return "Update Yu-Gi-Oh! (Bandai)";
}
void onCardLookupContextChanged() override;
[[nodiscard]] bool validateExtraFields() override;
private:
struct VariantFetchState {
std::atomic<bool> alive{true};
};
void onAutoDetectBySetNo(wxCommandEvent&);
void onNextSetNo(wxCommandEvent&);
void onAutoDetectByName(wxCommandEvent&);
void onRarityChoiceChanged(wxCommandEvent&);
void onSetSelectionChanged(wxCommandEvent&);
void scheduleDeferredVariantPrefetch();
void prefetchVariantsForCurrentCardSilent(unsigned capturedEpoch);
void requestByNameAsync(unsigned capturedEpoch, std::string name, std::string setId,
bool showFailureDialog);
void requestByNoAsync(unsigned capturedEpoch, std::string setNo, bool showFailureDialog);
void applyDetectedList(unsigned capturedEpoch,
Result<std::vector<AutoDetectedPrint>> detected,
bool showFailureDialog, bool applyFirst);
void clearCachedPrintVariants();
void applyDetectedPrint(const AutoDetectedPrint& print);
void applyRarityStringToChoice(const std::string& rarity);
void maybeAutoCheckHoloForRarity(const std::string& rarity);
void refreshVariantNextControls();
[[nodiscard]] std::size_t findAvailableSetIndex(const std::string& setId) const;
EditMode dialogMode_;
unsigned variantFetchEpoch_{0};
CardPreviewService& cardPreview_;
std::shared_ptr<VariantFetchState> variantFetchState_;
wxTextCtrl* setNoCtrl_{nullptr};
wxButton* autoSetNoBtn_{nullptr};
wxButton* nextSetNoBtn_{nullptr};
wxChoice* rarityChoice_{nullptr};
wxButton* autoRarityBtn_{nullptr};
wxCheckBox* holoCheck_{nullptr};
wxCheckBox* signedCheck_{nullptr};
wxCheckBox* alteredCheck_{nullptr};
std::vector<AutoDetectedPrint> cachedVariants_;
std::size_t variantRingPos_{0};
};
} // namespace ccm::ui
@@ -0,0 +1,26 @@
#pragma once
#include "ccm/domain/YuGiOhBandaiCard.hpp"
#include "ccm/services/CardSorter.hpp"
#include "ccm/ui/BaseCardListPanel.hpp"
namespace ccm::ui {
class YuGiOhBandaiCardListPanel final
: public BaseCardListPanel<YuGiOhBandaiCard, YuGiOhBandaiSortColumn> {
public:
explicit YuGiOhBandaiCardListPanel(wxWindow* parent);
protected:
[[nodiscard]] std::vector<TextColumnSpec> declareTextColumns() const override;
[[nodiscard]] std::vector<IconColumnSpec> declareIconColumns() const override;
[[nodiscard]] std::string renderTextCell(const YuGiOhBandaiCard& card,
std::size_t idx) const override;
[[nodiscard]] bool isIconColumnSet(const YuGiOhBandaiCard& card,
std::size_t idx) const override;
void sortBy(YuGiOhBandaiSortColumn column, bool ascending) override;
[[nodiscard]] bool matchesFilter(const YuGiOhBandaiCard& card,
std::string_view filter) const override;
};
} // namespace ccm::ui
@@ -0,0 +1,109 @@
#pragma once
#include "ccm/domain/YuGiOhBandaiCard.hpp"
#include "ccm/games/IGameModule.hpp"
#include "ccm/services/CardPreviewService.hpp"
#include "ccm/services/CollectionService.hpp"
#include "ccm/services/ConfigService.hpp"
#include "ccm/services/ImageService.hpp"
#include "ccm/services/SetService.hpp"
#include "ccm/services/YuGiOhBandaiSetCatalogService.hpp"
#include "ccm/ui/IGameView.hpp"
#include <array>
#include <cstddef>
#include <string>
#include <string_view>
#include <vector>
class wxBitmapButton;
class wxBoxSizer;
class wxPanel;
class wxSimplebook;
class wxSplitterWindow;
class wxStaticText;
class wxTextCtrl;
namespace ccm::ui {
class YuGiOhBandaiCardListPanel;
class YuGiOhBandaiSelectedCardPanel;
class YuGiOhBandaiSetCompletionPanel;
class YuGiOhBandaiGameView final : public IGameView {
public:
YuGiOhBandaiGameView(ConfigService& config,
CollectionService<YuGiOhBandaiCard>& collection,
SetService& sets,
ImageService& images,
CardPreviewService& cardPreview,
IGameModule& module,
YuGiOhBandaiSetCatalogService& catalogStore);
[[nodiscard]] Game gameId() const noexcept override { return Game::YuGiOhBandai; }
[[nodiscard]] std::string displayName() const override { return "Yu-Gi-Oh! (Bandai)"; }
wxPanel* listPanel(wxWindow* parent) override;
wxPanel* selectedPanel(wxWindow* parent) override;
wxPanel* contentPanel(wxWindow* parent) override;
[[nodiscard]] wxPanel* contentPanelIfCreated() const noexcept override {
return contentPanel_;
}
[[nodiscard]] bool hostsOwnLayout() const noexcept override { return true; }
void refreshCollection(std::optional<std::uint32_t> selectId = std::nullopt) override;
void onAddCard(wxWindow* parentWindow) override;
void onEditCard(wxWindow* parentWindow) override;
void onDeleteCard(wxWindow* parentWindow) override;
std::string onUpdateSets(wxWindow* parentWindow) override;
void setFilter(std::string_view filter) override;
void nudgeSelection(int delta) override;
void applyTheme(const ThemePalette& palette) override;
[[nodiscard]] std::string updateSetsMenuLabel() const override {
return "Update Yu-Gi-Oh! (Bandai)";
}
private:
void ensureSetsLoaded();
// Fetches sets + checklist catalog from Yugipedia and persists both.
// Returns false on failure (error dialogs already shown).
[[nodiscard]] bool downloadSetsAndCatalog(wxWindow* parentWindow,
std::size_t* setCountOut = nullptr,
std::size_t* packCountOut = nullptr);
// Fetches sets + checklist catalog when set-catalog.json is missing.
// Returns true if the catalog exists afterward. Shows error dialogs on failure.
[[nodiscard]] bool ensureCatalogLoaded(wxWindow* parentWindow);
void refreshSetCompletionFromStore();
const std::vector<Set>& setsForDialog();
void ensureSingleCardsMounted(wxWindow* splitterParent);
void buildSingleCardsToolbar(wxWindow* parent, wxBoxSizer* pageSizer);
void buildTabBar(wxWindow* parent, wxBoxSizer* rootSizer);
void selectTab(int index);
void refreshToolbarIcons(const ThemePalette& palette);
void refreshTabBarTheme(const ThemePalette& palette);
ConfigService& config_;
CollectionService<YuGiOhBandaiCard>& collection_;
SetService& sets_;
ImageService& images_;
CardPreviewService& cardPreview_;
IGameModule& module_;
YuGiOhBandaiSetCatalogService& catalogStore_;
wxPanel* contentPanel_{nullptr};
wxPanel* tabBar_{nullptr};
wxSimplebook* book_{nullptr};
wxSplitterWindow* singleSplitter_{nullptr};
YuGiOhBandaiCardListPanel* listPanel_{nullptr};
YuGiOhBandaiSelectedCardPanel* selectedPanel_{nullptr};
YuGiOhBandaiSetCompletionPanel* setCompletionPanel_{nullptr};
std::array<wxPanel*, 2> tabPanels_{{nullptr, nullptr}};
std::array<wxStaticText*, 2> tabLabels_{{nullptr, nullptr}};
int activeTab_{0};
std::array<wxBitmapButton*, 3> toolbarButtons_{{nullptr, nullptr, nullptr}};
wxTextCtrl* filterInput_{nullptr};
std::vector<Set> setsCache_;
bool attemptedInitialSetLoad_{false};
};
} // namespace ccm::ui
@@ -0,0 +1,25 @@
#pragma once
#include "ccm/domain/YuGiOhBandaiCard.hpp"
#include "ccm/ui/BaseSelectedCardPanel.hpp"
namespace ccm::ui {
class YuGiOhBandaiSelectedCardPanel final : public BaseSelectedCardPanel<YuGiOhBandaiCard> {
public:
YuGiOhBandaiSelectedCardPanel(wxWindow* parent,
ImageService& imageService,
CardPreviewService& cardPreview);
protected:
[[nodiscard]] std::vector<DetailRowSpec> declareDetailRows() const override;
[[nodiscard]] std::vector<FlagIconSpec> declareFlagIcons() const override;
[[nodiscard]] std::string detailValueFor(const YuGiOhBandaiCard& card,
DetailKey key) const override;
[[nodiscard]] bool isFlagSet(const YuGiOhBandaiCard& card, DetailKey key) const override;
[[nodiscard]] std::tuple<std::string, std::string, std::string>
previewKey(const YuGiOhBandaiCard& card) const override;
[[nodiscard]] Game gameId() const noexcept override { return Game::YuGiOhBandai; }
};
} // namespace ccm::ui
@@ -0,0 +1,71 @@
#pragma once
// YuGiOhBandaiSetCompletionPanel: Set Completion tab — pack tiles with
// progress bars for sets the user owns >=1 card of, plus an in-tab checklist
// drill-down (unowned rows greyed). Catalog is offline (set-catalog.json).
// Optional language filter restricts ownership to one language and labels
// set titles as "{setName} ({language})".
#include "ccm/domain/Enums.hpp"
#include "ccm/domain/YuGiOhBandaiCard.hpp"
#include "ccm/domain/YuGiOhBandaiSetCatalog.hpp"
#include "ccm/services/YuGiOhBandaiSetCatalogService.hpp"
#include "ccm/ui/Theme.hpp"
#include <wx/panel.h>
#include <optional>
#include <string>
#include <vector>
class wxBoxSizer;
class wxChoice;
class wxListCtrl;
class wxScrolledWindow;
class wxSimplebook;
class wxStaticText;
namespace ccm::ui {
class YuGiOhBandaiSetCompletionPanel : public wxPanel {
public:
YuGiOhBandaiSetCompletionPanel(wxWindow* parent, YuGiOhBandaiSetCatalogService& catalogStore);
void setCollection(std::vector<YuGiOhBandaiCard> cards);
void reloadFromStore();
void applyTheme(const ThemePalette& palette);
private:
void showGridPage();
void showChecklistPage(const std::string& setId, const std::string& setName);
void rebuildGrid();
void rebuildChecklist(const std::string& setId);
void setEmptyMessage(const wxString& message);
void clearGridTiles();
void refreshLanguageChoice();
void onLanguageChoice(wxCommandEvent& event);
void rebuildCurrentView();
[[nodiscard]] std::string displaySetName(const std::string& setName) const;
YuGiOhBandaiSetCatalogService& catalogStore_;
YuGiOhBandaiSetCatalog catalog_;
bool catalogLoaded_{false};
std::vector<YuGiOhBandaiCard> collection_;
ThemePalette palette_{};
std::optional<Language> languageFilter_;
wxChoice* languageChoice_{nullptr};
wxSimplebook* book_{nullptr};
wxPanel* gridPage_{nullptr};
wxScrolledWindow* scroll_{nullptr};
wxBoxSizer* gridSizer_{nullptr};
wxStaticText* emptyLabel_{nullptr};
wxPanel* detailPage_{nullptr};
wxStaticText* detailTitle_{nullptr};
wxListCtrl* checklist_{nullptr};
std::string detailSetId_;
std::string detailSetName_;
};
} // namespace ccm::ui