convenience features (#21)

This commit is contained in:
Sebastian Dine
2026-07-24 08:56:53 +02:00
committed by GitHub
parent ab0e3c5ae2
commit 7cf25d671f
22 changed files with 351 additions and 38 deletions
+59 -7
View File
@@ -80,16 +80,23 @@ public:
using card_type = TCard;
using sort_column_type = TSortColumn;
// Replace the displayed rows. Selection is reset (the panel will pick
// the first row on the next idle turn — see rebuildRows()).
void setCards(std::vector<TCard> cards) {
// Replace the displayed rows. When preferSelectId is set, selects that
// card if present (used after Add). Otherwise preserves the previously
// selected card by id when still present; the first-row CallAfter path in
// rebuildRows() runs only when there was no prior selection (startup).
void setCards(std::vector<TCard> cards,
std::optional<std::uint32_t> preferSelectId = std::nullopt) {
std::optional<std::uint32_t> keepId = preferSelectId;
if (!keepId) {
if (auto sel = selected()) keepId = sel->id;
}
cards_ = std::move(cards);
// Drop sort state when the underlying data is replaced - the indicator
// shown in the header should match the order actually rendered, and
// wxListCtrl keeps the indicator across DeleteAllItems().
nextDirByCol_.clear();
list_->RemoveSortIndicator();
rebuildRows();
rebuildRows(keepId);
if (!autoSizedOnce_ && !cards_.empty()) {
autoSizeAllColumns();
autoSizedOnce_ = true;
@@ -143,6 +150,30 @@ public:
list_->SetFocus();
}
// Move the selection by `delta` rows (+1 / -1). Used when Up/Down are
// pressed while focus is on the filter box. Clamps to the visible range;
// leaves list HWND focus alone so the caret can stay in the filter.
void nudgeSelection(int delta) {
if (list_ == nullptr || list_->GetItemCount() <= 0 || delta == 0) return;
long row = list_->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
if (row < 0) row = 0;
const long count = list_->GetItemCount();
long next = row + delta;
if (next < 0) next = 0;
if (next >= count) next = count - 1;
if (next == row) {
list_->EnsureVisible(next);
return;
}
suppressListFocus_ = true;
list_->SetItemState(row, 0, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
list_->SetItemState(next,
wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED,
wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
list_->EnsureVisible(next);
suppressListFocus_ = false;
}
protected:
// Column descriptor types -------------------------------------------------
@@ -330,6 +361,17 @@ private:
addText(textCols_.back().label, textCols_.back().width, noteCol);
headerRow_->SetSizer(s);
// Header is mouse-only (sort / resize). Keep it out of the tab order so
// Up/Down after a header click still drive the list, not wx focus travel.
headerRow_->SetCanFocus(false);
for (wxWindow* cell : headerCells_) {
if (cell == nullptr) continue;
cell->SetCanFocus(false);
for (wxWindow* child : cell->GetChildren()) {
if (child != nullptr) child->SetCanFocus(false);
}
}
}
// ----- header drag-resize / sort hit-test ---------------------------------
@@ -486,6 +528,7 @@ private:
sortBy(*sortCol, ascending);
rebuildRows(keepId);
if (list_ != nullptr) list_->SetFocus();
}
// ----- cached icon bitmaps for NM_CUSTOMDRAW -----------------------------
@@ -644,15 +687,22 @@ private:
// no per-row icon swap is required here.
(void)event;
if (inRebuild_) return;
// Row click / native arrow keys: keep HWND focus on the list. Filter
// nudge sets suppressListFocus_ so the caret stays in the text box.
if (!suppressListFocus_ && list_ != nullptr) list_->SetFocus();
notifySelectionChanged();
}
void onListItemActivated(wxListEvent& event) {
(void)event;
if (inRebuild_) return;
wxCommandEvent ev(EVT_CARD_ACTIVATED, GetId());
ev.SetEventObject(this);
ProcessWindowEvent(ev);
// Defer so ShowModal (Edit) does not run inside the list notify path.
CallAfter([this]() {
if (inRebuild_) return;
wxCommandEvent ev(EVT_CARD_ACTIVATED, GetId());
ev.SetEventObject(this);
ProcessWindowEvent(ev);
});
}
// ----- members ----------------------------------------------------------
@@ -682,6 +732,8 @@ private:
// Rebuild guard - see ui_wx/AGENTS.md for the burst-suppression rationale.
bool inRebuild_{false};
// When true, onSelectionChanged skips list_->SetFocus (filter Up/Down nudge).
bool suppressListFocus_{false};
std::map<TSortColumn, bool> nextDirByCol_;
@@ -50,12 +50,13 @@ public:
}
[[nodiscard]] bool hostsOwnLayout() const noexcept override { return true; }
void refreshCollection() override;
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 Digimon (Digi-Battle)";
+10 -3
View File
@@ -13,6 +13,8 @@
#include "ccm/domain/Set.hpp"
#include "ccm/ui/Theme.hpp"
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
@@ -54,9 +56,10 @@ public:
// list/selected panels parented onto MainFrame's shared splitter.
[[nodiscard]] virtual bool hostsOwnLayout() const noexcept { return false; }
// Reload the active collection from disk and refresh the panels. The
// selected card is preserved when possible.
virtual void refreshCollection() = 0;
// Reload the active collection from disk and refresh the panels. When
// selectId is set, that card is selected if present (e.g. after Add);
// otherwise the previously selected card is preserved when possible.
virtual void refreshCollection(std::optional<std::uint32_t> selectId = std::nullopt) = 0;
// Toolbar actions. `parentWindow` is the dialog owner for any modal we
// open (typically the `MainFrame`).
@@ -71,6 +74,10 @@ public:
// Forwarded by `MainFrame` whenever the filter input changes.
virtual void setFilter(std::string_view filter) = 0;
// Move the card-list selection by `delta` rows (+1 / -1). Used when Up/Down
// are pressed while the filter text box has focus.
virtual void nudgeSelection(int delta) = 0;
// Apply the active palette to all panels owned by this view.
virtual void applyTheme(const ThemePalette& palette) = 0;
+2 -1
View File
@@ -38,12 +38,13 @@ public:
wxPanel* listPanel(wxWindow* parent) override;
wxPanel* selectedPanel(wxWindow* parent) override;
void refreshCollection() override;
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 Magic"; }
+2 -1
View File
@@ -55,12 +55,13 @@ public:
}
[[nodiscard]] bool hostsOwnLayout() const noexcept override { return true; }
void refreshCollection() override;
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 Pokemon"; }
+2 -1
View File
@@ -50,12 +50,13 @@ public:
}
[[nodiscard]] bool hostsOwnLayout() const noexcept override { return true; }
void refreshCollection() override;
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!"; }