2 Commits
Author SHA1 Message Date
NeilClack 47785cd4b9 Proclamations: sub-cats always expanded, stable list width
build-dev / nix-build (push) Successful in 43s
- Sub-categories start expanded and cannot be collapsed
- Only root categories toggle open/closed
- LEFT on a sub-category is ignored (navigates to parent for depth-2)
- [+]/[-] indicators only shown on root categories
- Bounding box width computed over all items to prevent
  horizontal shifting when groups expand or collapse
2026-08-25 07:15:59 -05:00
NeilClack 15583edef8 Re-order settings menu 2026-08-25 06:57:43 -05:00
2 changed files with 48 additions and 35 deletions
+30 -17
View File
@@ -198,7 +198,7 @@ void ProclamationOptionsPage::rebuildItems()
FlatItem::Type::SubCategory, FlatItem::Type::SubCategory,
std::to_string(sub.id), std::to_string(sub.id),
sub.name, sub.name,
1, false, false, false, 1, true, false, false,
}); });
for (const auto& p : proclamations_) { for (const auto& p : proclamations_) {
@@ -268,7 +268,7 @@ void ProclamationOptionsPage::rebuildVisible()
parentVisible = true; // the always-on top-level proclamation parentVisible = true; // the always-on top-level proclamation
} else if (currentRoot >= 0 && items_[currentRoot].expanded) { } else if (currentRoot >= 0 && items_[currentRoot].expanded) {
parentVisible = (item.depth == 1) || parentVisible = (item.depth == 1) ||
(item.depth == 2 && currentSub >= 0 && items_[currentSub].expanded); (item.depth == 2 && currentSub >= 0);
} }
if (parentVisible) visible_.push_back(i); if (parentVisible) visible_.push_back(i);
} }
@@ -283,7 +283,7 @@ void ProclamationOptionsPage::toggleItem()
if (visible_.empty()) return; if (visible_.empty()) return;
FlatItem& item = items_[visible_[highlightIndex_]]; FlatItem& item = items_[visible_[highlightIndex_]];
if (item.type == FlatItem::Type::RootCategory || item.type == FlatItem::Type::SubCategory) { if (item.type == FlatItem::Type::RootCategory) {
item.expanded = !item.expanded; item.expanded = !item.expanded;
rebuildVisible(); rebuildVisible();
return; return;
@@ -388,7 +388,7 @@ void ProclamationOptionsPage::collapseCurrent()
{ {
if (visible_.empty()) return; if (visible_.empty()) return;
FlatItem& item = items_[visible_[highlightIndex_]]; FlatItem& item = items_[visible_[highlightIndex_]];
if (item.type == FlatItem::Type::RootCategory || item.type == FlatItem::Type::SubCategory) { if (item.type == FlatItem::Type::RootCategory) {
if (item.expanded) { if (item.expanded) {
item.expanded = false; item.expanded = false;
rebuildVisible(); rebuildVisible();
@@ -400,7 +400,7 @@ void ProclamationOptionsPage::expandCurrent()
{ {
if (visible_.empty()) return; if (visible_.empty()) return;
FlatItem& item = items_[visible_[highlightIndex_]]; FlatItem& item = items_[visible_[highlightIndex_]];
if (item.type == FlatItem::Type::RootCategory || item.type == FlatItem::Type::SubCategory) { if (item.type == FlatItem::Type::RootCategory) {
if (!item.expanded) { if (!item.expanded) {
item.expanded = true; item.expanded = true;
rebuildVisible(); rebuildVisible();
@@ -526,8 +526,7 @@ void ProclamationOptionsPage::handleEvent(const SDL_Event& event)
if (searchActive_) return; if (searchActive_) return;
if (!visible_.empty()) { if (!visible_.empty()) {
const FlatItem& item = items_[visible_[highlightIndex_]]; const FlatItem& item = items_[visible_[highlightIndex_]];
if ((item.type == FlatItem::Type::RootCategory || if (item.type == FlatItem::Type::RootCategory && item.expanded) {
item.type == FlatItem::Type::SubCategory) && item.expanded) {
collapseCurrent(); collapseCurrent();
} else { } else {
moveToParent(); moveToParent();
@@ -655,14 +654,18 @@ void ProclamationOptionsPage::render(SDL_Renderer* renderer)
SDL_RenderTexture(renderer, emptyHintText_.texture, nullptr, &dst); SDL_RenderTexture(renderer, emptyHintText_.texture, nullptr, &dst);
} }
} else { } else {
// Compute the widest row so the block can be centered. // Stable width: compute from every result so the list doesn't shift.
float maxTotalWidth = 0.0f; float maxTotalWidth = 0.0f;
for (const auto& r : searchResults_) {
int w = 0, h = 0;
TTF_GetStringSize(fontItem_, proclamations_[r].title.c_str(), 0, &w, &h);
maxTotalWidth = std::max(maxTotalWidth, static_cast<float>(w));
}
std::vector<float> textWidths(static_cast<size_t>(listSize)); std::vector<float> textWidths(static_cast<size_t>(listSize));
for (int i = scrollOffset_; i < scrollOffset_ + visibleCount_ && i < listSize; ++i) { for (int i = scrollOffset_; i < scrollOffset_ + visibleCount_ && i < listSize; ++i) {
const std::string& label = proclamations_[searchResults_[i]].title;
int w = 0, h = 0; int w = 0, h = 0;
TTF_GetStringSize(fontItem_, label.c_str(), 0, &w, &h); TTF_GetStringSize(fontItem_, proclamations_[searchResults_[i]].title.c_str(), 0, &w, &h);
maxTotalWidth = std::max(maxTotalWidth, static_cast<float>(w));
textWidths[i] = static_cast<float>(w); textWidths[i] = static_cast<float>(w);
} }
float sharedX = (fw - maxTotalWidth) / 2.0f; float sharedX = (fw - maxTotalWidth) / 2.0f;
@@ -695,19 +698,29 @@ void ProclamationOptionsPage::render(SDL_Renderer* renderer)
} }
} else { } else {
// --- Browse tree --- // --- Browse tree ---
// Compute the widest row so the block can be centered. // Stable width: compute from every item so the list doesn't
// shift left/right when groups expand or collapse.
float maxTotalWidth = 0.0f; float maxTotalWidth = 0.0f;
std::vector<float> textWidths(visible_.size()); for (const auto& item : items_) {
for (int i = scrollOffset_; i < scrollOffset_ + visibleCount_ && i < static_cast<int>(visible_.size()); ++i) {
const FlatItem& item = items_[visible_[i]];
std::string label = item.label; std::string label = item.label;
if (item.type == FlatItem::Type::RootCategory || item.type == FlatItem::Type::SubCategory) { if (item.type == FlatItem::Type::RootCategory) {
label = (item.expanded ? "[-] " : "[+] ") + label; label = (item.expanded ? "[-] " : "[+] ") + label;
} }
int w = 0, h = 0; int w = 0, h = 0;
TTF_GetStringSize(fontItem_, label.c_str(), 0, &w, &h); TTF_GetStringSize(fontItem_, label.c_str(), 0, &w, &h);
float total = item.depth * indent + static_cast<float>(w); float total = item.depth * indent + static_cast<float>(w);
maxTotalWidth = std::max(maxTotalWidth, total); maxTotalWidth = std::max(maxTotalWidth, total);
}
std::vector<float> textWidths(visible_.size());
for (int i = scrollOffset_; i < scrollOffset_ + visibleCount_ && i < static_cast<int>(visible_.size()); ++i) {
const FlatItem& item = items_[visible_[i]];
std::string label = item.label;
if (item.type == FlatItem::Type::RootCategory) {
label = (item.expanded ? "[-] " : "[+] ") + label;
}
int w = 0, h = 0;
TTF_GetStringSize(fontItem_, label.c_str(), 0, &w, &h);
textWidths[i] = static_cast<float>(w); textWidths[i] = static_cast<float>(w);
} }
@@ -721,7 +734,7 @@ void ProclamationOptionsPage::render(SDL_Renderer* renderer)
float y = listTop + rowIndex * rowPitch; float y = listTop + rowIndex * rowPitch;
std::string label = item.label; std::string label = item.label;
if (item.type == FlatItem::Type::RootCategory || item.type == FlatItem::Type::SubCategory) { if (item.type == FlatItem::Type::RootCategory) {
label = (item.expanded ? "[-] " : "[+] ") + label; label = (item.expanded ? "[-] " : "[+] ") + label;
} }
+18 -18
View File
@@ -19,18 +19,18 @@ namespace
{ {
constexpr int MENU_ITEM_COUNT = 9; constexpr int MENU_ITEM_COUNT = 9;
// 0 = WiFi Setup, 1 = Audio Options, 2 = Time Zone Setup, 3 = Proclamations, // 0 = Proclamations, 1 = Audio Options, 2 = Email Address, 3 = License Key,
// 4 = Email Address, 5 = License Key, 6 = Date Format, // 4 = WiFi Setup, 5 = Time Zone Setup, 6 = Date Format,
// 7 = View System Logs, 8 = Back // 7 = System Logs, 8 = Back
constexpr const char *MENU_LABELS[MENU_ITEM_COUNT] = { constexpr const char *MENU_LABELS[MENU_ITEM_COUNT] = {
"WiFi Setup",
"Audio Options",
"Time Zone Setup",
"Proclamations", "Proclamations",
"Audio Options",
"Email Address", "Email Address",
"License Key", "License Key",
"WiFi Setup",
"Time Zone Setup",
"Date Format", "Date Format",
"View System Logs", "System Logs",
"Back to Home", "Back to Home",
}; };
@@ -78,15 +78,6 @@ void Settings::handleEvent(const SDL_Event &event)
switch (selectedItem_) switch (selectedItem_)
{ {
case 0: case 0:
state_->pages.push(*state_, std::make_unique<WiFiSetupPage>());
break;
case 1:
state_->pages.push(*state_, std::make_unique<AudioSettingsPage>());
break;
case 2:
state_->pages.push(*state_, std::make_unique<TimeZonePage>());
break;
case 3:
// The Proclamation list is license-gated like Play. // The Proclamation list is license-gated like Play.
if (state_->licenseTracker.isValid()) if (state_->licenseTracker.isValid())
{ {
@@ -97,12 +88,21 @@ void Settings::handleEvent(const SDL_Event &event)
state_->pages.push(*state_, std::make_unique<LicenseBlockedPage>()); state_->pages.push(*state_, std::make_unique<LicenseBlockedPage>());
} }
break; break;
case 4: case 1:
state_->pages.push(*state_, std::make_unique<AudioSettingsPage>());
break;
case 2:
state_->pages.push(*state_, std::make_unique<EmailAddressPage>()); state_->pages.push(*state_, std::make_unique<EmailAddressPage>());
break; break;
case 5: case 3:
state_->pages.push(*state_, std::make_unique<LicenseKeyPage>()); state_->pages.push(*state_, std::make_unique<LicenseKeyPage>());
break; break;
case 4:
state_->pages.push(*state_, std::make_unique<WiFiSetupPage>());
break;
case 5:
state_->pages.push(*state_, std::make_unique<TimeZonePage>());
break;
case 6: case 6:
state_->pages.push(*state_, std::make_unique<DateFormatPage>()); state_->pages.push(*state_, std::make_unique<DateFormatPage>());
break; break;