Excluded locations using virtualized list

This commit is contained in:
Luke Street
2026-08-23 14:36:19 -06:00
parent 9a4b1edfe5
commit 622b782614
2 changed files with 96 additions and 102 deletions
+6 -28
View File
@@ -3,25 +3,6 @@
gap: 0dp;
}
.excluded-locations-pane-left {
padding: 0dp;
}
.clear-all-button {
margin: 12dp 24dp;
margin-bottom: 0dp;
}
.filter-input {
margin: 6dp 24dp;
height: 40dp;
}
.excluded-location-button {
margin: 6dp 24dp;
margin-bottom: 0dp;
}
.excluded-locations-header {
font-family: "Fira Sans Condensed";
font-weight: bold;
@@ -47,18 +28,15 @@
font-size: 15dp;
}
.excluded-locations-pane {
display: flex;
flex-flow: column;
flex: 1 1 0;
min-width: 0;
min-height: 0;
gap: 8dp;
overflow-y: hidden;
.excluded-locations-list {
font-size: 20dp;
border-top: 1dp #92875B;
}
.excluded-locations-list:not(:first-child) ui-list-content {
padding-top: 8dp;
}
.excluded-locations-inner-pane-right {
gap: 8dp;
font-size: 20dp;
@@ -67,4 +45,4 @@
.excluded-locations-inner-pane-right > span {
padding-left: 10dp;
}
}
+90 -74
View File
@@ -1118,19 +1118,18 @@ ModResult updateStartingInventoryTab(ModContext* ctx, void*, ModError*) {
UiElementHandle exlocHeaderElem = 0;
UiElementHandle exlocSubHeaderElem = 0;
UiElementHandle exlocRightPaneElem = 0;
UiElementHandle exlocLeftPaneElem = 0;
UiElementHandle exlocClearBtnElem = 0;
// UiElementHandle exlocFilterInputElem = 0;
UiListHandle exlocList = 0;
std::string exlocFilter{};
struct ExcludedTabLocData {
std::string name {};
std::string name{};
std::string lowercaseName{};
std::unordered_set<std::string> categories{};
};
auto& getExcludedLocationsList() {
static std::list<ExcludedTabLocData> locationsForExcludedTab;
const std::vector<ExcludedTabLocData>& excluded_location_catalog() {
static std::vector<ExcludedTabLocData> locationsForExcludedTab;
// If we haven't loaded the locations to display for the excluded locations tab, load them up
if (locationsForExcludedTab.empty()) {
@@ -1142,7 +1141,7 @@ auto& getExcludedLocationsList() {
name = locationNode["Name"].as<std::string>();
lowercaseName = name;
std::transform(lowercaseName.begin(), lowercaseName.end(), lowercaseName.begin(),
[](unsigned char c) { return std::tolower(c); });
[](unsigned char c) { return std::tolower(c); });
for (const auto& category : locationNode["Categories"]) {
excludedTabLocData.categories.insert(category.as<std::string>());
@@ -1160,25 +1159,24 @@ auto& getExcludedLocationsList() {
}
// Certain locations we don't include for now
if (randomizer::utility::str::Contains(excludedTabLocData.name,
"Renados Letter", "Telma Invoice", "Wooden Statue", "Ilia Charm",
"Defeat Ganondorf", "Twilit Insect", "Twilit Bloat", "Hint"))
if (utility::str::Contains(excludedTabLocData.name, "Renados Letter", "Telma Invoice",
"Wooden Statue", "Ilia Charm", "Defeat Ganondorf", "Twilit Insect",
"Twilit Bloat", "Hint"))
{
continue;
}
locationsForExcludedTab.push_back(excludedTabLocData);
locationsForExcludedTab.push_back(std::move(excludedTabLocData));
}
locationsForExcludedTab.sort([](const auto& a, const auto& b) {
return a.name < b.name;
});
std::ranges::sort(
locationsForExcludedTab, [](const auto& a, const auto& b) { return a.name < b.name; });
}
// Create the vector we're going to return
static std::vector<const std::string*> locationNames{};
locationNames.clear();
return locationsForExcludedTab;
}
std::vector<UiListItem> excluded_location_items() {
// Get settings values
auto& randoSettings = GetRandomizerConfig().GetSettings().GetMap();
bool goldenBugs = randoSettings.at("Golden Bugs") == "On";
@@ -1194,17 +1192,18 @@ auto& getExcludedLocationsList() {
// Create lowercase filter
std::string lowercaseFilter = exlocFilter;
std::transform(lowercaseFilter.begin(), lowercaseFilter.end(), lowercaseFilter.begin(),
[](unsigned char c) { return std::tolower(c); });
[](unsigned char c) { return std::tolower(c); });
// Add relevant location names
for (const auto& locData : locationsForExcludedTab) {
std::vector<UiListItem> items;
const auto& catalog = excluded_location_catalog();
items.reserve(catalog.size());
for (size_t i = 0; i < catalog.size(); ++i) {
const auto& locData = catalog[i];
// Skip categories that aren't shuffled
auto& cats = locData.categories;
const auto& cats = locData.categories;
if ((!goldenBugs && cats.contains("Golden Bug")) ||
(!skyCharacters && cats.contains("Sky Character")) ||
(!npcs && cats.contains("Npc")) ||
(!shops && cats.contains("Shop")) ||
(!goldenWolves && cats.contains("Golden Wolf")) ||
(!skyCharacters && cats.contains("Sky Character")) || (!npcs && cats.contains("Npc")) ||
(!shops && cats.contains("Shop")) || (!goldenWolves && cats.contains("Golden Wolf")) ||
(!hiddenRupees && cats.contains("Rupee - Hidden")) ||
(!freestandingRupees && cats.contains("Rupee - Freestanding")) ||
(!overworldPoes && cats.contains("Poe") && cats.contains("Overworld")) ||
@@ -1218,81 +1217,98 @@ auto& getExcludedLocationsList() {
continue;
}
locationNames.push_back(&locData.name);
UiListItem item = UI_LIST_ITEM_INIT;
item.key = i;
item.label = locData.name.c_str();
items.push_back(item);
}
return locationNames;
return items;
}
void refresh_excluded_location_items() {
if (exlocList == 0) {
return;
}
const auto items = excluded_location_items();
session::svc_mng.ui->list_set_items(
session::svc_mng.mod_ctx, exlocList, items.data(), items.size());
}
ModResult buildExcludedLocationsTab(ModContext* ctx, UiWindowHandle, UiElementHandle leftPane,
UiElementHandle rightPane, void*, ModError*)
{
UiElementHandle rightPane, void*, ModError*) {
using namespace session;
auto mod_ctx = svc_mng.mod_ctx;
exlocList = 0;
svc_mng.ui->elem_set_class(mod_ctx, leftPane, "excluded-locations-pane-left", true);
svc_mng.ui->elem_set_class(mod_ctx, rightPane, "excluded-locations-pane-right", true);
add_button(leftPane,
"Clear All",
"",
add_button(
leftPane, "Clear All", "",
[](ModContext*, void*) {
GetRandomizerConfig().GetSettings().GetModifiableExcludedLocations().clear();
SaveRandomizerConfig();
},
nullptr,
&exlocClearBtnElem);
nullptr, &exlocClearBtnElem);
svc_mng.ui->elem_set_class(mod_ctx, exlocClearBtnElem, "clear-all-button", true);
// TODO: leaving out filter for now, since we're building all the buttons at tab init.
// try to reimplement later when UiService is able to support something closer to the original impl
/* add_string_input(leftPane,
"Filter",
"",
256,
[](ModContext*, void*, UiControlValue* out_value) {
out_value->string_value = exlocFilter.c_str();
},
[](ModContext*, void*, const UiControlValue* value) {
exlocFilter = value->string_value;
},
&exlocFilterInputElem);
svc_mng.ui->elem_set_class(mod_ctx, exlocFilterInputElem, "filter-input", true); */
UiControlDesc filter = UI_CONTROL_DESC_INIT;
filter.kind = UI_CONTROL_STRING;
filter.label = "Filter";
filter.binding = UI_BINDING_CALLBACKS;
filter.get = [](ModContext*, void*, UiControlValue* outValue) {
outValue->string_value = exlocFilter.c_str();
};
filter.set = [](ModContext*, void*, const UiControlValue* value) {
exlocFilter = value->string_value;
refresh_excluded_location_items();
};
filter.max_length = 256;
filter.string_set_mode = UI_STRING_SET_ON_CHANGE;
svc_mng.ui->pane_add_control(mod_ctx, leftPane, &filter, nullptr);
svc_mng.ui->pane_add_rml(mod_ctx, leftPane, "", &exlocLeftPaneElem);
svc_mng.ui->elem_set_class(mod_ctx, exlocLeftPaneElem, "excluded-locations-pane", true);
auto exlocList = getExcludedLocationsList();
for (auto e : exlocList) {
UiElementHandle handle{};
add_button(leftPane,
e->c_str(),
"",
[](ModContext*, void* userdata) {
std::string locationName = static_cast<const char*>(userdata);
auto& excludedLocations = GetRandomizerConfig().GetSettings().GetModifiableExcludedLocations();
if (excludedLocations.contains(locationName)) {
excludedLocations.erase(locationName);
} else {
excludedLocations.insert(locationName);
}
SaveRandomizerConfig();
},
nullptr,
(void*)e->data(),
&handle);
svc_mng.ui->elem_set_class(mod_ctx, handle, "excluded-location-button", true);
const auto items = excluded_location_items();
UiListDesc list = UI_LIST_DESC_INIT;
list.items = items.data();
list.item_count = items.size();
list.on_pressed = [](ModContext*, UiListHandle, uint64_t key, void*) {
const auto& catalog = excluded_location_catalog();
if (key >= catalog.size()) {
return;
}
const auto& locationName = catalog[key].name;
auto& excludedLocations =
GetRandomizerConfig().GetSettings().GetModifiableExcludedLocations();
if (excludedLocations.contains(locationName)) {
excludedLocations.erase(locationName);
} else {
excludedLocations.insert(locationName);
}
SaveRandomizerConfig();
};
list.is_selected = [](ModContext*, UiListHandle, uint64_t key, void*) {
const auto& catalog = excluded_location_catalog();
return key < catalog.size() &&
GetRandomizerConfig().GetSettings().GetExcludedLocations().contains(
catalog[key].name);
};
const ModResult listResult = svc_mng.ui->pane_add_list(mod_ctx, leftPane, &list, &exlocList);
if (listResult != MOD_OK) {
return listResult;
}
svc_mng.ui->elem_set_class(mod_ctx, exlocList, "excluded-locations-list", true);
svc_mng.ui->pane_add_text(mod_ctx, rightPane, "Current Excluded Locations", &exlocHeaderElem);
svc_mng.ui->elem_set_class(mod_ctx, exlocHeaderElem, "excluded-locations-header", true);
svc_mng.ui->pane_add_text(mod_ctx, rightPane, "Re-select a location to remove it", &exlocSubHeaderElem);
svc_mng.ui->pane_add_text(
mod_ctx, rightPane, "Re-select a location to remove it", &exlocSubHeaderElem);
svc_mng.ui->elem_set_class(mod_ctx, exlocSubHeaderElem, "excluded-locations-subheader", true);
svc_mng.ui->pane_add_rml(mod_ctx, rightPane, "", &exlocRightPaneElem);
svc_mng.ui->elem_set_class(mod_ctx, exlocRightPaneElem, "excluded-locations-inner-pane-right", true);
svc_mng.ui->elem_set_class(
mod_ctx, exlocRightPaneElem, "excluded-locations-inner-pane-right", true);
return MOD_OK;
}