mirror of
https://github.com/sebastiandine/Card-Collection-Manager-3.git
synced 2026-09-04 09:13:27 +00:00
patch: Feature/ygo set selection (#16)
This commit is contained in:
+5
-4
@@ -10,18 +10,19 @@
|
||||
- `include/ccm/ui/BaseCardListPanel.hpp` — header-only template `BaseCardListPanel<TCard, TSortColumn>` that owns ALL the non-game-specific `wxListCtrl` machinery: 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 cached `wxBitmap` pairs (normal + selected color) consumed by `IconListCtrl::MSWOnNotify` so row icons are pixel-perfect centered under the themed-header icons, rebuild guard so DESELECTED/SELECTED storms collapse into a single bubbled `EVT_CARD_SELECTED`, case-insensitive substring filter via `setFilter(...)`, per-column toggle-direction sort. 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` — small `wxListCtrl` subclass that intercepts `NM_CUSTOMDRAW` on Windows and paints flag-icon sub-items at the exact center of each cell. It owns a `HIMAGELIST` (built from the cached `wxBitmap` pairs via straight-RGBA 32 bpp DIB sections) and draws each cell's icon with `ImageList_Draw(ILD_TRANSPARENT)` onto the native `HDC` from `NMLVCUSTOMDRAW`. This is the same low-level pixel path `wxImageList` uses 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::DrawBitmap` and a manually-premultiplied-DIB `AlphaBlend` — 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 template `BaseSelectedCardPanel<TCard>` that owns the right-hand-side detail panel: preview image fetched via `CardPreviewService` (with the `shared_ptr<State>` + `std::atomic alive`/`currentGen` cancellation 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 use fixed HTTPS URLs (`fallbackImageUrlForGame`, CCM2-aligned); **Yu-Gi-Oh!** tries Yugipedia thumbnail URL, then full `Back-EN.png` on `ms.yugipedia.com`, then reads `<exeDir>/assets/ygo_card_back.png` (copied next to the executable by `app/CMakeLists.txt` on link — source file `ui_wx/assets/ygo_card_back.png`). The constructor caches `<exeDir>/` for that disk path. Subclasses describe the detail rows / flag icons / preview lookup `(name, setId, setNo)` and own a `Game` constant.
|
||||
- `include/ccm/ui/BaseCardEditDialog.hpp` — header-only template `BaseCardEditDialog<TCard>` that owns the standard Add/Edit form: Name, Set picker (read-only `wxComboBox` with prefix-match typeahead and case-insensitive id matching for legacy data), Amount spin, Language and Condition choices, Note, image management (Add multiple via `wxFD_MULTIPLE`, Remove, double-click to view), OK/Cancel + validation. After `buildAndPopulate()`, the template snapshots the loaded card into `openingSnapshot_`; in **`EditMode::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's `Set #`) via `appendExtraRows`, and copy values in/out of the typed card (`readExtraFromCard` / `writeExtraToCard`). The template binds `EVT_TEXT` on **Name** and invokes `onCardLookupContextChanged()` so games can drop stale keyed metadata when the user edits the lookup identity (Yu-Gi-Oh! clears its YGOPRODeck print-variant cache here). `YuGiOhCardEditDialog` additionally `CallAfter`s a silent `detectPrintVariants` when 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/BaseCardEditDialog.hpp` — header-only template `BaseCardEditDialog<TCard>` that owns the standard Add/Edit form: Name, Set picker (read-only `wxComboBox` with prefix-match typeahead and case-insensitive id matching for legacy data), Amount spin, Language and Condition choices, Note, image management (Add multiple via `wxFD_MULTIPLE`, Remove, double-click to view), OK/Cancel + validation. The **Set** row is built on a host `wxPanel` with a horizontal `wxBoxSizer`; games may override `customizeSetPickerRow(row, combo)` to wrap the combo (default: combo only). After a programmatic selection, `applySetSelectionByIndex` updates `card_.set` and calls `onSetSelectionApplied()` (default no-op). After `buildAndPopulate()`, the template snapshots the loaded card into `openingSnapshot_`; in **`EditMode::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's `Set #`) via `appendExtraRows`, and copy values in/out of the typed card (`readExtraFromCard` / `writeExtraToCard`). The template binds `EVT_TEXT` on **Name** and invokes `onCardLookupContextChanged()` so games can drop stale keyed metadata when the user edits the lookup identity (Yu-Gi-Oh! clears its YGOPRODeck print-variant cache here). `YuGiOhCardEditDialog` overrides `customizeSetPickerRow` to add a **`SwitchCtrl`** pill switch plus a **hint** label (`Set name` / `Set code`), a text field, and **Auto detect** (resolves `Set.id` via `ccm/util/YuGiOhSetLookup.hpp` against `availableSets()`, then returns to the dropdown on success); it overrides `onSetSelectionApplied` to match manual set-change behavior. It additionally `CallAfter`s a silent `detectPrintVariants` when 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); fires `EVT_CCM_SWITCH` on user toggle and reads colors from `inferThemeFromWindow` / `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`. Same shape as the Magic ones; differences are limited to the Set # field, the Holo / 1. Edition flags, and the Pokemon TCG preview lookup key (which includes `setNo`).
|
||||
- `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 original `IconHolo` from `PokemonTable.tsx`) and `kSvgFirstEdition` (themed "1" inside an outlined badge, rebuilt from the original `IconPokemonFirstEdition.tsx` — every fill/stroke uses `@FILL@` so the icon themes alongside the others). Toolbar glyphs: `kSvgToolbarAdd` / `kSvgToolbarEdit` / `kSvgToolbarDelete` (vscode-codicons). `svgIconBitmap` / `paddedSvgIcon` helpers backed by `wxBitmapBundle::FromSVG`. Bitmaps from `svgIconBitmap` go straight to `wxStaticBitmap` / `wxBitmapButton::SetBitmap` cleanly; for the row-icon path `IconListCtrl` packs them into a private premultiplied-BGRA `HIMAGELIST` and draws with `ImageList_Draw`. See convention 11 for the full pitfall write-up.
|
||||
- `src/BaseEvents.cpp` — single-translation-unit definitions for `EVT_CARD_SELECTED` and `EVT_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` — edits `Configuration` via `ConfigService::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`) for consistent dark/light dialogs.
|
||||
- `include/ccm/ui/Theme.hpp` + `src/Theme.cpp` — shared theme helpers and popup helpers (`showThemedMessageDialog`, `showThemedConfirmDialog`) for consistent dark/light dialogs. `applyThemeToWindowTree` paints `wxButton`, `wxBitmapButton`, and **`wxToggleButton`** in dark mode (custom `wxEVT_PAINT` + hover/focus) so native Win32 theming cannot flash a light hover plate; light mode leaves buttons native where possible. `SwitchCtrl` is palette-driven and self-painted (not native `wxToggleButton`).
|
||||
|
||||
## Conventions
|
||||
|
||||
1. **Only consume core through `AppContext`.** Do not include any header from `ccm/infra/` here. The set of allowed `ccm/...` includes is `domain/`, `services/`, `games/IGameModule.hpp`, `ports/ICardPreviewSource.hpp`, and `util/Result.hpp`.
|
||||
1. **Only consume core through `AppContext`.** Do not include any header from `ccm/infra/` here. The set of allowed `ccm/...` includes is `domain/`, `services/`, `games/IGameModule.hpp`, `ports/ICardPreviewSource.hpp`, and `util/` headers that remain UI-agnostic (for example `util/Result.hpp`, `util/YuGiOhPrintingSlot.hpp`, `util/YuGiOhSetLookup.hpp`). Do not pull arbitrary `util/` or `games/` implementation headers beyond what a panel/dialog already needs for display or small shared helpers.
|
||||
2. **Image decoding lives here, not in core.** Use `wxImage::LoadFile(path.string())` against the path returned by `IImageStore::resolvePath`. Core stays free of any image library.
|
||||
3. **Ownership**: dialogs and panels are heap-allocated and parented to a `wxWindow`. wxWidgets owns the lifetime — do **not** wrap them in `unique_ptr`. `IGameView` instances themselves are owned by `app/main.cpp` (`std::unique_ptr<>`); the panels owned by the views become children of the `MainFrame` splitter on first mount.
|
||||
4. **Custom events**: `EVT_CARD_SELECTED` is fired by the list panel on itself (not its parent). Each `IGameView` binds it on its typed list panel inside the panel's first construction so the typed selection flows directly into the typed selected panel — `MainFrame` never sees a `MagicCard` or a `PokemonCard`. Do not move that binding back into `MainFrame`.
|
||||
@@ -70,7 +71,7 @@
|
||||
- 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.
|
||||
16. **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 in `BaseCardEditDialog`.
|
||||
- Keep shared templates game-agnostic: put buttons and detection behavior in `<Name>CardEditDialog`, not in `BaseCardEditDialog`. Yu-Gi-Oh!'s **Set code** entry (`SwitchCtrl` + text + **Auto detect** against cached sets) is wired through the template hook `customizeSetPickerRow` so 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.
|
||||
|
||||
## Required follow-ups
|
||||
|
||||
@@ -21,6 +21,7 @@ add_library(ccm_ui_wx STATIC
|
||||
src/YuGiOhGameView.cpp
|
||||
|
||||
src/SettingsDialog.cpp
|
||||
src/SwitchCtrl.cpp
|
||||
src/ImageViewerDialog.cpp
|
||||
src/IconListCtrl.cpp
|
||||
src/SvgIcons.cpp
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include <wx/filedlg.h>
|
||||
#include <wx/listbox.h>
|
||||
#include <wx/msgdlg.h>
|
||||
#include <wx/panel.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/spinctrl.h>
|
||||
#include <wx/stattext.h>
|
||||
@@ -146,6 +147,28 @@ protected:
|
||||
return &available[static_cast<std::size_t>(sel)];
|
||||
}
|
||||
|
||||
[[nodiscard]] const std::vector<Set>& availableSets() const noexcept {
|
||||
return preloadedSets_ != nullptr ? *preloadedSets_ : sets_;
|
||||
}
|
||||
|
||||
// Default: combo only. Yu-Gi-Oh! overrides to add set-code entry + toggle.
|
||||
virtual void customizeSetPickerRow(wxBoxSizer& row, wxComboBox* combo) {
|
||||
row.Add(combo, 1, wxEXPAND);
|
||||
}
|
||||
|
||||
// After programmatically changing the set combo + `card_.set` (see
|
||||
// `applySetSelectionByIndex`). Default no-op; Yu-Gi-Oh! clears print-variant cache.
|
||||
virtual void onSetSelectionApplied() {}
|
||||
|
||||
void applySetSelectionByIndex(std::size_t index) {
|
||||
const auto& available = availableSets();
|
||||
if (!setCombo_ || !setCombo_->IsEnabled()) return;
|
||||
if (index >= available.size()) return;
|
||||
setCombo_->SetSelection(static_cast<int>(index));
|
||||
card_.set = available[index];
|
||||
onSetSelectionApplied();
|
||||
}
|
||||
|
||||
private:
|
||||
void readSets() {
|
||||
auto loaded = setService_.getSets(game_);
|
||||
@@ -158,10 +181,6 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] const std::vector<Set>& availableSets() const noexcept {
|
||||
return preloadedSets_ != nullptr ? *preloadedSets_ : sets_;
|
||||
}
|
||||
|
||||
void buildLayout() {
|
||||
auto* root = new wxBoxSizer(wxVERTICAL);
|
||||
auto* grid = new wxFlexGridSizer(2, 6, 8);
|
||||
@@ -174,9 +193,16 @@ private:
|
||||
});
|
||||
appendRow(grid, "Name", nameCtrl_);
|
||||
|
||||
setCombo_ = new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0,
|
||||
// `setCombo_` must be parented to `setHost` so every control in the Set row
|
||||
// shares the same `wxPanel`; otherwise the combo stays a direct child of the
|
||||
// dialog while the sizer lives on `setHost`, which corrupts layout on MSW.
|
||||
auto* setHost = new wxPanel(this, wxID_ANY);
|
||||
auto* setRow = new wxBoxSizer(wxHORIZONTAL);
|
||||
setHost->SetSizer(setRow);
|
||||
setCombo_ = new wxComboBox(setHost, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0,
|
||||
nullptr, wxCB_READONLY);
|
||||
appendRow(grid, "Set", setCombo_);
|
||||
customizeSetPickerRow(*setRow, setCombo_);
|
||||
appendRow(grid, "Set", setHost);
|
||||
|
||||
// Subclass extra rows go between Set and Amount (Pokemon adds Set #).
|
||||
appendExtraRows(grid);
|
||||
@@ -237,8 +263,13 @@ private:
|
||||
|
||||
CallAfter([this]() {
|
||||
if (nameCtrl_) {
|
||||
nameCtrl_->SetInsertionPoint(0);
|
||||
nameCtrl_->ShowPosition(0);
|
||||
nameCtrl_->SetFocus();
|
||||
if (mode_ == EditMode::Edit && !nameCtrl_->IsEmpty()) {
|
||||
nameCtrl_->SetInsertionPointEnd();
|
||||
} else {
|
||||
nameCtrl_->SetInsertionPoint(0);
|
||||
nameCtrl_->ShowPosition(0);
|
||||
}
|
||||
}
|
||||
if (noteCtrl_) {
|
||||
noteCtrl_->SetInsertionPoint(0);
|
||||
|
||||
@@ -70,6 +70,10 @@ namespace ccm::ui {
|
||||
// not duplicated per template instantiation.
|
||||
wxDECLARE_EVENT(EVT_CARD_SELECTED, wxCommandEvent);
|
||||
|
||||
// Raised on `wxEVT_LIST_ITEM_ACTIVATED` (double-click / Enter on a row).
|
||||
// `IGameView` implementations bind this to open Edit for `selected()`.
|
||||
wxDECLARE_EVENT(EVT_CARD_ACTIVATED, wxCommandEvent);
|
||||
|
||||
template <typename TCard, typename TSortColumn>
|
||||
class BaseCardListPanel : public wxPanel {
|
||||
public:
|
||||
@@ -238,6 +242,7 @@ protected:
|
||||
|
||||
list_->Bind(wxEVT_LIST_ITEM_SELECTED, &BaseCardListPanel::onSelectionChanged, this);
|
||||
list_->Bind(wxEVT_LIST_ITEM_DESELECTED, &BaseCardListPanel::onSelectionChanged, this);
|
||||
list_->Bind(wxEVT_LIST_ITEM_ACTIVATED, &BaseCardListPanel::onListItemActivated, this);
|
||||
}
|
||||
|
||||
// Forwarded helpers ------------------------------------------------------
|
||||
@@ -642,6 +647,14 @@ private:
|
||||
notifySelectionChanged();
|
||||
}
|
||||
|
||||
void onListItemActivated(wxListEvent& event) {
|
||||
(void)event;
|
||||
if (inRebuild_) return;
|
||||
wxCommandEvent ev(EVT_CARD_ACTIVATED, GetId());
|
||||
ev.SetEventObject(this);
|
||||
ProcessWindowEvent(ev);
|
||||
}
|
||||
|
||||
// ----- members ----------------------------------------------------------
|
||||
|
||||
static constexpr int kFlagIconSize = 14;
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
// Tracks when a modal Add/Edit card dialog is on screen so a second one
|
||||
// cannot be stacked (toolbar + list activation, or rare re-entrant cases).
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace ccm::ui {
|
||||
|
||||
// User-visible hint when Add/Edit is requested while a card dialog is already modal.
|
||||
inline constexpr const char* kCardEditModalBlockedUtf8 =
|
||||
"Close the open card dialog (save or cancel) before opening another card.";
|
||||
|
||||
[[nodiscard]] inline std::atomic<int>& cardEditModalDepthRef() noexcept {
|
||||
static std::atomic<int> depth{0};
|
||||
return depth;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool cardEditModalIsActive() noexcept {
|
||||
return cardEditModalDepthRef().load(std::memory_order_relaxed) > 0;
|
||||
}
|
||||
|
||||
struct CardEditModalGuard {
|
||||
CardEditModalGuard() {
|
||||
cardEditModalDepthRef().fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
~CardEditModalGuard() {
|
||||
cardEditModalDepthRef().fetch_sub(1, std::memory_order_relaxed);
|
||||
}
|
||||
CardEditModalGuard(const CardEditModalGuard&) = delete;
|
||||
CardEditModalGuard& operator=(const CardEditModalGuard&) = delete;
|
||||
};
|
||||
|
||||
} // namespace ccm::ui
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <wx/event.h>
|
||||
#include <wx/window.h>
|
||||
|
||||
namespace ccm::ui {
|
||||
|
||||
wxDECLARE_EVENT(EVT_CCM_SWITCH, wxCommandEvent);
|
||||
|
||||
// Small on/off switch (pill track + thumb) for modal dialogs. Fires `EVT_CCM_SWITCH`
|
||||
// when the user toggles; bind with the control pointer as the event source.
|
||||
class SwitchCtrl final : public wxWindow {
|
||||
public:
|
||||
explicit SwitchCtrl(wxWindow* parent, wxWindowID id = wxID_ANY, bool initialOn = false);
|
||||
|
||||
[[nodiscard]] bool GetValue() const noexcept { return on_; }
|
||||
void SetValue(bool on, bool notify = false);
|
||||
|
||||
bool Enable(bool enable = true) override;
|
||||
|
||||
private:
|
||||
void onPaint(wxPaintEvent&);
|
||||
void onLeftDown(wxMouseEvent&);
|
||||
void onEnter(wxMouseEvent&);
|
||||
void onLeave(wxMouseEvent&);
|
||||
|
||||
bool on_{false};
|
||||
bool hovered_{false};
|
||||
};
|
||||
|
||||
} // namespace ccm::ui
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "ccm/ports/ICardPreviewSource.hpp"
|
||||
#include "ccm/services/CardPreviewService.hpp"
|
||||
#include "ccm/ui/BaseCardEditDialog.hpp"
|
||||
#include "ccm/ui/SwitchCtrl.hpp"
|
||||
#include <wx/button.h>
|
||||
#include <wx/stattext.h>
|
||||
|
||||
@@ -21,11 +22,13 @@ public:
|
||||
|
||||
protected:
|
||||
void buildFlagsRow(wxBoxSizer* flagsBox) override;
|
||||
void customizeSetPickerRow(wxBoxSizer& row, wxComboBox* combo) override;
|
||||
void appendExtraRows(wxFlexGridSizer* grid) override;
|
||||
void readExtraFromCard() override;
|
||||
void writeExtraToCard() override;
|
||||
[[nodiscard]] std::string updateMenuName() const override { return "Update Yu-Gi-Oh!"; }
|
||||
void onCardLookupContextChanged() override;
|
||||
void onSetSelectionApplied() override;
|
||||
|
||||
private:
|
||||
void onAutoDetectSetNo(wxCommandEvent&);
|
||||
@@ -34,6 +37,10 @@ private:
|
||||
void onNextRarity(wxCommandEvent&);
|
||||
void onSetNoTextChanged(wxCommandEvent&);
|
||||
void onSetSelectionChanged(wxCommandEvent&);
|
||||
void handleSetSelectionChanged();
|
||||
void onSetRowSwitch(wxCommandEvent&);
|
||||
void onSetCodeAutoDetect(wxCommandEvent&);
|
||||
void syncSetModeHint();
|
||||
void autoDetectFromApi(bool fillSetNo, bool fillRarity);
|
||||
void refreshSetNoFullPreview();
|
||||
void clearCachedPrintVariants();
|
||||
@@ -63,6 +70,12 @@ private:
|
||||
wxCheckBox* signedCheck_{nullptr};
|
||||
wxCheckBox* alteredCheck_{nullptr};
|
||||
|
||||
wxPanel* setCodeRowPanel_{nullptr};
|
||||
wxTextCtrl* setCodeText_{nullptr};
|
||||
wxButton* setCodeAutoBtn_{nullptr};
|
||||
wxStaticText* setModeHint_{nullptr};
|
||||
SwitchCtrl* setPickerSwitch_{nullptr};
|
||||
|
||||
std::vector<AutoDetectedPrint> cachedVariants_;
|
||||
std::vector<std::string> uniqueSetCodes_;
|
||||
std::vector<std::string> raritiesForCurrentSetCode_;
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// declared in the corresponding base headers (BaseCardListPanel.hpp,
|
||||
// BaseSelectedCardPanel.hpp) and defined exactly once here, so that template
|
||||
// instantiations (Magic, Pokemon, ...) all use the same event type tag.
|
||||
// EVT_CARD_ACTIVATED is declared alongside EVT_CARD_SELECTED in
|
||||
// BaseCardListPanel.hpp.
|
||||
|
||||
#include "ccm/ui/BaseCardListPanel.hpp"
|
||||
#include "ccm/ui/BaseSelectedCardPanel.hpp"
|
||||
@@ -9,6 +11,7 @@
|
||||
namespace ccm::ui {
|
||||
|
||||
wxDEFINE_EVENT(EVT_CARD_SELECTED, wxCommandEvent);
|
||||
wxDEFINE_EVENT(EVT_CARD_ACTIVATED, wxCommandEvent);
|
||||
wxDEFINE_EVENT(EVT_PREVIEW_STATUS, wxCommandEvent);
|
||||
|
||||
} // namespace ccm::ui
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#include "ccm/ui/MagicGameView.hpp"
|
||||
|
||||
#include "ccm/ui/CardEditModalGuard.hpp"
|
||||
#include "ccm/ui/MagicCardEditDialog.hpp"
|
||||
#include "ccm/ui/MagicCardListPanel.hpp"
|
||||
#include "ccm/ui/MagicSelectedCardPanel.hpp"
|
||||
#include "ccm/ui/Theme.hpp"
|
||||
|
||||
#include <wx/msgdlg.h>
|
||||
#include <wx/window.h>
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
@@ -54,6 +56,10 @@ wxPanel* MagicGameView::listPanel(wxWindow* parent) {
|
||||
selectedPanel_->setCard(listPanel_->selected());
|
||||
}
|
||||
});
|
||||
listPanel_->Bind(EVT_CARD_ACTIVATED, [this](wxCommandEvent&) {
|
||||
wxWindow* owner = wxGetTopLevelParent(listPanel_);
|
||||
onEditCard(owner != nullptr ? owner : static_cast<wxWindow*>(listPanel_));
|
||||
});
|
||||
}
|
||||
return listPanel_;
|
||||
}
|
||||
@@ -88,6 +94,11 @@ const std::vector<Set>& MagicGameView::setsForDialog() {
|
||||
}
|
||||
|
||||
void MagicGameView::onAddCard(wxWindow* parentWindow) {
|
||||
if (cardEditModalIsActive()) {
|
||||
showThemedMessageDialog(parentWindow, wxString::FromUTF8(kCardEditModalBlockedUtf8),
|
||||
wxString::FromUTF8("Add card"), wxOK | wxICON_INFORMATION);
|
||||
return;
|
||||
}
|
||||
MagicCard fresh;
|
||||
fresh.amount = 1;
|
||||
fresh.language = Language::English;
|
||||
@@ -96,6 +107,7 @@ void MagicGameView::onAddCard(wxWindow* parentWindow) {
|
||||
MagicCardEditDialog dlg(parentWindow, images_, sets_, EditMode::Create, fresh,
|
||||
&setsForDialog());
|
||||
themeModalDialog(&dlg, config_.current().theme);
|
||||
CardEditModalGuard modalGuard;
|
||||
if (dlg.ShowModal() != wxID_OK) return;
|
||||
|
||||
auto added = collection_.add(Game::Magic, dlg.card());
|
||||
@@ -132,9 +144,15 @@ void MagicGameView::onEditCard(wxWindow* parentWindow) {
|
||||
showThemedMessageDialog(parentWindow, "Select a card first.", "Edit", wxOK | wxICON_INFORMATION);
|
||||
return;
|
||||
}
|
||||
if (cardEditModalIsActive()) {
|
||||
showThemedMessageDialog(parentWindow, wxString::FromUTF8(kCardEditModalBlockedUtf8),
|
||||
wxString::FromUTF8("Edit"), wxOK | wxICON_INFORMATION);
|
||||
return;
|
||||
}
|
||||
MagicCardEditDialog dlg(parentWindow, images_, sets_, EditMode::Edit, *sel,
|
||||
&setsForDialog());
|
||||
themeModalDialog(&dlg, config_.current().theme);
|
||||
CardEditModalGuard modalGuard;
|
||||
if (dlg.ShowModal() != wxID_OK) return;
|
||||
auto updated = collection_.update(Game::Magic, dlg.card());
|
||||
if (!updated) {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#include "ccm/ui/PokemonGameView.hpp"
|
||||
|
||||
#include "ccm/ui/CardEditModalGuard.hpp"
|
||||
#include "ccm/ui/PokemonCardEditDialog.hpp"
|
||||
#include "ccm/ui/PokemonCardListPanel.hpp"
|
||||
#include "ccm/ui/PokemonSelectedCardPanel.hpp"
|
||||
#include "ccm/ui/Theme.hpp"
|
||||
|
||||
#include <wx/msgdlg.h>
|
||||
#include <wx/window.h>
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
@@ -51,6 +53,10 @@ wxPanel* PokemonGameView::listPanel(wxWindow* parent) {
|
||||
selectedPanel_->setCard(listPanel_->selected());
|
||||
}
|
||||
});
|
||||
listPanel_->Bind(EVT_CARD_ACTIVATED, [this](wxCommandEvent&) {
|
||||
wxWindow* owner = wxGetTopLevelParent(listPanel_);
|
||||
onEditCard(owner != nullptr ? owner : static_cast<wxWindow*>(listPanel_));
|
||||
});
|
||||
}
|
||||
return listPanel_;
|
||||
}
|
||||
@@ -85,6 +91,11 @@ const std::vector<Set>& PokemonGameView::setsForDialog() {
|
||||
}
|
||||
|
||||
void PokemonGameView::onAddCard(wxWindow* parentWindow) {
|
||||
if (cardEditModalIsActive()) {
|
||||
showThemedMessageDialog(parentWindow, wxString::FromUTF8(kCardEditModalBlockedUtf8),
|
||||
wxString::FromUTF8("Add card"), wxOK | wxICON_INFORMATION);
|
||||
return;
|
||||
}
|
||||
PokemonCard fresh;
|
||||
fresh.amount = 1;
|
||||
fresh.language = Language::English;
|
||||
@@ -93,6 +104,7 @@ void PokemonGameView::onAddCard(wxWindow* parentWindow) {
|
||||
PokemonCardEditDialog dlg(parentWindow, images_, sets_, cardPreview_, EditMode::Create, fresh,
|
||||
&setsForDialog());
|
||||
themeModalDialog(&dlg, config_.current().theme);
|
||||
CardEditModalGuard modalGuard;
|
||||
if (dlg.ShowModal() != wxID_OK) return;
|
||||
|
||||
auto added = collection_.add(Game::Pokemon, dlg.card());
|
||||
@@ -129,9 +141,15 @@ void PokemonGameView::onEditCard(wxWindow* parentWindow) {
|
||||
showThemedMessageDialog(parentWindow, "Select a card first.", "Edit", wxOK | wxICON_INFORMATION);
|
||||
return;
|
||||
}
|
||||
if (cardEditModalIsActive()) {
|
||||
showThemedMessageDialog(parentWindow, wxString::FromUTF8(kCardEditModalBlockedUtf8),
|
||||
wxString::FromUTF8("Edit"), wxOK | wxICON_INFORMATION);
|
||||
return;
|
||||
}
|
||||
PokemonCardEditDialog dlg(parentWindow, images_, sets_, cardPreview_, EditMode::Edit, *sel,
|
||||
&setsForDialog());
|
||||
themeModalDialog(&dlg, config_.current().theme);
|
||||
CardEditModalGuard modalGuard;
|
||||
if (dlg.ShowModal() != wxID_OK) return;
|
||||
auto updated = collection_.update(Game::Pokemon, dlg.card());
|
||||
if (!updated) {
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
#include "ccm/ui/SwitchCtrl.hpp"
|
||||
#include "ccm/domain/Enums.hpp"
|
||||
#include "ccm/ui/Theme.hpp"
|
||||
|
||||
#include <wx/dcbuffer.h>
|
||||
#include <wx/dcclient.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace ccm::ui {
|
||||
|
||||
wxDEFINE_EVENT(EVT_CCM_SWITCH, wxCommandEvent);
|
||||
|
||||
namespace {
|
||||
|
||||
wxColour liftRgb(const wxColour& c, int delta) {
|
||||
auto lift = [delta](unsigned char ch) -> unsigned char {
|
||||
const int v = static_cast<int>(ch) + delta;
|
||||
return static_cast<unsigned char>(v > 255 ? 255 : (v < 0 ? 0 : v));
|
||||
};
|
||||
return wxColour(lift(c.Red()), lift(c.Green()), lift(c.Blue()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SwitchCtrl::SwitchCtrl(wxWindow* parent, wxWindowID id, bool initialOn)
|
||||
: wxWindow(parent, id, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE, wxString()),
|
||||
on_(initialOn) {
|
||||
SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
SetCursor(wxCURSOR_HAND);
|
||||
const wxSize sz = FromDIP(wxSize(40, 20));
|
||||
SetMinSize(sz);
|
||||
SetMaxSize(sz);
|
||||
SetInitialSize(sz);
|
||||
|
||||
Bind(wxEVT_PAINT, &SwitchCtrl::onPaint, this);
|
||||
Bind(wxEVT_LEFT_DOWN, &SwitchCtrl::onLeftDown, this);
|
||||
Bind(wxEVT_ENTER_WINDOW, &SwitchCtrl::onEnter, this);
|
||||
Bind(wxEVT_LEAVE_WINDOW, &SwitchCtrl::onLeave, this);
|
||||
Bind(wxEVT_ERASE_BACKGROUND, [](wxEraseEvent&) {});
|
||||
}
|
||||
|
||||
void SwitchCtrl::SetValue(bool on, bool notify) {
|
||||
if (on_ == on) return;
|
||||
on_ = on;
|
||||
Refresh();
|
||||
if (notify) {
|
||||
wxCommandEvent e(EVT_CCM_SWITCH, GetId());
|
||||
e.SetEventObject(this);
|
||||
e.SetInt(on_ ? 1 : 0);
|
||||
ProcessEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
bool SwitchCtrl::Enable(bool enable) {
|
||||
const bool ok = wxWindow::Enable(enable);
|
||||
SetCursor(enable ? wxCURSOR_HAND : wxCURSOR_ARROW);
|
||||
Refresh();
|
||||
return ok;
|
||||
}
|
||||
|
||||
void SwitchCtrl::onEnter(wxMouseEvent& ev) {
|
||||
hovered_ = true;
|
||||
Refresh();
|
||||
ev.Skip();
|
||||
}
|
||||
|
||||
void SwitchCtrl::onLeave(wxMouseEvent& ev) {
|
||||
hovered_ = false;
|
||||
Refresh();
|
||||
ev.Skip();
|
||||
}
|
||||
|
||||
void SwitchCtrl::onLeftDown(wxMouseEvent& ev) {
|
||||
if (!IsEnabled()) {
|
||||
ev.Skip();
|
||||
return;
|
||||
}
|
||||
on_ = !on_;
|
||||
Refresh();
|
||||
wxCommandEvent e(EVT_CCM_SWITCH, GetId());
|
||||
e.SetEventObject(this);
|
||||
e.SetInt(on_ ? 1 : 0);
|
||||
ProcessEvent(e);
|
||||
ev.Skip(false);
|
||||
}
|
||||
|
||||
void SwitchCtrl::onPaint(wxPaintEvent&) {
|
||||
wxAutoBufferedPaintDC dc(this);
|
||||
const wxRect rect = GetClientRect();
|
||||
if (rect.width <= 0 || rect.height <= 0) return;
|
||||
|
||||
const Theme theme = inferThemeFromWindow(this);
|
||||
const ThemePalette p = paletteForTheme(theme);
|
||||
const bool dark = theme == Theme::Dark;
|
||||
|
||||
wxColour trackOff = p.inputBg;
|
||||
wxColour trackOn = p.buttonBg;
|
||||
wxColour thumb = dark ? wxColour(240, 240, 240) : wxColour(252, 252, 252);
|
||||
wxColour border = dark ? wxColour(72, 72, 72) : wxColour(158, 158, 158);
|
||||
|
||||
wxColour track = on_ ? trackOn : trackOff;
|
||||
if (hovered_ && IsEnabled()) {
|
||||
track = liftRgb(track, dark ? 14 : 10);
|
||||
}
|
||||
if (!IsEnabled()) {
|
||||
track = liftRgb(track, dark ? -22 : -25);
|
||||
thumb = liftRgb(thumb, dark ? -55 : -35);
|
||||
border = liftRgb(border, dark ? -15 : 10);
|
||||
}
|
||||
|
||||
// Fill the full client rect first so rounded-track corners do not show
|
||||
// undrawn pixels (often black) against the parent panel.
|
||||
dc.SetPen(*wxTRANSPARENT_PEN);
|
||||
dc.SetBrush(wxBrush(p.panelBg));
|
||||
dc.DrawRectangle(rect);
|
||||
|
||||
dc.SetPen(wxPen(border));
|
||||
dc.SetBrush(wxBrush(track));
|
||||
const int radius = rect.height / 2;
|
||||
dc.DrawRoundedRectangle(rect, radius);
|
||||
|
||||
const int pad = FromDIP(2);
|
||||
const int thumbD = std::max(4, rect.height - 2 * pad);
|
||||
const int travel = std::max(0, rect.width - 2 * pad - thumbD);
|
||||
const int thumbX = pad + (on_ ? travel : 0);
|
||||
const int thumbY = rect.y + (rect.height - thumbD) / 2;
|
||||
|
||||
wxColour thumbBorder = liftRgb(border, dark ? 18 : -12);
|
||||
dc.SetPen(wxPen(thumbBorder));
|
||||
dc.SetBrush(wxBrush(thumb));
|
||||
dc.DrawEllipse(thumbX, thumbY, thumbD, thumbD);
|
||||
}
|
||||
|
||||
} // namespace ccm::ui
|
||||
+33
-5
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <wx/button.h>
|
||||
#include <wx/bmpbuttn.h>
|
||||
#include <wx/tglbtn.h>
|
||||
#include <wx/choice.h>
|
||||
#include <wx/dcbuffer.h>
|
||||
#include <wx/frame.h>
|
||||
@@ -444,8 +445,11 @@ void applyThemeToWindowTree(wxWindow* root, const ThemePalette& palette, Theme t
|
||||
#endif
|
||||
}
|
||||
|
||||
// `wxToggleButton` is not a `wxButton` on MSW; without this branch it keeps
|
||||
// native visual styles (e.g. light hover flashes) under dark palette dialogs.
|
||||
if (dynamic_cast<wxButton*>(root) != nullptr ||
|
||||
dynamic_cast<wxBitmapButton*>(root) != nullptr) {
|
||||
dynamic_cast<wxBitmapButton*>(root) != nullptr ||
|
||||
dynamic_cast<wxToggleButton*>(root) != nullptr) {
|
||||
const bool darkLike = isDarkLikeTheme(theme);
|
||||
root->SetThemeEnabled(!darkLike);
|
||||
root->SetBackgroundColour(palette.buttonBg);
|
||||
@@ -485,7 +489,11 @@ void applyThemeToWindowTree(wxWindow* root, const ThemePalette& palette, Theme t
|
||||
return;
|
||||
}
|
||||
it->second.hovered = false;
|
||||
const wxColour bg = it->second.focused ? it->second.hoverBg : it->second.normalBg;
|
||||
const bool toggleOn =
|
||||
dynamic_cast<wxToggleButton*>(root) != nullptr &&
|
||||
static_cast<wxToggleButton*>(root)->GetValue();
|
||||
const wxColour bg =
|
||||
(it->second.focused || toggleOn) ? it->second.hoverBg : it->second.normalBg;
|
||||
root->SetBackgroundColour(bg);
|
||||
root->SetForegroundColour(it->second.text);
|
||||
root->Refresh();
|
||||
@@ -513,7 +521,11 @@ void applyThemeToWindowTree(wxWindow* root, const ThemePalette& palette, Theme t
|
||||
const wxPoint localPos = root->ScreenToClient(mousePos);
|
||||
const bool inside = root->GetClientRect().Contains(localPos);
|
||||
it->second.hovered = inside;
|
||||
const wxColour bg = (inside || it->second.focused) ? it->second.hoverBg : it->second.normalBg;
|
||||
const bool toggleOn =
|
||||
dynamic_cast<wxToggleButton*>(root) != nullptr &&
|
||||
static_cast<wxToggleButton*>(root)->GetValue();
|
||||
const wxColour bg =
|
||||
(inside || it->second.focused || toggleOn) ? it->second.hoverBg : it->second.normalBg;
|
||||
root->SetBackgroundColour(bg);
|
||||
root->SetForegroundColour(it->second.text);
|
||||
root->Refresh();
|
||||
@@ -539,12 +551,25 @@ void applyThemeToWindowTree(wxWindow* root, const ThemePalette& palette, Theme t
|
||||
}
|
||||
it->second.focused = false;
|
||||
it->second.pressed = false;
|
||||
const wxColour bg = it->second.hovered ? it->second.hoverBg : it->second.normalBg;
|
||||
const bool toggleOn =
|
||||
dynamic_cast<wxToggleButton*>(root) != nullptr &&
|
||||
static_cast<wxToggleButton*>(root)->GetValue();
|
||||
const wxColour bg =
|
||||
(it->second.hovered || toggleOn) ? it->second.hoverBg : it->second.normalBg;
|
||||
root->SetBackgroundColour(bg);
|
||||
root->SetForegroundColour(it->second.text);
|
||||
root->Refresh();
|
||||
event.Skip();
|
||||
});
|
||||
if (auto* toggle = dynamic_cast<wxToggleButton*>(root)) {
|
||||
toggle->Bind(wxEVT_TOGGLEBUTTON, [root](wxCommandEvent& event) {
|
||||
auto it = gButtonVisualStates.find(root);
|
||||
if (it != gButtonVisualStates.end() && it->second.darkLike) {
|
||||
root->Refresh();
|
||||
}
|
||||
event.Skip();
|
||||
});
|
||||
}
|
||||
root->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
root->Bind(wxEVT_ERASE_BACKGROUND, [](wxEraseEvent&) {});
|
||||
root->Bind(wxEVT_PAINT, [root](wxPaintEvent& event) {
|
||||
@@ -555,10 +580,13 @@ void applyThemeToWindowTree(wxWindow* root, const ThemePalette& palette, Theme t
|
||||
}
|
||||
wxAutoBufferedPaintDC dc(root);
|
||||
const wxRect rect = root->GetClientRect();
|
||||
const bool toggleOn =
|
||||
dynamic_cast<wxToggleButton*>(root) != nullptr &&
|
||||
static_cast<wxToggleButton*>(root)->GetValue();
|
||||
wxColour bg = it->second.normalBg;
|
||||
if (it->second.pressed) {
|
||||
bg = it->second.pressedBg;
|
||||
} else if (it->second.hovered || it->second.focused) {
|
||||
} else if (it->second.hovered || it->second.focused || toggleOn) {
|
||||
bg = it->second.hoverBg;
|
||||
}
|
||||
const wxColour fg = it->second.text;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "ccm/ui/YuGiOhCardEditDialog.hpp"
|
||||
#include "ccm/ui/SwitchCtrl.hpp"
|
||||
#include "ccm/domain/Enums.hpp"
|
||||
#include "ccm/util/YuGiOhPrintingSlot.hpp"
|
||||
#include "ccm/util/YuGiOhSetLookup.hpp"
|
||||
#include <wx/app.h>
|
||||
#include <wx/panel.h>
|
||||
#include <algorithm>
|
||||
@@ -57,6 +59,33 @@ void YuGiOhCardEditDialog::buildFlagsRow(wxBoxSizer* flagsBox) {
|
||||
flagsBox->Add(alteredCheck_, 0, wxRIGHT, 12);
|
||||
}
|
||||
|
||||
void YuGiOhCardEditDialog::customizeSetPickerRow(wxBoxSizer& row, wxComboBox* combo) {
|
||||
wxWindow* const host = combo->GetParent();
|
||||
setCodeRowPanel_ = new wxPanel(host, wxID_ANY);
|
||||
auto* inner = new wxBoxSizer(wxHORIZONTAL);
|
||||
setCodeText_ = new wxTextCtrl(setCodeRowPanel_, wxID_ANY);
|
||||
setCodeAutoBtn_ = new wxButton(setCodeRowPanel_, wxID_ANY, "Auto detect");
|
||||
inner->Add(setCodeText_, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, 6);
|
||||
inner->Add(setCodeAutoBtn_, 0, wxALIGN_CENTER_VERTICAL);
|
||||
setCodeRowPanel_->SetSizer(inner);
|
||||
setCodeRowPanel_->Show(false);
|
||||
|
||||
setModeHint_ = new wxStaticText(host, wxID_ANY, wxString());
|
||||
setPickerSwitch_ = new SwitchCtrl(host, wxID_ANY, false);
|
||||
setPickerSwitch_->Bind(EVT_CCM_SWITCH, &YuGiOhCardEditDialog::onSetRowSwitch, this);
|
||||
setCodeAutoBtn_->Bind(wxEVT_BUTTON, &YuGiOhCardEditDialog::onSetCodeAutoDetect, this);
|
||||
|
||||
row.Add(combo, 1, wxEXPAND);
|
||||
row.Add(setCodeRowPanel_, 1, wxEXPAND);
|
||||
row.Add(setModeHint_, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, 5);
|
||||
row.Add(setPickerSwitch_, 0, wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
if (availableSets().empty()) {
|
||||
setPickerSwitch_->Enable(false);
|
||||
}
|
||||
syncSetModeHint();
|
||||
}
|
||||
|
||||
void YuGiOhCardEditDialog::appendExtraRows(wxFlexGridSizer* grid) {
|
||||
auto* setNoPanel = new wxPanel(this, wxID_ANY);
|
||||
setNoCtrl_ = new wxTextCtrl(setNoPanel, wxID_ANY);
|
||||
@@ -340,10 +369,78 @@ void YuGiOhCardEditDialog::onSetNoTextChanged(wxCommandEvent&) {
|
||||
}
|
||||
|
||||
void YuGiOhCardEditDialog::onSetSelectionChanged(wxCommandEvent& ev) {
|
||||
handleSetSelectionChanged();
|
||||
ev.Skip();
|
||||
}
|
||||
|
||||
void YuGiOhCardEditDialog::onSetSelectionApplied() {
|
||||
handleSetSelectionChanged();
|
||||
}
|
||||
|
||||
void YuGiOhCardEditDialog::handleSetSelectionChanged() {
|
||||
clearCachedPrintVariants();
|
||||
refreshSetNoFullPreview();
|
||||
scheduleDeferredVariantPrefetch();
|
||||
ev.Skip();
|
||||
}
|
||||
|
||||
void YuGiOhCardEditDialog::syncSetModeHint() {
|
||||
if (!setModeHint_ || !setPickerSwitch_) return;
|
||||
// Switch on = set-code entry; hint tells user how to return to the name list.
|
||||
setModeHint_->SetLabel(setPickerSwitch_->GetValue() ? wxString::FromUTF8("Set name")
|
||||
: wxString::FromUTF8("Set code"));
|
||||
}
|
||||
|
||||
void YuGiOhCardEditDialog::onSetRowSwitch(wxCommandEvent&) {
|
||||
if (!setPickerSwitch_ || !setComboControl() || !setCodeRowPanel_) return;
|
||||
syncSetModeHint();
|
||||
const bool codeMode = setPickerSwitch_->GetValue();
|
||||
setComboControl()->Show(!codeMode);
|
||||
setCodeRowPanel_->Show(codeMode);
|
||||
wxWindow* host = setComboControl()->GetParent();
|
||||
if (host) {
|
||||
host->Layout();
|
||||
}
|
||||
Layout();
|
||||
}
|
||||
|
||||
void YuGiOhCardEditDialog::onSetCodeAutoDetect(wxCommandEvent&) {
|
||||
if (!setCodeText_ || !setPickerSwitch_) return;
|
||||
const auto& sets = availableSets();
|
||||
if (sets.empty()) {
|
||||
showThemedMessageDialog(this,
|
||||
"No sets are cached. Use Sets > Update Yu-Gi-Oh! first.",
|
||||
"Set code", wxOK | wxICON_INFORMATION);
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string raw = setCodeText_->GetValue().ToStdString(wxConvUTF8);
|
||||
const auto r = lookupYuGiOhSetByShorthand(raw, sets);
|
||||
using Kind = YuGiOhSetShorthandLookup::Kind;
|
||||
if (r.kind == Kind::NotFound) {
|
||||
showThemedMessageDialog(
|
||||
this,
|
||||
"No set matches that code. Check the code spelling or use Sets > Update Yu-Gi-Oh! to refresh the list.",
|
||||
"Set code", wxOK | wxICON_INFORMATION);
|
||||
return;
|
||||
}
|
||||
if (r.kind == Kind::Ambiguous) {
|
||||
showThemedMessageDialog(this,
|
||||
"Multiple cached sets match that code. Refresh the set list or pick the set from the list.",
|
||||
"Set code", wxOK | wxICON_INFORMATION);
|
||||
return;
|
||||
}
|
||||
|
||||
applySetSelectionByIndex(r.index);
|
||||
|
||||
setPickerSwitch_->SetValue(false, false);
|
||||
syncSetModeHint();
|
||||
setComboControl()->Show(true);
|
||||
setCodeRowPanel_->Show(false);
|
||||
wxWindow* host = setComboControl()->GetParent();
|
||||
if (host) {
|
||||
host->Layout();
|
||||
}
|
||||
Layout();
|
||||
}
|
||||
|
||||
std::string YuGiOhCardEditDialog::extractSetNoNumeric(std::string_view fullSetNo) const {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#include "ccm/ui/YuGiOhGameView.hpp"
|
||||
|
||||
#include "ccm/ui/CardEditModalGuard.hpp"
|
||||
#include "ccm/ui/YuGiOhCardEditDialog.hpp"
|
||||
#include "ccm/ui/YuGiOhCardListPanel.hpp"
|
||||
#include "ccm/ui/YuGiOhSelectedCardPanel.hpp"
|
||||
#include "ccm/ui/Theme.hpp"
|
||||
|
||||
#include <wx/msgdlg.h>
|
||||
#include <wx/window.h>
|
||||
|
||||
#include <optional>
|
||||
#include <algorithm>
|
||||
@@ -56,6 +58,10 @@ wxPanel* YuGiOhGameView::listPanel(wxWindow* parent) {
|
||||
selectedPanel_->setCard(listPanel_->selected());
|
||||
}
|
||||
});
|
||||
listPanel_->Bind(EVT_CARD_ACTIVATED, [this](wxCommandEvent&) {
|
||||
wxWindow* owner = wxGetTopLevelParent(listPanel_);
|
||||
onEditCard(owner != nullptr ? owner : static_cast<wxWindow*>(listPanel_));
|
||||
});
|
||||
}
|
||||
return listPanel_;
|
||||
}
|
||||
@@ -94,6 +100,11 @@ const std::vector<Set>& YuGiOhGameView::setsForDialog() {
|
||||
}
|
||||
|
||||
void YuGiOhGameView::onAddCard(wxWindow* parentWindow) {
|
||||
if (cardEditModalIsActive()) {
|
||||
showThemedMessageDialog(parentWindow, wxString::FromUTF8(kCardEditModalBlockedUtf8),
|
||||
wxString::FromUTF8("Add card"), wxOK | wxICON_INFORMATION);
|
||||
return;
|
||||
}
|
||||
YuGiOhCard fresh;
|
||||
fresh.amount = 1;
|
||||
fresh.language = Language::English;
|
||||
@@ -102,6 +113,7 @@ void YuGiOhGameView::onAddCard(wxWindow* parentWindow) {
|
||||
YuGiOhCardEditDialog dlg(parentWindow, images_, sets_, cardPreview_, EditMode::Create, fresh,
|
||||
&setsForDialog());
|
||||
themeModalDialog(&dlg, config_.current().theme);
|
||||
CardEditModalGuard modalGuard;
|
||||
if (dlg.ShowModal() != wxID_OK) return;
|
||||
|
||||
auto added = collection_.add(Game::YuGiOh, dlg.card());
|
||||
@@ -141,9 +153,15 @@ void YuGiOhGameView::onEditCard(wxWindow* parentWindow) {
|
||||
showThemedMessageDialog(parentWindow, "Select a card first.", "Edit", wxOK | wxICON_INFORMATION);
|
||||
return;
|
||||
}
|
||||
if (cardEditModalIsActive()) {
|
||||
showThemedMessageDialog(parentWindow, wxString::FromUTF8(kCardEditModalBlockedUtf8),
|
||||
wxString::FromUTF8("Edit"), wxOK | wxICON_INFORMATION);
|
||||
return;
|
||||
}
|
||||
YuGiOhCardEditDialog dlg(parentWindow, images_, sets_, cardPreview_, EditMode::Edit, *sel,
|
||||
&setsForDialog());
|
||||
themeModalDialog(&dlg, config_.current().theme);
|
||||
CardEditModalGuard modalGuard;
|
||||
if (dlg.ShowModal() != wxID_OK) return;
|
||||
auto updated = collection_.update(Game::YuGiOh, dlg.card());
|
||||
if (!updated) {
|
||||
|
||||
Reference in New Issue
Block a user