From 95576e6b373ea355c0bafe5eb290ef419ac4bf7b Mon Sep 17 00:00:00 2001
From: Link of Origin
Date: Wed, 2 Apr 2025 16:29:06 -0600
Subject: [PATCH 01/20] Rough first pass
Conditional visibility added as a feature to the SettingsList
---
.../pages/generator/generator.component.ts | 61 +++++++++++++++++++
SettingTypes.py | 9 ++-
SettingsList.py | 36 +++++++++--
SettingsToJson.py | 59 ++++++++++++++++++
4 files changed, 158 insertions(+), 7 deletions(-)
diff --git a/GUI/src/app/pages/generator/generator.component.ts b/GUI/src/app/pages/generator/generator.component.ts
index 00814d275..402c07ba1 100644
--- a/GUI/src/app/pages/generator/generator.component.ts
+++ b/GUI/src/app/pages/generator/generator.component.ts
@@ -1264,6 +1264,27 @@ export class GeneratorComponent implements OnInit {
}
private triggerSettingVisibility(targetSetting: any, targetValue: boolean, triggeredChange: boolean) {
+ // Resolve logic that could conditionally enable this setting.
+ // ORDER MATTERS! Logic that could disable settings is below this, which gives that priority (and is what we want).
+ if (targetSetting["conditionally_controls_setting"] != null) {
+ targetSetting["conditionally_controls_setting"].split(",").forEach(setting => {
+
+ let dependentSetting = this.global.findSettingByName(setting);
+ let dependentSettingConditionalEnables = dependentSetting.conditional_visibility;
+ if (dependentSettingConditionalEnables != null) {
+ let conditionsMetToEnable = this.conditionsMetToEnable(dependentSettingConditionalEnables);
+ console.log("Setting should be enabled: " + dependentSetting.name + " => " + conditionsMetToEnable);
+
+ if (conditionsMetToEnable != this.global.generator_settingsVisibilityMap[dependentSetting.name]) {
+ console.log("Setting conditionally changing visibility: " + dependentSetting.name + " => " + conditionsMetToEnable)
+ this.global.generator_settingsVisibilityMap[dependentSetting.name] = conditionsMetToEnable;
+ triggeredChange = true;
+ }
+ }
+ });
+ }
+
+ // This list of settings could be disabled entirely (Legacy)
targetSetting["controls_visibility_setting"].split(",").forEach(setting => {
//Ignore settings that don't exist in this specific app
@@ -1272,19 +1293,59 @@ export class GeneratorComponent implements OnInit {
let enabledChildren = false;
+ // If the setting can be enabled but its currently disabled, attempt to re-enable it.
if (targetValue == false && this.global.generator_settingsVisibilityMap[setting] == true) {
enabledChildren = this.clearDeactivationsOfSetting(this.global.findSettingByName(setting));
}
+ // If the setting will be disabled but it is currently enabled, note a change is occurring.
+ // Alternatively, if we enabled it earlier, note a change is occurring.
if ((targetValue == true && this.global.generator_settingsVisibilityMap[setting] == false) || (enabledChildren)) //Only trigger change if a (sub) setting gets re-enabled
triggeredChange = true;
+ // targetValue = false => This setting will be enabled.
+ // targetValue = true => This setting will be disabled.
this.global.generator_settingsVisibilityMap[setting] = targetValue;
});
return triggeredChange;
}
+ private conditionsMetToEnable(settingConditions: any) {
+ let shouldEnable = false;
+ // There may be multiple combinations of conditions that may enable this setting.
+ // We'll check each one, and if one of them passes we'll enable the setting
+ for (let conditionName in settingConditions) {
+ console.log(conditionName + " => Evaluating condition...");
+ let conditionList = settingConditions[conditionName];
+ let conditionsPassed = [];
+ for (let i = 0; i < conditionList.length; i++) {
+ let condition = conditionList[i];
+ let partialConditionPassed = false;
+ // Only one of these conditional settings has to match the given value
+ for (let conditionalSettingName in condition) {
+ // If the conditional setting is currently set to the conditional value...
+ if (condition[conditionalSettingName] == this.global.generator_settingsMap[conditionalSettingName]) {
+ console.log(conditionName + " => Partial condition passed! Reason: " + conditionalSettingName + "=" + condition[conditionalSettingName]);
+ partialConditionPassed = true;
+ break;
+ }
+ }
+
+ conditionsPassed.push(partialConditionPassed);
+ };
+
+ // If one full condition passed, we'll enable the setting
+ if (!conditionsPassed.includes(false)) {
+ console.log(conditionName + " => Evaulated condition as PASSING!");
+ shouldEnable = true; // Could early exit after this, but letting it process all conditions for debugging purposes
+ }
+ }
+
+ console.log("Setting should be enabled due to conditions: " + shouldEnable);
+ return shouldEnable;
+ }
+
clearDeactivationsOfSetting(setting: any) {
let enabledChildren = false;
diff --git a/SettingTypes.py b/SettingTypes.py
index 9a6ac734b..d83847b6e 100644
--- a/SettingTypes.py
+++ b/SettingTypes.py
@@ -10,7 +10,7 @@
class SettingInfo:
def __init__(self, setting_type: type, gui_text: Optional[str], gui_type: Optional[str], shared: bool,
choices: Optional[dict | list] = None, default: Any = None, disabled_default: Any = None,
- disable: Optional[dict] = None, gui_tooltip: Optional[str] = None, gui_params: Optional[dict] = None,
+ disable: Optional[dict] = None, conditional_visibility: Optional[dict] = None, gui_tooltip: Optional[str] = None, gui_params: Optional[dict] = None,
cosmetic: bool = False) -> None:
self.type: type = setting_type # type of the setting's value, used to properly convert types to setting strings
self.shared: bool = shared # whether the setting is one that should be shared, used in converting settings to a string
@@ -19,6 +19,7 @@ def __init__(self, setting_type: type, gui_text: Optional[str], gui_type: Option
self.gui_type: Optional[str] = gui_type
self.gui_tooltip: Optional[str] = "" if gui_tooltip is None else gui_tooltip
self.gui_params: dict[str, Any] = {} if gui_params is None else gui_params # additional parameters that the randomizer uses for the gui
+ self.conditional_visibility: Optional[dict] = conditional_visibility # dictionary of settings and values that can enable this setting
self.disable: Optional[dict] = disable # dictionary of settings this setting disabled
self.dependency = None # lambda that determines if this is disabled. Generated later
@@ -162,9 +163,10 @@ class SettingInfoInt(SettingInfo):
def __init__(self, gui_text: Optional[str], gui_type: Optional[str], shared: bool,
choices: Optional[dict | list] = None, default: Optional[int] = None,
disabled_default: Optional[int] = None, disable: Optional[dict] = None,
+ conditional_visibility: Optional[dict] = None,
gui_tooltip: Optional[str] = None, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
super().__init__(setting_type=int, gui_text=gui_text, gui_type=gui_type, shared=shared, choices=choices,
- default=default, disabled_default=disabled_default, disable=disable, gui_tooltip=gui_tooltip,
+ default=default, disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility, gui_tooltip=gui_tooltip,
gui_params=gui_params, cosmetic=cosmetic)
def __get__(self, obj, obj_type=None) -> int:
@@ -299,6 +301,7 @@ def __init__(self, gui_text: Optional[str], choices: Optional[dict | list], defa
class Scale(SettingInfoInt):
def __init__(self, gui_text: Optional[str], default: Optional[int], minimum: int, maximum: int, step: int = 1,
gui_tooltip: Optional[str] = None, disable: Optional[dict] = None, disabled_default: Optional[int] = None,
+ conditional_visibility: Optional[dict] = None,
shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
choices = {
i: str(i) for i in range(minimum, maximum+1, step)
@@ -311,7 +314,7 @@ def __init__(self, gui_text: Optional[str], default: Optional[int], minimum: int
gui_params['step'] = step
super().__init__(gui_text=gui_text, gui_type='Scale', shared=shared, choices=choices, default=default,
- disabled_default=disabled_default, disable=disable, gui_tooltip=gui_tooltip,
+ disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility, gui_tooltip=gui_tooltip,
gui_params=gui_params, cosmetic=cosmetic)
diff --git a/SettingsList.py b/SettingsList.py
index 8a259bfc3..b151c58b5 100644
--- a/SettingsList.py
+++ b/SettingsList.py
@@ -716,8 +716,8 @@ class SettingInfos:
'randomize_key': 'randomize_settings',
},
disable = {
- True: {'settings': ['shuffle_ganon_bosskey', 'ganon_bosskey_stones', 'ganon_bosskey_medallions', 'ganon_bosskey_rewards', 'ganon_bosskey_tokens', 'ganon_bosskey_hearts']},
- False: {'settings': ['triforce_count_per_world', 'triforce_goal_per_world']},
+ True: {'settings': ['ganon_bosskey_stones', 'ganon_bosskey_medallions', 'ganon_bosskey_rewards', 'ganon_bosskey_tokens', 'ganon_bosskey_hearts']},
+ # False: {'settings': ['triforce_count_per_world', 'triforce_goal_per_world']},
},
)
@@ -742,6 +742,18 @@ class SettingInfos:
'web:max': 200,
'electron:max': 200,
},
+ conditional_visibility = {
+ # Single condition, name is for reading purposes only
+ "TCPW_ganon_boss_key_on_triforce_pieces_AND_triforce_hunt_disabled": [ # FIXME: EXAMPLE ONLY - REMOVE/UPDATE
+ # First partial condition that must be met for the whole condition to be true
+ {"shuffle_ganon_bosskey": "triforces"}, # List of setting/value pairs, of which at least ONE of which must be true (OR logic). key = setting name, value = setting value
+ # Second partial condition that must also be met for the whole condition to be true (AND logic)
+ {"triforce_hunt": False},
+ ],
+ "TCPW_triforce_hunt_enabled": [
+ {"triforce_hunt": True},
+ ],
+ },
)
triforce_goal_per_world = Scale(
@@ -762,6 +774,18 @@ class SettingInfos:
'web:max': 100,
'electron:max': 100,
},
+ conditional_visibility = {
+ # Single condition, name is for reading purposes only
+ "TGPW_ganon_boss_key_on_triforce_pieces_AND_triforce_hunt_disabled": [ # FIXME: EXAMPLE ONLY - REMOVE/UPDATE
+ # First setting value that must be set for this condition to be met
+ {"shuffle_ganon_bosskey": "triforces"}, # List of setting/value pairs, of which at least ONE of which must be true (OR logic). key = setting name, value = setting value
+ # Second setting that must also be true for this to pass (AND logic)
+ {"triforce_hunt": False},
+ ],
+ "TGPW_triforce_hunt_enabled": [
+ {"triforce_hunt": True},
+ ],
+ },
)
lacs_condition = Combobox(
@@ -1069,6 +1093,7 @@ class SettingInfos:
'dungeons': "Dungeon Rewards",
'tokens': "Tokens",
'hearts': "Hearts",
+ 'triforces': "Triforce Pieces",
},
gui_tooltip = '''\
'Remove': Ganon's Castle Boss Key is removed
@@ -1110,6 +1135,8 @@ class SettingInfos:
'Hearts': Ganon's Castle Boss Key will be awarded
when reaching the target number of hearts.
+
+ TODO: Add tooltip for triforce pieces
''',
shared = True,
disable = {
@@ -1118,6 +1145,7 @@ class SettingInfos:
'!dungeons': {'settings': ['ganon_bosskey_rewards']},
'!tokens': {'settings': ['ganon_bosskey_tokens']},
'!hearts': {'settings': ['ganon_bosskey_hearts']},
+ # 'triforces': {'settings': ['ganon_bosskey_stones', 'ganon_bosskey_medallions','ganon_bosskey_rewards','ganon_bosskey_tokens','ganon_bosskey_hearts']},
},
gui_params = {
'randomize_key': 'randomize_settings',
@@ -5575,9 +5603,9 @@ class UnmappedSettingError(Exception):
if info.disable is not None:
for option, disabling in info.disable.items():
- negative = False
+ negative = False # If this option is enabled, the "disabling" settings will be disabled
if isinstance(option, str) and option[0] == '!':
- negative = True
+ negative = True # If this option is NOT enabled, the "disabling" settings will be disabled
option = option[1:]
for setting_name in disabling.get('settings', []):
SettingInfos.setting_infos[setting_name].create_dependency(info, option, negative)
diff --git a/SettingsToJson.py b/SettingsToJson.py
index 0dddae3ef..f2a3956de 100755
--- a/SettingsToJson.py
+++ b/SettingsToJson.py
@@ -15,6 +15,7 @@
setting_keys: list[str] = ['hide_when_disabled', 'min', 'max', 'size', 'max_length', 'file_types', 'no_line_break', 'function', 'option_remove', 'dynamic']
types_with_options: list[str] = ['Checkbutton', 'Radiobutton', 'Combobox', 'SearchBox', 'MultipleSelect']
+conditional_visibility_dependencies: dict[str, dict] = {}
def remove_trailing_lines(text: str) -> str:
while text.endswith('
'):
@@ -53,6 +54,11 @@ def add_disable_option_to_json(disable_option: dict[str, Any], option_json: dict
option_json['controls_visibility_tab'] += ',' + ','.join(disable_option['tabs'])
+def mark_conditional_visibility_option_for_setting(setting_name: str, conditional_settings: dict[str, Any]) -> None:
+ if setting_name not in conditional_visibility_dependencies:
+ conditional_visibility_dependencies[setting_name] = conditional_settings
+
+
def get_setting_json(setting: str, web_version: bool, as_array: bool = False) -> Optional[dict[str, Any]]:
try:
setting_info = SettingInfos.setting_infos[setting]
@@ -82,6 +88,11 @@ def get_setting_json(setting: str, web_version: bool, as_array: bool = False) ->
setting_disable = {}
if setting_info.disable is not None:
setting_disable = copy.deepcopy(setting_info.disable)
+
+ # If this setting can be conditionally enabled, we will need to revisit it once the full JSON has been built
+ if setting_info.conditional_visibility is not None:
+ mark_conditional_visibility_option_for_setting(setting_info.name, setting_info.conditional_visibility)
+ setting_json['conditional_visibility'] = setting_info.conditional_visibility
version_specific_keys = []
@@ -260,6 +271,9 @@ def create_settings_list_json(path: str, web_version: bool = False) -> None:
output_json['cosmeticsObj'][tab['name']] = tab_json_object
output_json['cosmeticsArray'].append(tab_json_array)
+ # Resolve conditional visibility settings now that the full json is available
+ resolve_conditional_visibility_dependencies(output_json)
+
for d in hint_dist_files():
with open(d, 'r') as dist_file:
dist = json.load(dist_file)
@@ -273,6 +287,51 @@ def create_settings_list_json(path: str, web_version: bool = False) -> None:
json.dump(output_json, f)
+def resolve_conditional_visibility_dependencies(output_json: dict) -> None:
+ for dependent_setting_name, dependent_conditions in conditional_visibility_dependencies.items():
+ for condition_name, conditions in dependent_conditions.items():
+ for conditional_settings in conditions:
+ for setting_name, setting_value in conditional_settings.items():
+ # Handle the setting in the "object" portion of the json
+ setting_obj_json = find_setting_obj_json_in_output(setting_name, output_json)
+ if setting_obj_json is not None:
+ setting_obj_option_json = setting_obj_json['options'][setting_value]
+ if setting_obj_option_json is not None:
+ if 'conditionally_controls_setting' not in setting_obj_option_json:
+ setting_obj_option_json['conditionally_controls_setting'] = dependent_setting_name
+ elif dependent_setting_name not in setting_obj_option_json['conditionally_controls_setting']:
+ setting_obj_option_json['conditionally_controls_setting'] += ',' + dependent_setting_name
+
+ # Handle the setting in the "array" portion of the json
+ setting_array_json = find_setting_array_json_in_output(setting_name, output_json)
+ if setting_array_json is not None:
+ for setting_array_option_json in setting_array_json['options']:
+ if setting_array_option_json['name'] is setting_value:
+ if 'conditionally_controls_setting' not in setting_array_option_json:
+ setting_array_option_json['conditionally_controls_setting'] = dependent_setting_name
+ elif dependent_setting_name not in setting_array_option_json['conditionally_controls_setting']:
+ setting_array_option_json['conditionally_controls_setting'] += ',' + dependent_setting_name
+ break # bail early(...bad idea?)
+
+
+def find_setting_obj_json_in_output(setting_name: str, output_json: dict[str, dict[str, dict]]) -> dict:
+ for setting_tab_name, setting_tab_json in output_json['settingsObj'].items():
+ for setting_section_name, setting_section_json in setting_tab_json['sections'].items():
+ setting_json = setting_section_json['settings'].get(setting_name)
+ if setting_json is not None:
+ return setting_json
+ return None
+
+
+def find_setting_array_json_in_output(setting_name: str, output_json: dict[str, dict[str, dict]]) -> dict:
+ for setting_tab_json in output_json['settingsArray']:
+ for setting_section_json in setting_tab_json['sections']:
+ for setting_json in setting_section_json['settings']:
+ if setting_name == setting_json['name']:
+ return setting_json
+ return None
+
+
def get_setting_details(setting_key: str, web_version: bool) -> None:
setting_json_object = get_setting_json(setting_key, web_version, as_array=False)
setting_json_array = get_setting_json(setting_key, web_version, as_array=True)
From 5b10aebfb9f08989a5c6f7aafcae6617e9be84f2 Mon Sep 17 00:00:00 2001
From: Link of Origin
Date: Mon, 7 Apr 2025 13:01:54 -0600
Subject: [PATCH 02/20] Fix CI errors
---
SettingsList.py | 2 +-
SettingsToJson.py | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/SettingsList.py b/SettingsList.py
index b151c58b5..1649b5e24 100644
--- a/SettingsList.py
+++ b/SettingsList.py
@@ -1135,7 +1135,7 @@ class SettingInfos:
'Hearts': Ganon's Castle Boss Key will be awarded
when reaching the target number of hearts.
-
+
TODO: Add tooltip for triforce pieces
''',
shared = True,
diff --git a/SettingsToJson.py b/SettingsToJson.py
index f2a3956de..19132c19a 100755
--- a/SettingsToJson.py
+++ b/SettingsToJson.py
@@ -88,7 +88,7 @@ def get_setting_json(setting: str, web_version: bool, as_array: bool = False) ->
setting_disable = {}
if setting_info.disable is not None:
setting_disable = copy.deepcopy(setting_info.disable)
-
+
# If this setting can be conditionally enabled, we will need to revisit it once the full JSON has been built
if setting_info.conditional_visibility is not None:
mark_conditional_visibility_option_for_setting(setting_info.name, setting_info.conditional_visibility)
@@ -301,7 +301,7 @@ def resolve_conditional_visibility_dependencies(output_json: dict) -> None:
setting_obj_option_json['conditionally_controls_setting'] = dependent_setting_name
elif dependent_setting_name not in setting_obj_option_json['conditionally_controls_setting']:
setting_obj_option_json['conditionally_controls_setting'] += ',' + dependent_setting_name
-
+
# Handle the setting in the "array" portion of the json
setting_array_json = find_setting_array_json_in_output(setting_name, output_json)
if setting_array_json is not None:
From 0c2ff6e1fc453ff728be1ad9ec84f442976e6d22 Mon Sep 17 00:00:00 2001
From: Link of Origin
Date: Mon, 7 Apr 2025 14:20:17 -0600
Subject: [PATCH 03/20] Update remaining setting types
Successfully patched a ROM with this change. Did not load it up and playtest it.
---
Notes/GUI/architecture.md | 1 +
SettingTypes.py | 120 +++++++++++++++++++-------------------
2 files changed, 61 insertions(+), 60 deletions(-)
diff --git a/Notes/GUI/architecture.md b/Notes/GUI/architecture.md
index b1e8850da..36ca59994 100644
--- a/Notes/GUI/architecture.md
+++ b/Notes/GUI/architecture.md
@@ -81,6 +81,7 @@ The settings array follows that defines the settings that should appear in this
* controls-visibility-tab → What tab(s) to disable when this setting is enabled, used for Checkbuttons. Multiple tabs can be separated by comma and are addressed by their internal name
* controls-visibility-section → What section(s) to disable when this setting is enabled
* controls-visibility-setting → What specific setting(s) to disable when this setting is enabled
+* conditional-visibility → List of setting/value pairs this setting may be dependent on to determine what its current state should be (eg. disabled, specific value, etc.)
* hide-when-disabled → If this setting should be completely hidden when it gets disabled, not just greyed out. Used on the website to make the difference between generator and patcher more distinct
* min → The minimum numeric value allowed. Used for Scales and Numberinputs
* max → The maximum numeric value allowed. Used for Scales and Numberinputs. Can differ between Electron and website (e.g. multi world limit)
diff --git a/SettingTypes.py b/SettingTypes.py
index d83847b6e..590e20b45 100644
--- a/SettingTypes.py
+++ b/SettingTypes.py
@@ -101,10 +101,10 @@ def create_dependency(self, disabling_setting: 'SettingInfo', option, negative:
class SettingInfoNone(SettingInfo):
def __init__(self, gui_text: Optional[str], gui_type: Optional[str], gui_tooltip: Optional[str] = None,
- gui_params: Optional[dict] = None) -> None:
+ gui_params: Optional[dict] = None, conditional_visibility: Optional[dict] = None) -> None:
super().__init__(setting_type=type(None), gui_text=gui_text, gui_type=gui_type, shared=False, choices=None,
- default=None, disabled_default=None, disable=None, gui_tooltip=gui_tooltip,
- gui_params=gui_params, cosmetic=False)
+ default=None, disabled_default=None, conditional_visibility=conditional_visibility,
+ disable=None, gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=False)
def __get__(self, obj, obj_type=None) -> None:
raise Exception(f"{self.name} is not a setting and cannot be retrieved.")
@@ -115,16 +115,16 @@ def __set__(self, obj, value: str) -> None:
class SettingInfoBool(SettingInfo):
def __init__(self, gui_text: Optional[str], gui_type: Optional[str], shared: bool, default: Optional[bool] = None,
- disabled_default: Optional[bool] = None, disable: Optional[dict] = None, gui_tooltip: Optional[str] = None,
- gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
+ disabled_default: Optional[bool] = None, disable: Optional[dict] = None, conditional_visibility: Optional[dict] = None,
+ gui_tooltip: Optional[str] = None, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
choices = {
True: 'checked',
False: 'unchecked',
}
super().__init__(setting_type=bool, gui_text=gui_text, gui_type=gui_type, shared=shared, choices=choices,
- default=default, disabled_default=disabled_default, disable=disable, gui_tooltip=gui_tooltip,
- gui_params=gui_params, cosmetic=cosmetic)
+ default=default, disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
def __get__(self, obj, obj_type=None) -> bool:
value = super().__get__(obj, obj_type)
@@ -141,11 +141,11 @@ def __set__(self, obj, value: bool) -> None:
class SettingInfoStr(SettingInfo):
def __init__(self, gui_text: Optional[str], gui_type: Optional[str], shared: bool = False,
choices: Optional[dict | list] = None, default: Optional[str] = None,
- disabled_default: Optional[str] = None, disable: Optional[dict] = None,
+ disabled_default: Optional[str] = None, disable: Optional[dict] = None, conditional_visibility: Optional[dict] = None,
gui_tooltip: Optional[str] = None, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
super().__init__(setting_type=str, gui_text=gui_text, gui_type=gui_type, shared=shared, choices=choices,
- default=default, disabled_default=disabled_default, disable=disable, gui_tooltip=gui_tooltip,
- gui_params=gui_params, cosmetic=cosmetic)
+ default=default, disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
def __get__(self, obj, obj_type=None) -> str:
value = super().__get__(obj, obj_type)
@@ -162,12 +162,11 @@ def __set__(self, obj, value: str) -> None:
class SettingInfoInt(SettingInfo):
def __init__(self, gui_text: Optional[str], gui_type: Optional[str], shared: bool,
choices: Optional[dict | list] = None, default: Optional[int] = None,
- disabled_default: Optional[int] = None, disable: Optional[dict] = None,
- conditional_visibility: Optional[dict] = None,
+ disabled_default: Optional[int] = None, disable: Optional[dict] = None, conditional_visibility: Optional[dict] = None,
gui_tooltip: Optional[str] = None, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
super().__init__(setting_type=int, gui_text=gui_text, gui_type=gui_type, shared=shared, choices=choices,
- default=default, disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility, gui_tooltip=gui_tooltip,
- gui_params=gui_params, cosmetic=cosmetic)
+ default=default, disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
def __get__(self, obj, obj_type=None) -> int:
value = super().__get__(obj, obj_type)
@@ -184,11 +183,11 @@ def __set__(self, obj, value: int) -> None:
class SettingInfoList(SettingInfo):
def __init__(self, gui_text: Optional[str], gui_type: Optional[str], shared: bool,
choices: Optional[dict | list] = None, default: Optional[list] = None,
- disabled_default: Optional[list] = None, disable: Optional[dict] = None,
+ disabled_default: Optional[list] = None, disable: Optional[dict] = None, conditional_visibility: Optional[dict] = None,
gui_tooltip: Optional[str] = None, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
super().__init__(setting_type=list, gui_text=gui_text, gui_type=gui_type, shared=shared, choices=choices,
- default=default, disabled_default=disabled_default, disable=disable, gui_tooltip=gui_tooltip,
- gui_params=gui_params, cosmetic=cosmetic)
+ default=default, disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
def __get__(self, obj, obj_type=None) -> list:
value = super().__get__(obj, obj_type)
@@ -205,11 +204,11 @@ def __set__(self, obj, value: list) -> None:
class SettingInfoDict(SettingInfo):
def __init__(self, gui_text: Optional[str], gui_type: Optional[str], shared: bool,
choices: Optional[dict | list] = None, default: Optional[dict] = None,
- disabled_default: Optional[dict] = None, disable: Optional[dict] = None,
+ disabled_default: Optional[dict] = None, disable: Optional[dict] = None, conditional_visibility: Optional[dict] = None,
gui_tooltip: Optional[str] = None, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
super().__init__(setting_type=dict, gui_text=gui_text, gui_type=gui_type, shared=shared, choices=choices,
- default=default, disabled_default=disabled_default, disable=disable, gui_tooltip=gui_tooltip,
- gui_params=gui_params, cosmetic=cosmetic)
+ default=default, disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
def __get__(self, obj, obj_type=None) -> dict:
value = super().__get__(obj, obj_type)
@@ -225,84 +224,83 @@ def __set__(self, obj, value: dict) -> None:
class Button(SettingInfoNone):
def __init__(self, gui_text: Optional[str], gui_tooltip: Optional[str] = None,
- gui_params: Optional[dict] = None) -> None:
- super().__init__(gui_text=gui_text, gui_type="Button", gui_tooltip=gui_tooltip, gui_params=gui_params)
+ gui_params: Optional[dict] = None, conditional_visibility: Optional[dict] = None) -> None:
+ super().__init__(gui_text=gui_text, gui_type="Button", gui_tooltip=gui_tooltip, gui_params=gui_params, conditional_visibility=conditional_visibility)
class Textbox(SettingInfoNone):
def __init__(self, gui_text: Optional[str], gui_tooltip: Optional[str] = None,
- gui_params: Optional[dict] = None) -> None:
- super().__init__(gui_text=gui_text, gui_type="Textbox", gui_tooltip=gui_tooltip, gui_params=gui_params)
+ gui_params: Optional[dict] = None, conditional_visibility: Optional[dict] = None) -> None:
+ super().__init__(gui_text=gui_text, gui_type="Textbox", gui_tooltip=gui_tooltip, gui_params=gui_params, conditional_visibility=conditional_visibility)
class Checkbutton(SettingInfoBool):
def __init__(self, gui_text: Optional[str], gui_tooltip: Optional[str] = None, disable: Optional[dict] = None,
- disabled_default: Optional[bool] = None, default: bool = False, shared: bool = False,
- gui_params: Optional[dict] = None, cosmetic: bool = False):
+ disabled_default: Optional[bool] = None, conditional_visibility: Optional[dict] = None, default: bool = False,
+ shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False):
super().__init__(gui_text=gui_text, gui_type='Checkbutton', shared=shared, default=default,
- disabled_default=disabled_default, disable=disable, gui_tooltip=gui_tooltip,
- gui_params=gui_params, cosmetic=cosmetic)
+ disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
class Combobox(SettingInfoStr):
def __init__(self, gui_text: Optional[str], choices: Optional[dict | list], default: Optional[str],
gui_tooltip: Optional[str] = None, disable: Optional[dict] = None, disabled_default: Optional[str] = None,
- shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
+ conditional_visibility: Optional[dict] = None, shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
super().__init__(gui_text=gui_text, gui_type='Combobox', shared=shared, choices=choices, default=default,
- disabled_default=disabled_default, disable=disable, gui_tooltip=gui_tooltip,
- gui_params=gui_params, cosmetic=cosmetic)
+ disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
class Radiobutton(SettingInfoStr):
def __init__(self, gui_text: Optional[str], choices: Optional[dict | list], default: Optional[str],
gui_tooltip: Optional[str] = None, disable: Optional[dict] = None, disabled_default: Optional[str] = None,
- shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
+ conditional_visibility: Optional[dict] = None, shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
super().__init__(gui_text=gui_text, gui_type='Radiobutton', shared=shared, choices=choices, default=default,
- disabled_default=disabled_default, disable=disable, gui_tooltip=gui_tooltip,
- gui_params=gui_params, cosmetic=cosmetic)
+ disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
class Fileinput(SettingInfoStr):
def __init__(self, gui_text: Optional[str], choices: Optional[dict | list] = None, default: Optional[str] = None,
gui_tooltip: Optional[str] = None, disable: Optional[dict] = None, disabled_default: Optional[str] = None,
- shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
+ conditional_visibility: Optional[dict] = None, shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
super().__init__(gui_text=gui_text, gui_type='Fileinput', shared=shared, choices=choices, default=default,
- disabled_default=disabled_default, disable=disable, gui_tooltip=gui_tooltip,
- gui_params=gui_params, cosmetic=cosmetic)
+ disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
class Directoryinput(SettingInfoStr):
def __init__(self, gui_text: Optional[str], choices: Optional[dict | list] = None, default: Optional[str] = None,
gui_tooltip: Optional[str] = None, disable: Optional[dict] = None, disabled_default: Optional[str] = None,
- shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
+ conditional_visibility: Optional[dict] = None, shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
super().__init__(gui_text=gui_text, gui_type='Directoryinput', shared=shared, choices=choices, default=default,
- disabled_default=disabled_default, disable=disable, gui_tooltip=gui_tooltip,
- gui_params=gui_params, cosmetic=cosmetic)
+ disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
class Textinput(SettingInfoStr):
def __init__(self, gui_text: Optional[str], choices: Optional[dict | list] = None, default: Optional[str] = None,
gui_tooltip: Optional[str] = None, disable: Optional[dict] = None, disabled_default: Optional[str] = None,
- shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
+ conditional_visibility: Optional[dict] = None, shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
super().__init__(gui_text=gui_text, gui_type='Textinput', shared=shared, choices=choices, default=default,
- disabled_default=disabled_default, disable=disable, gui_tooltip=gui_tooltip,
- gui_params=gui_params, cosmetic=cosmetic)
+ disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
class ComboboxInt(SettingInfoInt):
def __init__(self, gui_text: Optional[str], choices: Optional[dict | list], default: Optional[int],
gui_tooltip: Optional[str] = None, disable: Optional[dict] = None, disabled_default: Optional[int] = None,
- shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
+ conditional_visibility: Optional[dict] = None, shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
super().__init__(gui_text=gui_text, gui_type='Combobox', shared=shared, choices=choices, default=default,
- disabled_default=disabled_default, disable=disable, gui_tooltip=gui_tooltip,
- gui_params=gui_params, cosmetic=cosmetic)
+ disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
class Scale(SettingInfoInt):
def __init__(self, gui_text: Optional[str], default: Optional[int], minimum: int, maximum: int, step: int = 1,
gui_tooltip: Optional[str] = None, disable: Optional[dict] = None, disabled_default: Optional[int] = None,
- conditional_visibility: Optional[dict] = None,
- shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
+ conditional_visibility: Optional[dict] = None, shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
choices = {
i: str(i) for i in range(minimum, maximum+1, step)
}
@@ -314,15 +312,15 @@ def __init__(self, gui_text: Optional[str], default: Optional[int], minimum: int
gui_params['step'] = step
super().__init__(gui_text=gui_text, gui_type='Scale', shared=shared, choices=choices, default=default,
- disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility, gui_tooltip=gui_tooltip,
- gui_params=gui_params, cosmetic=cosmetic)
+ disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
class Numberinput(SettingInfoInt):
def __init__(self, gui_text: Optional[str], default: Optional[int], minimum: Optional[int] = None,
maximum: Optional[int] = None, gui_tooltip: Optional[str] = None, disable: Optional[dict] = None,
- disabled_default: Optional[int] = None, shared: bool = False, gui_params: Optional[dict] = None,
- cosmetic: bool = False) -> None:
+ disabled_default: Optional[int] = None, conditional_visibility: Optional[dict] = None,
+ shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
if gui_params is None:
gui_params = {}
if minimum is not None:
@@ -331,23 +329,25 @@ def __init__(self, gui_text: Optional[str], default: Optional[int], minimum: Opt
gui_params['max'] = maximum
super().__init__(gui_text=gui_text, gui_type='Numberinput', shared=shared, choices=None, default=default,
- disabled_default=disabled_default, disable=disable, gui_tooltip=gui_tooltip,
- gui_params=gui_params, cosmetic=cosmetic)
+ disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
class MultipleSelect(SettingInfoList):
def __init__(self, gui_text: Optional[str], choices: Optional[dict | list], default: Optional[list],
gui_tooltip: Optional[str] = None, disable: Optional[dict] = None, disabled_default: Optional[list] = None,
- shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
+ conditional_visibility: Optional[dict] = None, shared: bool = False, gui_params: Optional[dict] = None,
+ cosmetic: bool = False) -> None:
super().__init__(gui_text=gui_text, gui_type='MultipleSelect', shared=shared, choices=choices, default=default,
- disabled_default=disabled_default, disable=disable, gui_tooltip=gui_tooltip,
- gui_params=gui_params, cosmetic=cosmetic)
+ disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
class SearchBox(SettingInfoList):
def __init__(self, gui_text: Optional[str], choices: Optional[dict | list], default: Optional[list],
gui_tooltip: Optional[str] = None, disable: Optional[dict] = None, disabled_default: Optional[list] = None,
- shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
+ conditional_visibility: Optional[dict] = None, shared: bool = False, gui_params: Optional[dict] = None,
+ cosmetic: bool = False) -> None:
super().__init__(gui_text=gui_text, gui_type='SearchBox', shared=shared, choices=choices, default=default,
- disabled_default=disabled_default, disable=disable, gui_tooltip=gui_tooltip,
- gui_params=gui_params, cosmetic=cosmetic)
+ disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
From 2b34c73144baeaa51c3c6e9519a5d5f7b85a7253 Mon Sep 17 00:00:00 2001
From: Link of Origin
Date: Mon, 7 Apr 2025 14:24:09 -0600
Subject: [PATCH 04/20] Remove testing code
Can always cherry-pick revert this change if you want to try it
---
.../pages/generator/generator.component.ts | 6 ----
SettingsList.py | 30 +------------------
2 files changed, 1 insertion(+), 35 deletions(-)
diff --git a/GUI/src/app/pages/generator/generator.component.ts b/GUI/src/app/pages/generator/generator.component.ts
index 402c07ba1..79475af8c 100644
--- a/GUI/src/app/pages/generator/generator.component.ts
+++ b/GUI/src/app/pages/generator/generator.component.ts
@@ -1273,10 +1273,8 @@ export class GeneratorComponent implements OnInit {
let dependentSettingConditionalEnables = dependentSetting.conditional_visibility;
if (dependentSettingConditionalEnables != null) {
let conditionsMetToEnable = this.conditionsMetToEnable(dependentSettingConditionalEnables);
- console.log("Setting should be enabled: " + dependentSetting.name + " => " + conditionsMetToEnable);
if (conditionsMetToEnable != this.global.generator_settingsVisibilityMap[dependentSetting.name]) {
- console.log("Setting conditionally changing visibility: " + dependentSetting.name + " => " + conditionsMetToEnable)
this.global.generator_settingsVisibilityMap[dependentSetting.name] = conditionsMetToEnable;
triggeredChange = true;
}
@@ -1316,7 +1314,6 @@ export class GeneratorComponent implements OnInit {
// There may be multiple combinations of conditions that may enable this setting.
// We'll check each one, and if one of them passes we'll enable the setting
for (let conditionName in settingConditions) {
- console.log(conditionName + " => Evaluating condition...");
let conditionList = settingConditions[conditionName];
let conditionsPassed = [];
for (let i = 0; i < conditionList.length; i++) {
@@ -1326,7 +1323,6 @@ export class GeneratorComponent implements OnInit {
for (let conditionalSettingName in condition) {
// If the conditional setting is currently set to the conditional value...
if (condition[conditionalSettingName] == this.global.generator_settingsMap[conditionalSettingName]) {
- console.log(conditionName + " => Partial condition passed! Reason: " + conditionalSettingName + "=" + condition[conditionalSettingName]);
partialConditionPassed = true;
break;
}
@@ -1337,12 +1333,10 @@ export class GeneratorComponent implements OnInit {
// If one full condition passed, we'll enable the setting
if (!conditionsPassed.includes(false)) {
- console.log(conditionName + " => Evaulated condition as PASSING!");
shouldEnable = true; // Could early exit after this, but letting it process all conditions for debugging purposes
}
}
- console.log("Setting should be enabled due to conditions: " + shouldEnable);
return shouldEnable;
}
diff --git a/SettingsList.py b/SettingsList.py
index 1649b5e24..96ff99744 100644
--- a/SettingsList.py
+++ b/SettingsList.py
@@ -717,7 +717,7 @@ class SettingInfos:
},
disable = {
True: {'settings': ['ganon_bosskey_stones', 'ganon_bosskey_medallions', 'ganon_bosskey_rewards', 'ganon_bosskey_tokens', 'ganon_bosskey_hearts']},
- # False: {'settings': ['triforce_count_per_world', 'triforce_goal_per_world']},
+ False: {'settings': ['triforce_count_per_world', 'triforce_goal_per_world']},
},
)
@@ -742,18 +742,6 @@ class SettingInfos:
'web:max': 200,
'electron:max': 200,
},
- conditional_visibility = {
- # Single condition, name is for reading purposes only
- "TCPW_ganon_boss_key_on_triforce_pieces_AND_triforce_hunt_disabled": [ # FIXME: EXAMPLE ONLY - REMOVE/UPDATE
- # First partial condition that must be met for the whole condition to be true
- {"shuffle_ganon_bosskey": "triforces"}, # List of setting/value pairs, of which at least ONE of which must be true (OR logic). key = setting name, value = setting value
- # Second partial condition that must also be met for the whole condition to be true (AND logic)
- {"triforce_hunt": False},
- ],
- "TCPW_triforce_hunt_enabled": [
- {"triforce_hunt": True},
- ],
- },
)
triforce_goal_per_world = Scale(
@@ -774,18 +762,6 @@ class SettingInfos:
'web:max': 100,
'electron:max': 100,
},
- conditional_visibility = {
- # Single condition, name is for reading purposes only
- "TGPW_ganon_boss_key_on_triforce_pieces_AND_triforce_hunt_disabled": [ # FIXME: EXAMPLE ONLY - REMOVE/UPDATE
- # First setting value that must be set for this condition to be met
- {"shuffle_ganon_bosskey": "triforces"}, # List of setting/value pairs, of which at least ONE of which must be true (OR logic). key = setting name, value = setting value
- # Second setting that must also be true for this to pass (AND logic)
- {"triforce_hunt": False},
- ],
- "TGPW_triforce_hunt_enabled": [
- {"triforce_hunt": True},
- ],
- },
)
lacs_condition = Combobox(
@@ -1093,7 +1069,6 @@ class SettingInfos:
'dungeons': "Dungeon Rewards",
'tokens': "Tokens",
'hearts': "Hearts",
- 'triforces': "Triforce Pieces",
},
gui_tooltip = '''\
'Remove': Ganon's Castle Boss Key is removed
@@ -1135,8 +1110,6 @@ class SettingInfos:
'Hearts': Ganon's Castle Boss Key will be awarded
when reaching the target number of hearts.
-
- TODO: Add tooltip for triforce pieces
''',
shared = True,
disable = {
@@ -1145,7 +1118,6 @@ class SettingInfos:
'!dungeons': {'settings': ['ganon_bosskey_rewards']},
'!tokens': {'settings': ['ganon_bosskey_tokens']},
'!hearts': {'settings': ['ganon_bosskey_hearts']},
- # 'triforces': {'settings': ['ganon_bosskey_stones', 'ganon_bosskey_medallions','ganon_bosskey_rewards','ganon_bosskey_tokens','ganon_bosskey_hearts']},
},
gui_params = {
'randomize_key': 'randomize_settings',
From 0a7adcd0760c7a7368fdf69e4542de704bdf1499 Mon Sep 17 00:00:00 2001
From: Link of Origin
Date: Mon, 7 Apr 2025 15:13:26 -0600
Subject: [PATCH 05/20] Revert testing change
---
SettingsList.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/SettingsList.py b/SettingsList.py
index 96ff99744..a4618660f 100644
--- a/SettingsList.py
+++ b/SettingsList.py
@@ -716,7 +716,7 @@ class SettingInfos:
'randomize_key': 'randomize_settings',
},
disable = {
- True: {'settings': ['ganon_bosskey_stones', 'ganon_bosskey_medallions', 'ganon_bosskey_rewards', 'ganon_bosskey_tokens', 'ganon_bosskey_hearts']},
+ True: {'settings': ['shuffle_ganon_bosskey', 'ganon_bosskey_stones', 'ganon_bosskey_medallions', 'ganon_bosskey_rewards', 'ganon_bosskey_tokens', 'ganon_bosskey_hearts']},
False: {'settings': ['triforce_count_per_world', 'triforce_goal_per_world']},
},
)
From 899f42f6b981e939712390b6431c9e64a106880e Mon Sep 17 00:00:00 2001
From: Link of Origin
Date: Mon, 7 Apr 2025 15:24:36 -0600
Subject: [PATCH 06/20] Change element visibility checks to a function call
This change is effectively a no-op, so no actual change in behavior or logic here. However, extracting the logic into a function means we can more dynamically determine if an element should be displayed/hidden or enabled/disabled, etc.
Currently these states are tightly coupled AND there is no way to change that coupling during run-time. With this change, there can be a distinction between whether or not a setting is enabled/disabled and whether it is hidden/visible. This function can be updated to dynamically determine if something should be "disabled and visible" and then later make it "disabled and hidden", which was previously impossible.
---
.../pages/generator/generator.component.html | 2 +-
.../pages/generator/generator.component.ts | 26 ++++++++++++-------
2 files changed, 18 insertions(+), 10 deletions(-)
diff --git a/GUI/src/app/pages/generator/generator.component.html b/GUI/src/app/pages/generator/generator.component.html
index 11d212355..22387bdc9 100644
--- a/GUI/src/app/pages/generator/generator.component.html
+++ b/GUI/src/app/pages/generator/generator.component.html
@@ -13,7 +13,7 @@ {{section.text}}
0" class="subheaderLabel" [innerHTML]="section.subheader">
-
+
diff --git a/GUI/src/app/pages/generator/generator.component.ts b/GUI/src/app/pages/generator/generator.component.ts
index 79475af8c..50aed5d2f 100644
--- a/GUI/src/app/pages/generator/generator.component.ts
+++ b/GUI/src/app/pages/generator/generator.component.ts
@@ -753,7 +753,7 @@ export class GeneratorComponent implements OnInit {
return;
//Check setting is enabled first
- if (!this.global.generator_settingsVisibilityMap[setting.name])
+ if (!this.settingIsEnabled(setting.name))
return;
event.dataTransfer.dropEffect = 'link'; //Change cursor to link icon when in input area
@@ -771,7 +771,7 @@ export class GeneratorComponent implements OnInit {
return;
//Check setting is enabled first
- if (!this.global.generator_settingsVisibilityMap[setting.name])
+ if (!this.settingIsEnabled(setting.name))
return;
let items = event.dataTransfer.items;
@@ -1075,13 +1075,21 @@ export class GeneratorComponent implements OnInit {
return typeof (variable);
}
+ settingIsEnabled(setting_name: string) {
+ return this.global.generator_settingsVisibilityMap[setting_name];
+ }
+
+ settingIsFullyHidden(setting: any) {
+ return !this.settingIsEnabled(setting.name) && setting.hide_when_disabled;
+ }
+
getNextVisibleSetting(settings: any, startingIndex: number) {
if (settings.length > startingIndex) {
for (let i = startingIndex; i < settings.length; i++) {
let setting = settings[i];
- if (this.global.generator_settingsVisibilityMap[setting.name] || !setting.hide_when_disabled)
+ if (!this.settingIsFullyHidden(setting))
return setting;
}
}
@@ -1248,11 +1256,11 @@ export class GeneratorComponent implements OnInit {
let enabledChildren = false;
//If a setting gets disabled, re-enable all the settings that this setting caused to deactivate. The later full check will fix any potential issues
- if (targetValue == false && this.global.generator_settingsVisibilityMap[setting.name] == true) {
+ if (targetValue == false && this.settingIsEnabled(setting.name) == true) {
enabledChildren = this.clearDeactivationsOfSetting(setting);
}
- if ((targetValue == true && this.global.generator_settingsVisibilityMap[setting.name] == false) || (enabledChildren)) //Only trigger change if a (sub) setting gets re-enabled
+ if ((targetValue == true && this.settingIsEnabled(setting.name) == false) || (enabledChildren)) //Only trigger change if a (sub) setting gets re-enabled
triggeredChange = true;
this.global.generator_settingsVisibilityMap[setting.name] = targetValue;
@@ -1274,7 +1282,7 @@ export class GeneratorComponent implements OnInit {
if (dependentSettingConditionalEnables != null) {
let conditionsMetToEnable = this.conditionsMetToEnable(dependentSettingConditionalEnables);
- if (conditionsMetToEnable != this.global.generator_settingsVisibilityMap[dependentSetting.name]) {
+ if (conditionsMetToEnable != this.settingIsEnabled(dependentSetting.name)) {
this.global.generator_settingsVisibilityMap[dependentSetting.name] = conditionsMetToEnable;
triggeredChange = true;
}
@@ -1292,13 +1300,13 @@ export class GeneratorComponent implements OnInit {
let enabledChildren = false;
// If the setting can be enabled but its currently disabled, attempt to re-enable it.
- if (targetValue == false && this.global.generator_settingsVisibilityMap[setting] == true) {
+ if (targetValue == false && this.settingIsEnabled(setting)) {
enabledChildren = this.clearDeactivationsOfSetting(this.global.findSettingByName(setting));
}
// If the setting will be disabled but it is currently enabled, note a change is occurring.
// Alternatively, if we enabled it earlier, note a change is occurring.
- if ((targetValue == true && this.global.generator_settingsVisibilityMap[setting] == false) || (enabledChildren)) //Only trigger change if a (sub) setting gets re-enabled
+ if ((targetValue == true && !this.settingIsEnabled(setting)) || (enabledChildren)) //Only trigger change if a (sub) setting gets re-enabled
triggeredChange = true;
// targetValue = false => This setting will be enabled.
@@ -1374,7 +1382,7 @@ export class GeneratorComponent implements OnInit {
this.global.getGlobalVar('generatorSettingsArray').forEach(tab => tab.sections.forEach(section => section.settings.forEach(checkSetting => {
- if (skipSetting && checkSetting.name === skipSetting || !this.global.generator_settingsVisibilityMap[checkSetting.name]) //Disabled settings can not alter visibility anymore
+ if (skipSetting && checkSetting.name === skipSetting || !this.settingIsEnabled(checkSetting.name)) //Disabled settings can not alter visibility anymore
return;
if (checkSetting["type"] === "Checkbutton" || checkSetting["type"] === "Radiobutton" || checkSetting["type"] === "Combobox" || checkSetting["type"] === "SearchBox") {
From d0679d93bfc82539eef558bfeb13abdc5262cda0 Mon Sep 17 00:00:00 2001
From: Link of Origin
Date: Mon, 7 Apr 2025 15:30:35 -0600
Subject: [PATCH 07/20] Continued decoupling of visibility and enabling logic
---
.../pages/generator/generator.component.html | 86 +++++++++----------
1 file changed, 43 insertions(+), 43 deletions(-)
diff --git a/GUI/src/app/pages/generator/generator.component.html b/GUI/src/app/pages/generator/generator.component.html
index 22387bdc9..9e401883d 100644
--- a/GUI/src/app/pages/generator/generator.component.html
+++ b/GUI/src/app/pages/generator/generator.component.html
@@ -17,13 +17,13 @@ {{section.text}}
- 0 ? 'hint' : 'noop'" nbPopoverPlacement="right" nbPopoverAdjustment="clockwise">{{setting.text}}
- {{setting.text}}
- {{setting.text}}
+ 0 ? 'hint' : 'noop'" nbPopoverPlacement="top" nbPopoverAdjustment="clockwise">
{{option.text}}
@@ -32,8 +32,8 @@ {{section.text}}
- 0" class="comboBoxLabel" [ngClass]="{'disabled': !global.generator_settingsVisibilityMap[setting.name], 'oneLineComboBox': getColumnCount(refEl) > 2 && (setting.no_line_break || (itemIndex > 0 && section.settings[itemIndex-1].no_line_break)), 'oLCBLabelFirst': getColumnCount(refEl) > 2 && setting.no_line_break, 'oLCBLabelSecond': getColumnCount(refEl) > 2 && itemIndex > 0 && section.settings[itemIndex-1].no_line_break}">{{setting.text}} ({{global.generator_customColorMap[setting.name]}})
- 2 && (setting.no_line_break || (itemIndex > 0 && section.settings[itemIndex-1].no_line_break)), 'oLCBLabelFirst': getColumnCount(refEl) > 2 && setting.no_line_break, 'oLCBLabelSecond': getColumnCount(refEl) > 2 && itemIndex > 0 && section.settings[itemIndex-1].no_line_break}">{{setting.text}}
+ 0" class="comboBoxLabel" [ngClass]="{'disabled': !settingIsEnabled(setting.name), 'oneLineComboBox': getColumnCount(refEl) > 2 && (setting.no_line_break || (itemIndex > 0 && section.settings[itemIndex-1].no_line_break)), 'oLCBLabelFirst': getColumnCount(refEl) > 2 && setting.no_line_break, 'oLCBLabelSecond': getColumnCount(refEl) > 2 && itemIndex > 0 && section.settings[itemIndex-1].no_line_break}">{{setting.text}} ({{global.generator_customColorMap[setting.name]}})
+ 2 && (setting.no_line_break || (itemIndex > 0 && section.settings[itemIndex-1].no_line_break)), 'oLCBLabelFirst': getColumnCount(refEl) > 2 && setting.no_line_break, 'oLCBLabelSecond': getColumnCount(refEl) > 2 && itemIndex > 0 && section.settings[itemIndex-1].no_line_break}">{{setting.text}}
{{section.text}}
2 && (setting.no_line_break || (itemIndex > 0 && section.settings[itemIndex-1].no_line_break)), 'select-colors' : getColumnCount(refEl) === 1}"
- [disabled]="!global.generator_settingsVisibilityMap[setting.name]"
+ [disabled]="!settingIsEnabled(setting.name)"
[(selected)]="global.generator_settingsMap[setting.name]"
(selectedChange)="checkVisibility($event, setting, findOption(setting.options, $event), refColorPicker)"
[nbPopover]="tooltipComponent"
@@ -71,7 +71,7 @@ {{section.text}}
2 && (setting.no_line_break || (itemIndex > 0 && section.settings[itemIndex-1].no_line_break))}"
- [disabled]="!global.generator_settingsVisibilityMap[setting.name]"
+ [disabled]="!settingIsEnabled(setting.name)"
[(selected)]="global.generator_settingsMap[setting.name]"
(selectedChange)="checkVisibility($event, setting, findOption(setting.options, $event))"
[nbPopover]="tooltipComponent"
@@ -96,7 +96,7 @@ {{section.text}}
2 && (setting.no_line_break || (itemIndex > 0 && section.settings[itemIndex-1].no_line_break)),
'oLCBLabelFirst': getColumnCount(refEl) > 2 && setting.no_line_break,
'oLCBLabelSecond': getColumnCount(refEl) > 2 && itemIndex > 0 && section.settings[itemIndex-1].no_line_break
@@ -106,7 +106,7 @@ {{section.text}}
2 && setting.no_line_break}"
- [disabled]="!global.generator_settingsVisibilityMap[setting.name]"
+ [disabled]="!settingIsEnabled(setting.name)"
[(selected)]="global.generator_settingsMap[setting.name]"
(selectedChange)="checkVisibility($event, setting, findOption(setting.options, $event))"
[nbPopover]="tooltipComponent"
@@ -130,95 +130,95 @@ {{section.text}}
- {{setting.text}}:
- {{setting.text}}
- {{setting.text}}:
+ {{setting.text}}
+ 0 ? 'hint' : 'noop'" nbPopoverPlacement="right" nbPopoverAdjustment="counterclockwise">
- 0 ? 'hint' : 'noop'" nbPopoverPlacement="right" nbPopoverAdjustment="counterclockwise">
- {{setting.text}}:
- {{setting.text}}
- {{setting.text}}:
+ {{setting.text}}
+ 0 ? 'hint' : 'noop'" nbPopoverPlacement="right" nbPopoverAdjustment="counterclockwise">
- {{setting.text}}
- {{setting.text}}
+ {{setting.text}}
+ {{setting.text}}
- {{setting.text}}:
- {{setting.text}}
- {{setting.text}}:
+ {{setting.text}}
+ 0 ? 'hint' : 'noop'" nbPopoverPlacement="right" nbPopoverAdjustment="counterclockwise">
-
+
- {{setting.text}}
+ {{setting.text}}
- 0 ? 'hint' : 'noop'" nbPopoverPlacement="top" nbPopoverAdjustment="clockwise">
-
+
-
-
+ 0 ? 'hint' : 'noop'" nbPopoverPlacement="top" nbPopoverAdjustment="clockwise" ngfDrop [(file)]="global.generator_settingsMap[setting.name]">
-
-
+ 0 ? 'hint' : 'noop'" nbPopoverPlacement="top" nbPopoverAdjustment="clockwise">
-
-
+
+
- {{setting.text}}
+ {{setting.text}}
- 0 ? 'hint' : 'noop'" nbPopoverPlacement="top" nbPopoverAdjustment="clockwise">
-
+
- 0 ? 'hint' : 'noop'" nbPopoverPlacement="top" nbPopoverAdjustment="clockwise" (dragover)="onDirectoryDragOverWeb($event, setting)" (drop)="onDirectoryDropWeb($event, setting)">
-
+
-
+
-
+
{{section.text}}
[nbPopoverPlacement]="'top'"
[nbPopoverAdjustment]="'vertical'" />
-
-
-
+
+
+
- 0 ? setting.tags : null" [tooltipComponent]="tooltipComponent" tooltip="tooltip" [nbPopover]="tooltipComponent" [nbPopoverContext]="{tooltip: setting.tooltip}" [nbPopoverTrigger]="setting.tooltip && setting.tooltip.length > 0 ? 'click' : 'noop'" nbPopoverPlacement="right" nbPopoverAdjustment="counterclockwise">
From 9ec057cd057ba28d12b5e58371b73f8099aa32da Mon Sep 17 00:00:00 2001
From: Link of Origin
Date: Mon, 7 Apr 2025 15:58:31 -0600
Subject: [PATCH 08/20] Fix comment (I think?)
Still not 100% confident in this particular logic, but this seems correct after looking at it again
---
GUI/src/app/pages/generator/generator.component.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/GUI/src/app/pages/generator/generator.component.ts b/GUI/src/app/pages/generator/generator.component.ts
index 50aed5d2f..5beddf6d4 100644
--- a/GUI/src/app/pages/generator/generator.component.ts
+++ b/GUI/src/app/pages/generator/generator.component.ts
@@ -1309,8 +1309,8 @@ export class GeneratorComponent implements OnInit {
if ((targetValue == true && !this.settingIsEnabled(setting)) || (enabledChildren)) //Only trigger change if a (sub) setting gets re-enabled
triggeredChange = true;
- // targetValue = false => This setting will be enabled.
- // targetValue = true => This setting will be disabled.
+ // targetValue = true => This setting will be enabled.
+ // targetValue = false => This setting will be disabled.
this.global.generator_settingsVisibilityMap[setting] = targetValue;
});
From b66deda91f691514fbe912ff774944c8cebfd963 Mon Sep 17 00:00:00 2001
From: Link of Origin
Date: Mon, 7 Apr 2025 17:00:22 -0600
Subject: [PATCH 09/20] Comments for the visibility logic
Hoping I got it right now... Hard to keep this straight sometimes
---
.../app/pages/generator/generator.component.ts | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/GUI/src/app/pages/generator/generator.component.ts b/GUI/src/app/pages/generator/generator.component.ts
index 5beddf6d4..65f11cc3e 100644
--- a/GUI/src/app/pages/generator/generator.component.ts
+++ b/GUI/src/app/pages/generator/generator.component.ts
@@ -1271,6 +1271,11 @@ export class GeneratorComponent implements OnInit {
return triggeredChange;
}
+ // targetSetting = The current option of the setting to process.
+ // targetValue = 'true' if the settings this option controls should be enabled, 'false' if they should be disabled.
+ // (Note: This is passed in 'checkVisibility' as "option != value", in other words: "This option is NOT the option the setting is being changed to".)
+ // triggeredChange = Set to 'true' to force this function to return 'true', suggesting a change occurred regardless of how things processed.
+ // Otherwise, the function will return 'true' if a dependent setting's state was altered, otherwise it will return 'false'.
private triggerSettingVisibility(targetSetting: any, targetValue: boolean, triggeredChange: boolean) {
// Resolve logic that could conditionally enable this setting.
// ORDER MATTERS! Logic that could disable settings is below this, which gives that priority (and is what we want).
@@ -1290,7 +1295,8 @@ export class GeneratorComponent implements OnInit {
});
}
- // This list of settings could be disabled entirely (Legacy)
+ // NOTE: We are treating any setting under "controls_visibility_setting" as one
+ // that should be disabled by the current option. Could be worth renaming...
targetSetting["controls_visibility_setting"].split(",").forEach(setting => {
//Ignore settings that don't exist in this specific app
@@ -1299,13 +1305,17 @@ export class GeneratorComponent implements OnInit {
let enabledChildren = false;
- // If the setting can be enabled but its currently disabled, attempt to re-enable it.
+ // We are about to disable this setting.
+ // If this is currently enabled, attempt to re-enabled any settings that it
+ // may be disabling on its own. If it's disabled, it shouldn't also disable other settings.
if (targetValue == false && this.settingIsEnabled(setting)) {
enabledChildren = this.clearDeactivationsOfSetting(this.global.findSettingByName(setting));
}
- // If the setting will be disabled but it is currently enabled, note a change is occurring.
- // Alternatively, if we enabled it earlier, note a change is occurring.
+ // We are about to enable this setting.
+ // If this setting is currently disabled, note that we are causing a change.
+ // Alternatively, if disabling this setting causes any other settings to be
+ // enabled due to it being disabled, then also note that we are causing a change
if ((targetValue == true && !this.settingIsEnabled(setting)) || (enabledChildren)) //Only trigger change if a (sub) setting gets re-enabled
triggeredChange = true;
From 62f83e43f45a969237729cdc9a80da41650f948b Mon Sep 17 00:00:00 2001
From: Link of Origin
Date: Tue, 8 Apr 2025 13:30:50 -0600
Subject: [PATCH 10/20] Expand functionality for conditional controls
Conditional controls can now do the following when a condition is met:
- Define a value the setting should be set to
- Enforce whether the setting should be enabled or not
- Enforce whether the setting should be visible or not (currently not possible until more updates are made deeper in the pipeline)
---
.../pages/generator/generator.component.ts | 135 +++++++++++-------
SettingsToJson.py | 4 +-
2 files changed, 89 insertions(+), 50 deletions(-)
diff --git a/GUI/src/app/pages/generator/generator.component.ts b/GUI/src/app/pages/generator/generator.component.ts
index 65f11cc3e..1f41ce1d4 100644
--- a/GUI/src/app/pages/generator/generator.component.ts
+++ b/GUI/src/app/pages/generator/generator.component.ts
@@ -1190,7 +1190,7 @@ export class GeneratorComponent implements OnInit {
this.triggerTabVisibility(targetSetting, targetValue);
}
- if ("controls_visibility_setting" in targetSetting) {
+ if ("controls_visibility_setting" in targetSetting || 'conditionally_controls_setting' in targetSetting) {
triggeredChange = this.triggerSettingVisibility(targetSetting, targetValue, triggeredChange);
}
@@ -1277,62 +1277,92 @@ export class GeneratorComponent implements OnInit {
// triggeredChange = Set to 'true' to force this function to return 'true', suggesting a change occurred regardless of how things processed.
// Otherwise, the function will return 'true' if a dependent setting's state was altered, otherwise it will return 'false'.
private triggerSettingVisibility(targetSetting: any, targetValue: boolean, triggeredChange: boolean) {
- // Resolve logic that could conditionally enable this setting.
- // ORDER MATTERS! Logic that could disable settings is below this, which gives that priority (and is what we want).
- if (targetSetting["conditionally_controls_setting"] != null) {
- targetSetting["conditionally_controls_setting"].split(",").forEach(setting => {
-
- let dependentSetting = this.global.findSettingByName(setting);
- let dependentSettingConditionalEnables = dependentSetting.conditional_visibility;
- if (dependentSettingConditionalEnables != null) {
- let conditionsMetToEnable = this.conditionsMetToEnable(dependentSettingConditionalEnables);
-
- if (conditionsMetToEnable != this.settingIsEnabled(dependentSetting.name)) {
- this.global.generator_settingsVisibilityMap[dependentSetting.name] = conditionsMetToEnable;
- triggeredChange = true;
- }
+ // Resolve logic that could conditionally update this setting.
+ let conditionalSettingUpdates = {};
+ if (targetSetting["conditionally_controls_setting"] != null) {
+ targetSetting["conditionally_controls_setting"].split(",").forEach(setting => {
+
+ let dependentSetting = this.global.findSettingByName(setting);
+ if (dependentSetting.conditional_visibility != null) {
+ let targetSettingState = this.getTargetSettingStateFromConditions(dependentSetting);
+ let currentSettingState = { // basically doing this twice (once here, once in fn call above), could be cleaner...
+ "value": this.global.generator_settingsMap[dependentSetting.name],
+ "visible": !this.settingIsFullyHidden(dependentSetting),
+ "enabled": this.settingIsEnabled(dependentSetting.name),
+ };
+
+ // If any part of the setting would change, save the new setting state for later
+ if (currentSettingState['value'] != targetSettingState['value'] ||
+ currentSettingState['visible'] != targetSettingState['visible'] ||
+ currentSettingState['enabled'] != targetSettingState['enabled']
+ ) {
+ console.log(dependentSetting.name + " setting would be updated by conditions!");
+ conditionalSettingUpdates[dependentSetting.name] = targetSettingState;
}
- });
- }
-
- // NOTE: We are treating any setting under "controls_visibility_setting" as one
- // that should be disabled by the current option. Could be worth renaming...
- targetSetting["controls_visibility_setting"].split(",").forEach(setting => {
-
- //Ignore settings that don't exist in this specific app
- if (!(setting in this.global.generator_settingsVisibilityMap))
- return;
+ }
+ });
+ }
+
+ // NOTE: We are treating any setting under "controls_visibility_setting" as one
+ // that should be disabled by the current option. Could be worth renaming...
+ let settingsDisabled = []; // Setting names in here are being disabled and take priority over any changes made by conditional logic
+ targetSetting["controls_visibility_setting"].split(",").forEach(setting => {
+
+ //Ignore settings that don't exist in this specific app
+ if (!(setting in this.global.generator_settingsVisibilityMap))
+ return;
- let enabledChildren = false;
+ let enabledChildren = false;
- // We are about to disable this setting.
- // If this is currently enabled, attempt to re-enabled any settings that it
- // may be disabling on its own. If it's disabled, it shouldn't also disable other settings.
- if (targetValue == false && this.settingIsEnabled(setting)) {
- enabledChildren = this.clearDeactivationsOfSetting(this.global.findSettingByName(setting));
- }
+ // We are about to disable this setting.
+ // If this is currently enabled, attempt to re-enabled any settings that it
+ // may be disabling on its own. If it's disabled, it shouldn't also disable other settings.
+ if (targetValue == false && this.settingIsEnabled(setting)) {
+ enabledChildren = this.clearDeactivationsOfSetting(this.global.findSettingByName(setting));
+ settingsDisabled.push(setting);
+ }
- // We are about to enable this setting.
- // If this setting is currently disabled, note that we are causing a change.
- // Alternatively, if disabling this setting causes any other settings to be
- // enabled due to it being disabled, then also note that we are causing a change
- if ((targetValue == true && !this.settingIsEnabled(setting)) || (enabledChildren)) //Only trigger change if a (sub) setting gets re-enabled
- triggeredChange = true;
+ // We are about to enable this setting.
+ // If this setting is currently disabled, note that we are triggering a change.
+ // Alternatively, if disabling this setting causes any other settings to be
+ // enabled due to it being disabled, then also note that we are triggering a change.
+ if ((targetValue == true && !this.settingIsEnabled(setting)) || (enabledChildren)) //Only trigger change if a (sub) setting gets re-enabled
+ triggeredChange = true;
// targetValue = true => This setting will be enabled.
// targetValue = false => This setting will be disabled.
- this.global.generator_settingsVisibilityMap[setting] = targetValue;
- });
+ this.global.generator_settingsVisibilityMap[setting] = targetValue;
+ });
+
+ // If a setting won't be forcibly disabled, allow conditions to update the setting
+ for (let settingName in conditionalSettingUpdates) {
+ if (settingsDisabled[settingName] == null) {
+ console.log(settingName + " is being updated by conditions! New state = " + JSON.stringify(conditionalSettingUpdates[settingName]));
+ this.global.generator_settingsMap[settingName] = conditionalSettingUpdates[settingName]['value'];
+ this.global.generator_settingsVisibilityMap[settingName] = conditionalSettingUpdates[settingName]['enabled'];
+ // TODO: Revisit for "visibility" when/if the "visibility" and "enabled" logic are more decoupled and we have more direct control
+ triggeredChange = true;
+ }
+ }
return triggeredChange;
}
- private conditionsMetToEnable(settingConditions: any) {
- let shouldEnable = false;
- // There may be multiple combinations of conditions that may enable this setting.
- // We'll check each one, and if one of them passes we'll enable the setting
+ private getTargetSettingStateFromConditions(setting: any) {
+ // Start with the current state as the target
+ // If no conditions change the target state, then we effectively just return the current state
+ let targetSettingState = {
+ "value": this.global.generator_settingsMap[setting.name],
+ "visible": !this.settingIsFullyHidden(setting),
+ "enabled": this.settingIsEnabled(setting.name),
+ };
+
+ // There may be multiple combinations of conditions that may alter this setting.
+ // We'll check each one, and if one of them passes we'll use that to determine the setting's state.
+ let settingConditions = setting.conditional_visibility;
for (let conditionName in settingConditions) {
- let conditionList = settingConditions[conditionName];
+ var conditionToTest = settingConditions[conditionName];
+ let conditionList = conditionToTest['conditions'];
let conditionsPassed = [];
for (let i = 0; i < conditionList.length; i++) {
let condition = conditionList[i];
@@ -1349,13 +1379,22 @@ export class GeneratorComponent implements OnInit {
conditionsPassed.push(partialConditionPassed);
};
- // If one full condition passed, we'll enable the setting
+ // If one full condition passed, we'll use that condition's target state
if (!conditionsPassed.includes(false)) {
- shouldEnable = true; // Could early exit after this, but letting it process all conditions for debugging purposes
+ console.log(setting.name + " had at least one condition pass!");
+ // TODO: Define priority rules so we know what should take precedent.
+ // - Option 1: First that passes has priority => just early exit
+ // - Option 2: Last that passes has priority => could result in mixed data sets if "condition1" sets some of the state and later "condition 3" sets other parts
+ // - Option 3: Manually define priority inside the blob => basically option 1 with extra logic. But what if two options have the same priority? First or last wins?
+ // If the condition sets one of these keys, we'll use that value. Otherwise use the current value.
+ targetSettingState['value'] = conditionToTest['value'] != null ? conditionToTest['value'] : targetSettingState['value'];
+ targetSettingState['visible'] = conditionToTest['visible'] != null ? conditionToTest['visible'] : targetSettingState['visible'];
+ targetSettingState['enabled'] = conditionToTest['enabled'] != null ? conditionToTest['enabled'] : targetSettingState['enabled'];
+ break; // First condition that passes wins and takes priority
}
}
- return shouldEnable;
+ return targetSettingState;
}
clearDeactivationsOfSetting(setting: any) {
diff --git a/SettingsToJson.py b/SettingsToJson.py
index 19132c19a..c5c0c9bfa 100755
--- a/SettingsToJson.py
+++ b/SettingsToJson.py
@@ -289,8 +289,8 @@ def create_settings_list_json(path: str, web_version: bool = False) -> None:
def resolve_conditional_visibility_dependencies(output_json: dict) -> None:
for dependent_setting_name, dependent_conditions in conditional_visibility_dependencies.items():
- for condition_name, conditions in dependent_conditions.items():
- for conditional_settings in conditions:
+ for condition_name, condition_details in dependent_conditions.items():
+ for conditional_settings in condition_details['conditions']:
for setting_name, setting_value in conditional_settings.items():
# Handle the setting in the "object" portion of the json
setting_obj_json = find_setting_obj_json_in_output(setting_name, output_json)
From b30113282d29d2102af78a6ea9faa00cb160dfcf Mon Sep 17 00:00:00 2001
From: Link of Origin
Date: Tue, 8 Apr 2025 13:44:33 -0600
Subject: [PATCH 11/20] Rename to "conditional_controls"
With the last change, this certainly goes beyond just altering visibility. Updating language throughout to reflect that. Plus, it helps distinguish it from the current "visibility" code that is already a bit of a misnomer.
---
.../pages/generator/generator.component.ts | 4 +-
SettingTypes.py | 80 +++++++++----------
SettingsToJson.py | 20 ++---
3 files changed, 52 insertions(+), 52 deletions(-)
diff --git a/GUI/src/app/pages/generator/generator.component.ts b/GUI/src/app/pages/generator/generator.component.ts
index 1f41ce1d4..07f68ed16 100644
--- a/GUI/src/app/pages/generator/generator.component.ts
+++ b/GUI/src/app/pages/generator/generator.component.ts
@@ -1283,7 +1283,7 @@ export class GeneratorComponent implements OnInit {
targetSetting["conditionally_controls_setting"].split(",").forEach(setting => {
let dependentSetting = this.global.findSettingByName(setting);
- if (dependentSetting.conditional_visibility != null) {
+ if (dependentSetting.conditional_controls != null) {
let targetSettingState = this.getTargetSettingStateFromConditions(dependentSetting);
let currentSettingState = { // basically doing this twice (once here, once in fn call above), could be cleaner...
"value": this.global.generator_settingsMap[dependentSetting.name],
@@ -1359,7 +1359,7 @@ export class GeneratorComponent implements OnInit {
// There may be multiple combinations of conditions that may alter this setting.
// We'll check each one, and if one of them passes we'll use that to determine the setting's state.
- let settingConditions = setting.conditional_visibility;
+ let settingConditions = setting.conditional_controls;
for (let conditionName in settingConditions) {
var conditionToTest = settingConditions[conditionName];
let conditionList = conditionToTest['conditions'];
diff --git a/SettingTypes.py b/SettingTypes.py
index 590e20b45..53f2f0ed8 100644
--- a/SettingTypes.py
+++ b/SettingTypes.py
@@ -10,7 +10,7 @@
class SettingInfo:
def __init__(self, setting_type: type, gui_text: Optional[str], gui_type: Optional[str], shared: bool,
choices: Optional[dict | list] = None, default: Any = None, disabled_default: Any = None,
- disable: Optional[dict] = None, conditional_visibility: Optional[dict] = None, gui_tooltip: Optional[str] = None, gui_params: Optional[dict] = None,
+ disable: Optional[dict] = None, conditional_controls: Optional[dict] = None, gui_tooltip: Optional[str] = None, gui_params: Optional[dict] = None,
cosmetic: bool = False) -> None:
self.type: type = setting_type # type of the setting's value, used to properly convert types to setting strings
self.shared: bool = shared # whether the setting is one that should be shared, used in converting settings to a string
@@ -19,7 +19,7 @@ def __init__(self, setting_type: type, gui_text: Optional[str], gui_type: Option
self.gui_type: Optional[str] = gui_type
self.gui_tooltip: Optional[str] = "" if gui_tooltip is None else gui_tooltip
self.gui_params: dict[str, Any] = {} if gui_params is None else gui_params # additional parameters that the randomizer uses for the gui
- self.conditional_visibility: Optional[dict] = conditional_visibility # dictionary of settings and values that can enable this setting
+ self.conditional_controls: Optional[dict] = conditional_controls # dictionary of settings and values that can enable this setting
self.disable: Optional[dict] = disable # dictionary of settings this setting disabled
self.dependency = None # lambda that determines if this is disabled. Generated later
@@ -101,9 +101,9 @@ def create_dependency(self, disabling_setting: 'SettingInfo', option, negative:
class SettingInfoNone(SettingInfo):
def __init__(self, gui_text: Optional[str], gui_type: Optional[str], gui_tooltip: Optional[str] = None,
- gui_params: Optional[dict] = None, conditional_visibility: Optional[dict] = None) -> None:
+ gui_params: Optional[dict] = None, conditional_controls: Optional[dict] = None) -> None:
super().__init__(setting_type=type(None), gui_text=gui_text, gui_type=gui_type, shared=False, choices=None,
- default=None, disabled_default=None, conditional_visibility=conditional_visibility,
+ default=None, disabled_default=None, conditional_controls=conditional_controls,
disable=None, gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=False)
def __get__(self, obj, obj_type=None) -> None:
@@ -115,7 +115,7 @@ def __set__(self, obj, value: str) -> None:
class SettingInfoBool(SettingInfo):
def __init__(self, gui_text: Optional[str], gui_type: Optional[str], shared: bool, default: Optional[bool] = None,
- disabled_default: Optional[bool] = None, disable: Optional[dict] = None, conditional_visibility: Optional[dict] = None,
+ disabled_default: Optional[bool] = None, disable: Optional[dict] = None, conditional_controls: Optional[dict] = None,
gui_tooltip: Optional[str] = None, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
choices = {
True: 'checked',
@@ -123,7 +123,7 @@ def __init__(self, gui_text: Optional[str], gui_type: Optional[str], shared: boo
}
super().__init__(setting_type=bool, gui_text=gui_text, gui_type=gui_type, shared=shared, choices=choices,
- default=default, disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ default=default, disabled_default=disabled_default, disable=disable, conditional_controls=conditional_controls,
gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
def __get__(self, obj, obj_type=None) -> bool:
@@ -141,10 +141,10 @@ def __set__(self, obj, value: bool) -> None:
class SettingInfoStr(SettingInfo):
def __init__(self, gui_text: Optional[str], gui_type: Optional[str], shared: bool = False,
choices: Optional[dict | list] = None, default: Optional[str] = None,
- disabled_default: Optional[str] = None, disable: Optional[dict] = None, conditional_visibility: Optional[dict] = None,
+ disabled_default: Optional[str] = None, disable: Optional[dict] = None, conditional_controls: Optional[dict] = None,
gui_tooltip: Optional[str] = None, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
super().__init__(setting_type=str, gui_text=gui_text, gui_type=gui_type, shared=shared, choices=choices,
- default=default, disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ default=default, disabled_default=disabled_default, disable=disable, conditional_controls=conditional_controls,
gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
def __get__(self, obj, obj_type=None) -> str:
@@ -162,10 +162,10 @@ def __set__(self, obj, value: str) -> None:
class SettingInfoInt(SettingInfo):
def __init__(self, gui_text: Optional[str], gui_type: Optional[str], shared: bool,
choices: Optional[dict | list] = None, default: Optional[int] = None,
- disabled_default: Optional[int] = None, disable: Optional[dict] = None, conditional_visibility: Optional[dict] = None,
+ disabled_default: Optional[int] = None, disable: Optional[dict] = None, conditional_controls: Optional[dict] = None,
gui_tooltip: Optional[str] = None, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
super().__init__(setting_type=int, gui_text=gui_text, gui_type=gui_type, shared=shared, choices=choices,
- default=default, disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ default=default, disabled_default=disabled_default, disable=disable, conditional_controls=conditional_controls,
gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
def __get__(self, obj, obj_type=None) -> int:
@@ -183,10 +183,10 @@ def __set__(self, obj, value: int) -> None:
class SettingInfoList(SettingInfo):
def __init__(self, gui_text: Optional[str], gui_type: Optional[str], shared: bool,
choices: Optional[dict | list] = None, default: Optional[list] = None,
- disabled_default: Optional[list] = None, disable: Optional[dict] = None, conditional_visibility: Optional[dict] = None,
+ disabled_default: Optional[list] = None, disable: Optional[dict] = None, conditional_controls: Optional[dict] = None,
gui_tooltip: Optional[str] = None, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
super().__init__(setting_type=list, gui_text=gui_text, gui_type=gui_type, shared=shared, choices=choices,
- default=default, disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ default=default, disabled_default=disabled_default, disable=disable, conditional_controls=conditional_controls,
gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
def __get__(self, obj, obj_type=None) -> list:
@@ -204,10 +204,10 @@ def __set__(self, obj, value: list) -> None:
class SettingInfoDict(SettingInfo):
def __init__(self, gui_text: Optional[str], gui_type: Optional[str], shared: bool,
choices: Optional[dict | list] = None, default: Optional[dict] = None,
- disabled_default: Optional[dict] = None, disable: Optional[dict] = None, conditional_visibility: Optional[dict] = None,
+ disabled_default: Optional[dict] = None, disable: Optional[dict] = None, conditional_controls: Optional[dict] = None,
gui_tooltip: Optional[str] = None, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
super().__init__(setting_type=dict, gui_text=gui_text, gui_type=gui_type, shared=shared, choices=choices,
- default=default, disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ default=default, disabled_default=disabled_default, disable=disable, conditional_controls=conditional_controls,
gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
def __get__(self, obj, obj_type=None) -> dict:
@@ -224,83 +224,83 @@ def __set__(self, obj, value: dict) -> None:
class Button(SettingInfoNone):
def __init__(self, gui_text: Optional[str], gui_tooltip: Optional[str] = None,
- gui_params: Optional[dict] = None, conditional_visibility: Optional[dict] = None) -> None:
- super().__init__(gui_text=gui_text, gui_type="Button", gui_tooltip=gui_tooltip, gui_params=gui_params, conditional_visibility=conditional_visibility)
+ gui_params: Optional[dict] = None, conditional_controls: Optional[dict] = None) -> None:
+ super().__init__(gui_text=gui_text, gui_type="Button", gui_tooltip=gui_tooltip, gui_params=gui_params, conditional_controls=conditional_controls)
class Textbox(SettingInfoNone):
def __init__(self, gui_text: Optional[str], gui_tooltip: Optional[str] = None,
- gui_params: Optional[dict] = None, conditional_visibility: Optional[dict] = None) -> None:
- super().__init__(gui_text=gui_text, gui_type="Textbox", gui_tooltip=gui_tooltip, gui_params=gui_params, conditional_visibility=conditional_visibility)
+ gui_params: Optional[dict] = None, conditional_controls: Optional[dict] = None) -> None:
+ super().__init__(gui_text=gui_text, gui_type="Textbox", gui_tooltip=gui_tooltip, gui_params=gui_params, conditional_controls=conditional_controls)
class Checkbutton(SettingInfoBool):
def __init__(self, gui_text: Optional[str], gui_tooltip: Optional[str] = None, disable: Optional[dict] = None,
- disabled_default: Optional[bool] = None, conditional_visibility: Optional[dict] = None, default: bool = False,
+ disabled_default: Optional[bool] = None, conditional_controls: Optional[dict] = None, default: bool = False,
shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False):
super().__init__(gui_text=gui_text, gui_type='Checkbutton', shared=shared, default=default,
- disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ disabled_default=disabled_default, disable=disable, conditional_controls=conditional_controls,
gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
class Combobox(SettingInfoStr):
def __init__(self, gui_text: Optional[str], choices: Optional[dict | list], default: Optional[str],
gui_tooltip: Optional[str] = None, disable: Optional[dict] = None, disabled_default: Optional[str] = None,
- conditional_visibility: Optional[dict] = None, shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
+ conditional_controls: Optional[dict] = None, shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
super().__init__(gui_text=gui_text, gui_type='Combobox', shared=shared, choices=choices, default=default,
- disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ disabled_default=disabled_default, disable=disable, conditional_controls=conditional_controls,
gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
class Radiobutton(SettingInfoStr):
def __init__(self, gui_text: Optional[str], choices: Optional[dict | list], default: Optional[str],
gui_tooltip: Optional[str] = None, disable: Optional[dict] = None, disabled_default: Optional[str] = None,
- conditional_visibility: Optional[dict] = None, shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
+ conditional_controls: Optional[dict] = None, shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
super().__init__(gui_text=gui_text, gui_type='Radiobutton', shared=shared, choices=choices, default=default,
- disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ disabled_default=disabled_default, disable=disable, conditional_controls=conditional_controls,
gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
class Fileinput(SettingInfoStr):
def __init__(self, gui_text: Optional[str], choices: Optional[dict | list] = None, default: Optional[str] = None,
gui_tooltip: Optional[str] = None, disable: Optional[dict] = None, disabled_default: Optional[str] = None,
- conditional_visibility: Optional[dict] = None, shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
+ conditional_controls: Optional[dict] = None, shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
super().__init__(gui_text=gui_text, gui_type='Fileinput', shared=shared, choices=choices, default=default,
- disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ disabled_default=disabled_default, disable=disable, conditional_controls=conditional_controls,
gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
class Directoryinput(SettingInfoStr):
def __init__(self, gui_text: Optional[str], choices: Optional[dict | list] = None, default: Optional[str] = None,
gui_tooltip: Optional[str] = None, disable: Optional[dict] = None, disabled_default: Optional[str] = None,
- conditional_visibility: Optional[dict] = None, shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
+ conditional_controls: Optional[dict] = None, shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
super().__init__(gui_text=gui_text, gui_type='Directoryinput', shared=shared, choices=choices, default=default,
- disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ disabled_default=disabled_default, disable=disable, conditional_controls=conditional_controls,
gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
class Textinput(SettingInfoStr):
def __init__(self, gui_text: Optional[str], choices: Optional[dict | list] = None, default: Optional[str] = None,
gui_tooltip: Optional[str] = None, disable: Optional[dict] = None, disabled_default: Optional[str] = None,
- conditional_visibility: Optional[dict] = None, shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
+ conditional_controls: Optional[dict] = None, shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
super().__init__(gui_text=gui_text, gui_type='Textinput', shared=shared, choices=choices, default=default,
- disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ disabled_default=disabled_default, disable=disable, conditional_controls=conditional_controls,
gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
class ComboboxInt(SettingInfoInt):
def __init__(self, gui_text: Optional[str], choices: Optional[dict | list], default: Optional[int],
gui_tooltip: Optional[str] = None, disable: Optional[dict] = None, disabled_default: Optional[int] = None,
- conditional_visibility: Optional[dict] = None, shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
+ conditional_controls: Optional[dict] = None, shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
super().__init__(gui_text=gui_text, gui_type='Combobox', shared=shared, choices=choices, default=default,
- disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ disabled_default=disabled_default, disable=disable, conditional_controls=conditional_controls,
gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
class Scale(SettingInfoInt):
def __init__(self, gui_text: Optional[str], default: Optional[int], minimum: int, maximum: int, step: int = 1,
gui_tooltip: Optional[str] = None, disable: Optional[dict] = None, disabled_default: Optional[int] = None,
- conditional_visibility: Optional[dict] = None, shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
+ conditional_controls: Optional[dict] = None, shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
choices = {
i: str(i) for i in range(minimum, maximum+1, step)
}
@@ -312,14 +312,14 @@ def __init__(self, gui_text: Optional[str], default: Optional[int], minimum: int
gui_params['step'] = step
super().__init__(gui_text=gui_text, gui_type='Scale', shared=shared, choices=choices, default=default,
- disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ disabled_default=disabled_default, disable=disable, conditional_controls=conditional_controls,
gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
class Numberinput(SettingInfoInt):
def __init__(self, gui_text: Optional[str], default: Optional[int], minimum: Optional[int] = None,
maximum: Optional[int] = None, gui_tooltip: Optional[str] = None, disable: Optional[dict] = None,
- disabled_default: Optional[int] = None, conditional_visibility: Optional[dict] = None,
+ disabled_default: Optional[int] = None, conditional_controls: Optional[dict] = None,
shared: bool = False, gui_params: Optional[dict] = None, cosmetic: bool = False) -> None:
if gui_params is None:
gui_params = {}
@@ -329,25 +329,25 @@ def __init__(self, gui_text: Optional[str], default: Optional[int], minimum: Opt
gui_params['max'] = maximum
super().__init__(gui_text=gui_text, gui_type='Numberinput', shared=shared, choices=None, default=default,
- disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ disabled_default=disabled_default, disable=disable, conditional_controls=conditional_controls,
gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
class MultipleSelect(SettingInfoList):
def __init__(self, gui_text: Optional[str], choices: Optional[dict | list], default: Optional[list],
gui_tooltip: Optional[str] = None, disable: Optional[dict] = None, disabled_default: Optional[list] = None,
- conditional_visibility: Optional[dict] = None, shared: bool = False, gui_params: Optional[dict] = None,
+ conditional_controls: Optional[dict] = None, shared: bool = False, gui_params: Optional[dict] = None,
cosmetic: bool = False) -> None:
super().__init__(gui_text=gui_text, gui_type='MultipleSelect', shared=shared, choices=choices, default=default,
- disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ disabled_default=disabled_default, disable=disable, conditional_controls=conditional_controls,
gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
class SearchBox(SettingInfoList):
def __init__(self, gui_text: Optional[str], choices: Optional[dict | list], default: Optional[list],
gui_tooltip: Optional[str] = None, disable: Optional[dict] = None, disabled_default: Optional[list] = None,
- conditional_visibility: Optional[dict] = None, shared: bool = False, gui_params: Optional[dict] = None,
+ conditional_controls: Optional[dict] = None, shared: bool = False, gui_params: Optional[dict] = None,
cosmetic: bool = False) -> None:
super().__init__(gui_text=gui_text, gui_type='SearchBox', shared=shared, choices=choices, default=default,
- disabled_default=disabled_default, disable=disable, conditional_visibility=conditional_visibility,
+ disabled_default=disabled_default, disable=disable, conditional_controls=conditional_controls,
gui_tooltip=gui_tooltip, gui_params=gui_params, cosmetic=cosmetic)
diff --git a/SettingsToJson.py b/SettingsToJson.py
index c5c0c9bfa..bc318c3bf 100755
--- a/SettingsToJson.py
+++ b/SettingsToJson.py
@@ -15,7 +15,7 @@
setting_keys: list[str] = ['hide_when_disabled', 'min', 'max', 'size', 'max_length', 'file_types', 'no_line_break', 'function', 'option_remove', 'dynamic']
types_with_options: list[str] = ['Checkbutton', 'Radiobutton', 'Combobox', 'SearchBox', 'MultipleSelect']
-conditional_visibility_dependencies: dict[str, dict] = {}
+conditional_control_dependencies: dict[str, dict] = {}
def remove_trailing_lines(text: str) -> str:
while text.endswith('
'):
@@ -54,9 +54,9 @@ def add_disable_option_to_json(disable_option: dict[str, Any], option_json: dict
option_json['controls_visibility_tab'] += ',' + ','.join(disable_option['tabs'])
-def mark_conditional_visibility_option_for_setting(setting_name: str, conditional_settings: dict[str, Any]) -> None:
- if setting_name not in conditional_visibility_dependencies:
- conditional_visibility_dependencies[setting_name] = conditional_settings
+def mark_conditional_control_option_for_setting(setting_name: str, conditional_settings: dict[str, Any]) -> None:
+ if setting_name not in conditional_control_dependencies:
+ conditional_control_dependencies[setting_name] = conditional_settings
def get_setting_json(setting: str, web_version: bool, as_array: bool = False) -> Optional[dict[str, Any]]:
@@ -90,9 +90,9 @@ def get_setting_json(setting: str, web_version: bool, as_array: bool = False) ->
setting_disable = copy.deepcopy(setting_info.disable)
# If this setting can be conditionally enabled, we will need to revisit it once the full JSON has been built
- if setting_info.conditional_visibility is not None:
- mark_conditional_visibility_option_for_setting(setting_info.name, setting_info.conditional_visibility)
- setting_json['conditional_visibility'] = setting_info.conditional_visibility
+ if setting_info.conditional_controls is not None:
+ mark_conditional_control_option_for_setting(setting_info.name, setting_info.conditional_controls)
+ setting_json['conditional_controls'] = setting_info.conditional_controls
version_specific_keys = []
@@ -272,7 +272,7 @@ def create_settings_list_json(path: str, web_version: bool = False) -> None:
output_json['cosmeticsArray'].append(tab_json_array)
# Resolve conditional visibility settings now that the full json is available
- resolve_conditional_visibility_dependencies(output_json)
+ resolve_conditional_control_dependencies(output_json)
for d in hint_dist_files():
with open(d, 'r') as dist_file:
@@ -287,8 +287,8 @@ def create_settings_list_json(path: str, web_version: bool = False) -> None:
json.dump(output_json, f)
-def resolve_conditional_visibility_dependencies(output_json: dict) -> None:
- for dependent_setting_name, dependent_conditions in conditional_visibility_dependencies.items():
+def resolve_conditional_control_dependencies(output_json: dict) -> None:
+ for dependent_setting_name, dependent_conditions in conditional_control_dependencies.items():
for condition_name, condition_details in dependent_conditions.items():
for conditional_settings in condition_details['conditions']:
for setting_name, setting_value in conditional_settings.items():
From 66c919943edd5c79a7680fb63fb6a227c5353fd2 Mon Sep 17 00:00:00 2001
From: Link of Origin
Date: Tue, 8 Apr 2025 13:45:19 -0600
Subject: [PATCH 12/20] Updating naming in architecture file
---
Notes/GUI/architecture.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Notes/GUI/architecture.md b/Notes/GUI/architecture.md
index 36ca59994..e9ea774ca 100644
--- a/Notes/GUI/architecture.md
+++ b/Notes/GUI/architecture.md
@@ -81,7 +81,7 @@ The settings array follows that defines the settings that should appear in this
* controls-visibility-tab → What tab(s) to disable when this setting is enabled, used for Checkbuttons. Multiple tabs can be separated by comma and are addressed by their internal name
* controls-visibility-section → What section(s) to disable when this setting is enabled
* controls-visibility-setting → What specific setting(s) to disable when this setting is enabled
-* conditional-visibility → List of setting/value pairs this setting may be dependent on to determine what its current state should be (eg. disabled, specific value, etc.)
+* conditional-controls → List of setting/value pairs this setting may be dependent on to determine what its current state should be (eg. disabled, specific value, etc.)
* hide-when-disabled → If this setting should be completely hidden when it gets disabled, not just greyed out. Used on the website to make the difference between generator and patcher more distinct
* min → The minimum numeric value allowed. Used for Scales and Numberinputs
* max → The maximum numeric value allowed. Used for Scales and Numberinputs. Can differ between Electron and website (e.g. multi world limit)
From b4bd1ff3ecbc4c8fe8cfd37e964e11416bab76a0 Mon Sep 17 00:00:00 2001
From: Link of Origin
Date: Tue, 8 Apr 2025 14:53:36 -0600
Subject: [PATCH 13/20] Fix logic to actually enforce disable logic over
conditional
---
GUI/src/app/pages/generator/generator.component.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/GUI/src/app/pages/generator/generator.component.ts b/GUI/src/app/pages/generator/generator.component.ts
index 07f68ed16..50c36b2d9 100644
--- a/GUI/src/app/pages/generator/generator.component.ts
+++ b/GUI/src/app/pages/generator/generator.component.ts
@@ -1336,7 +1336,7 @@ export class GeneratorComponent implements OnInit {
// If a setting won't be forcibly disabled, allow conditions to update the setting
for (let settingName in conditionalSettingUpdates) {
- if (settingsDisabled[settingName] == null) {
+ if (!settingsDisabled.includes(settingName)) {
console.log(settingName + " is being updated by conditions! New state = " + JSON.stringify(conditionalSettingUpdates[settingName]));
this.global.generator_settingsMap[settingName] = conditionalSettingUpdates[settingName]['value'];
this.global.generator_settingsVisibilityMap[settingName] = conditionalSettingUpdates[settingName]['enabled'];
From 2b41ead7a06c7402ecd668e699331e491c474948 Mon Sep 17 00:00:00 2001
From: Link of Origin
Date: Tue, 8 Apr 2025 17:03:57 -0600
Subject: [PATCH 14/20] Add a global variable for looking up setting state
New "Settings" global variable that can be accessed via the browser console. Type "Settings." followed by the name of the setting you want to get info for. Allows for fuzzy searching, suggestions, and auto-complete.
---
GUI/src/app/providers/GUIGlobal.ts | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/GUI/src/app/providers/GUIGlobal.ts b/GUI/src/app/providers/GUIGlobal.ts
index a6c82ca20..632647125 100644
--- a/GUI/src/app/providers/GUIGlobal.ts
+++ b/GUI/src/app/providers/GUIGlobal.ts
@@ -469,6 +469,7 @@ export class GUIGlobal implements OnDestroy {
async parseGeneratorGUISettings(guiSettings, userSettings) {
const isRGBHex = /[0-9A-Fa-f]{6}/;
+ globalThis.Settings = {};
//Intialize settings maps
for (let tabIndex = 0; tabIndex < guiSettings.settingsArray.length; tabIndex++) {
@@ -528,6 +529,20 @@ export class GUIGlobal implements OnDestroy {
this.generator_settingsVisibilityMap[setting.name] = true;
+ // Bind a property as a function that returns an object representing this setting for easy debugging
+ // By using the setting name as the property name, it allows for auto-complete and fuzzy searching/suggestions
+ // This works by binding a property to a getter function that has the current 'this' value bound to the function context
+ Object.defineProperty(globalThis.Settings, setting.name, {
+ get: () => {
+ return {
+ // Object representing the current state of this setting. Add more values as you see fit.
+ enabled: this.generator_settingsVisibilityMap[setting.name],
+ value: this.generator_settingsMap[setting.name],
+ _json: this.findSettingByName(setting.name),
+ };
+ },
+ });
+
if (setting.type == "SearchBox" && userSettings && setting.name in userSettings) { //Special parsing for SearchBox data
let valueArray = [];
From 25b59e0379c639ef87b17dc3da891f108d93da80 Mon Sep 17 00:00:00 2001
From: Link of Origin
Date: Wed, 9 Apr 2025 14:09:44 -0600
Subject: [PATCH 15/20] Refactor conditions processing and setting state
---
.../pages/generator/generator.component.ts | 68 ++++++++++---------
SettingsList.py | 26 +++++++
2 files changed, 63 insertions(+), 31 deletions(-)
diff --git a/GUI/src/app/pages/generator/generator.component.ts b/GUI/src/app/pages/generator/generator.component.ts
index 50c36b2d9..bc6d7729f 100644
--- a/GUI/src/app/pages/generator/generator.component.ts
+++ b/GUI/src/app/pages/generator/generator.component.ts
@@ -1083,6 +1083,14 @@ export class GeneratorComponent implements OnInit {
return !this.settingIsEnabled(setting.name) && setting.hide_when_disabled;
}
+ getSettingCurrentState(setting: any) {
+ return {
+ "value": this.global.generator_settingsMap[setting.name],
+ "visible": !this.settingIsFullyHidden(setting),
+ "enabled": this.settingIsEnabled(setting.name),
+ };
+ }
+
getNextVisibleSetting(settings: any, startingIndex: number) {
if (settings.length > startingIndex) {
@@ -1278,30 +1286,7 @@ export class GeneratorComponent implements OnInit {
// Otherwise, the function will return 'true' if a dependent setting's state was altered, otherwise it will return 'false'.
private triggerSettingVisibility(targetSetting: any, targetValue: boolean, triggeredChange: boolean) {
// Resolve logic that could conditionally update this setting.
- let conditionalSettingUpdates = {};
- if (targetSetting["conditionally_controls_setting"] != null) {
- targetSetting["conditionally_controls_setting"].split(",").forEach(setting => {
-
- let dependentSetting = this.global.findSettingByName(setting);
- if (dependentSetting.conditional_controls != null) {
- let targetSettingState = this.getTargetSettingStateFromConditions(dependentSetting);
- let currentSettingState = { // basically doing this twice (once here, once in fn call above), could be cleaner...
- "value": this.global.generator_settingsMap[dependentSetting.name],
- "visible": !this.settingIsFullyHidden(dependentSetting),
- "enabled": this.settingIsEnabled(dependentSetting.name),
- };
-
- // If any part of the setting would change, save the new setting state for later
- if (currentSettingState['value'] != targetSettingState['value'] ||
- currentSettingState['visible'] != targetSettingState['visible'] ||
- currentSettingState['enabled'] != targetSettingState['enabled']
- ) {
- console.log(dependentSetting.name + " setting would be updated by conditions!");
- conditionalSettingUpdates[dependentSetting.name] = targetSettingState;
- }
- }
- });
- }
+ let conditionalSettingUpdates = this.getConditionallyChangedSettingsForOption(targetSetting);
// NOTE: We are treating any setting under "controls_visibility_setting" as one
// that should be disabled by the current option. Could be worth renaming...
@@ -1315,7 +1300,7 @@ export class GeneratorComponent implements OnInit {
let enabledChildren = false;
// We are about to disable this setting.
- // If this is currently enabled, attempt to re-enabled any settings that it
+ // If this is currently enabled, attempt to re-enable any settings that it
// may be disabling on its own. If it's disabled, it shouldn't also disable other settings.
if (targetValue == false && this.settingIsEnabled(setting)) {
enabledChildren = this.clearDeactivationsOfSetting(this.global.findSettingByName(setting));
@@ -1340,7 +1325,7 @@ export class GeneratorComponent implements OnInit {
console.log(settingName + " is being updated by conditions! New state = " + JSON.stringify(conditionalSettingUpdates[settingName]));
this.global.generator_settingsMap[settingName] = conditionalSettingUpdates[settingName]['value'];
this.global.generator_settingsVisibilityMap[settingName] = conditionalSettingUpdates[settingName]['enabled'];
- // TODO: Revisit for "visibility" when/if the "visibility" and "enabled" logic are more decoupled and we have more direct control
+ // TODO: Revisit for "visibility" when/if the "visibility" and "enabled" logic are more decoupled and we have more direct control. (See 'settingIsEnabled' and 'settingIsFullyHidden')
triggeredChange = true;
}
}
@@ -1348,14 +1333,35 @@ export class GeneratorComponent implements OnInit {
return triggeredChange;
}
+ private getConditionallyChangedSettingsForOption(settingOption: any) {
+ let conditionalSettingUpdates = {};
+ if (settingOption["conditionally_controls_setting"] != null) {
+ settingOption["conditionally_controls_setting"].split(",").forEach(setting => {
+
+ let dependentSetting = this.global.findSettingByName(setting);
+ if (dependentSetting.conditional_controls != null) {
+ let targetSettingState = this.getTargetSettingStateFromConditions(dependentSetting);
+ let currentSettingState = this.getSettingCurrentState(dependentSetting);
+
+ // If any part of the setting would change, save the new setting state for later
+ if (currentSettingState['value'] != targetSettingState['value'] ||
+ currentSettingState['visible'] != targetSettingState['visible'] ||
+ currentSettingState['enabled'] != targetSettingState['enabled']
+ ) {
+ console.log(dependentSetting.name + " setting would be updated by conditions!");
+ conditionalSettingUpdates[dependentSetting.name] = targetSettingState;
+ }
+ }
+ });
+ }
+ return conditionalSettingUpdates;
+ }
+
+
private getTargetSettingStateFromConditions(setting: any) {
// Start with the current state as the target
// If no conditions change the target state, then we effectively just return the current state
- let targetSettingState = {
- "value": this.global.generator_settingsMap[setting.name],
- "visible": !this.settingIsFullyHidden(setting),
- "enabled": this.settingIsEnabled(setting.name),
- };
+ let targetSettingState = this.getSettingCurrentState(setting);
// There may be multiple combinations of conditions that may alter this setting.
// We'll check each one, and if one of them passes we'll use that to determine the setting's state.
diff --git a/SettingsList.py b/SettingsList.py
index a4618660f..8326b8756 100644
--- a/SettingsList.py
+++ b/SettingsList.py
@@ -742,6 +742,22 @@ class SettingInfos:
'web:max': 200,
'electron:max': 200,
},
+ conditional_controls = {
+ "show_for_triforce_hunt": {
+ "conditions": [
+ {"triforce_hunt": True},
+ ],
+ "visible": True,
+ "enabled": True,
+ },
+ "disable_when_triforce_hunt_off": {
+ "conditions": [
+ {"triforce_hunt": False},
+ ],
+ "visible": False,
+ "enabled": False,
+ },
+ }
)
triforce_goal_per_world = Scale(
@@ -1456,6 +1472,16 @@ class SettingInfos:
('random', 1),
],
},
+ conditional_controls = {
+ "set_start_age_to_child_for_closed_forest": {
+ "conditions": [
+ {"open_forest": "closed"},
+ ],
+ "value": "child",
+ "visible": True,
+ "enabled": False,
+ }
+ }
)
mq_dungeons_mode = Combobox(
From bfad64957da7b7efcdde91da51d2ddf36d58cee8 Mon Sep 17 00:00:00 2001
From: Link of Origin
Date: Wed, 9 Apr 2025 14:13:21 -0600
Subject: [PATCH 16/20] Light cleanup
---
GUI/src/app/pages/generator/generator.component.ts | 11 ++++-------
1 file changed, 4 insertions(+), 7 deletions(-)
diff --git a/GUI/src/app/pages/generator/generator.component.ts b/GUI/src/app/pages/generator/generator.component.ts
index bc6d7729f..38b2b5b00 100644
--- a/GUI/src/app/pages/generator/generator.component.ts
+++ b/GUI/src/app/pages/generator/generator.component.ts
@@ -1322,10 +1322,9 @@ export class GeneratorComponent implements OnInit {
// If a setting won't be forcibly disabled, allow conditions to update the setting
for (let settingName in conditionalSettingUpdates) {
if (!settingsDisabled.includes(settingName)) {
- console.log(settingName + " is being updated by conditions! New state = " + JSON.stringify(conditionalSettingUpdates[settingName]));
this.global.generator_settingsMap[settingName] = conditionalSettingUpdates[settingName]['value'];
this.global.generator_settingsVisibilityMap[settingName] = conditionalSettingUpdates[settingName]['enabled'];
- // TODO: Revisit for "visibility" when/if the "visibility" and "enabled" logic are more decoupled and we have more direct control. (See 'settingIsEnabled' and 'settingIsFullyHidden')
+ // TODO: Revisit for 'visible' when/if the "visibility" and "enabled" logic are more decoupled and we have more direct control. (See 'settingIsEnabled' and 'settingIsFullyHidden')
triggeredChange = true;
}
}
@@ -1345,10 +1344,9 @@ export class GeneratorComponent implements OnInit {
// If any part of the setting would change, save the new setting state for later
if (currentSettingState['value'] != targetSettingState['value'] ||
- currentSettingState['visible'] != targetSettingState['visible'] ||
- currentSettingState['enabled'] != targetSettingState['enabled']
+ currentSettingState['enabled'] != targetSettingState['enabled'] ||
+ currentSettingState['visible'] != targetSettingState['visible']
) {
- console.log(dependentSetting.name + " setting would be updated by conditions!");
conditionalSettingUpdates[dependentSetting.name] = targetSettingState;
}
}
@@ -1387,15 +1385,14 @@ export class GeneratorComponent implements OnInit {
// If one full condition passed, we'll use that condition's target state
if (!conditionsPassed.includes(false)) {
- console.log(setting.name + " had at least one condition pass!");
// TODO: Define priority rules so we know what should take precedent.
// - Option 1: First that passes has priority => just early exit
// - Option 2: Last that passes has priority => could result in mixed data sets if "condition1" sets some of the state and later "condition 3" sets other parts
// - Option 3: Manually define priority inside the blob => basically option 1 with extra logic. But what if two options have the same priority? First or last wins?
// If the condition sets one of these keys, we'll use that value. Otherwise use the current value.
targetSettingState['value'] = conditionToTest['value'] != null ? conditionToTest['value'] : targetSettingState['value'];
- targetSettingState['visible'] = conditionToTest['visible'] != null ? conditionToTest['visible'] : targetSettingState['visible'];
targetSettingState['enabled'] = conditionToTest['enabled'] != null ? conditionToTest['enabled'] : targetSettingState['enabled'];
+ targetSettingState['visible'] = conditionToTest['visible'] != null ? conditionToTest['visible'] : targetSettingState['visible'];
break; // First condition that passes wins and takes priority
}
}
From ad27b3b2fc8882fb22ae6482c2f7e07e9199b2a7 Mon Sep 17 00:00:00 2001
From: Link of Origin
Date: Wed, 9 Apr 2025 14:16:36 -0600
Subject: [PATCH 17/20] Revert SettingsList testing changes
Sneaky code...
---
SettingsList.py | 26 --------------------------
1 file changed, 26 deletions(-)
diff --git a/SettingsList.py b/SettingsList.py
index 8326b8756..a4618660f 100644
--- a/SettingsList.py
+++ b/SettingsList.py
@@ -742,22 +742,6 @@ class SettingInfos:
'web:max': 200,
'electron:max': 200,
},
- conditional_controls = {
- "show_for_triforce_hunt": {
- "conditions": [
- {"triforce_hunt": True},
- ],
- "visible": True,
- "enabled": True,
- },
- "disable_when_triforce_hunt_off": {
- "conditions": [
- {"triforce_hunt": False},
- ],
- "visible": False,
- "enabled": False,
- },
- }
)
triforce_goal_per_world = Scale(
@@ -1472,16 +1456,6 @@ class SettingInfos:
('random', 1),
],
},
- conditional_controls = {
- "set_start_age_to_child_for_closed_forest": {
- "conditions": [
- {"open_forest": "closed"},
- ],
- "value": "child",
- "visible": True,
- "enabled": False,
- }
- }
)
mq_dungeons_mode = Combobox(
From 945d2c0af6f3d1f01073d857a20150cea2dc9b05 Mon Sep 17 00:00:00 2001
From: Link of Origin
Date: Thu, 12 Jun 2025 08:44:26 -0600
Subject: [PATCH 18/20] Minor refactor and some bug fixes
- Updated the JSON blob that is generated for conditional controls to use a JSON array instead of a CSV (improves readability and maintainability)
- Added check for the old "controls_visibility_setting" to not run the logic if that setting didn't have such rules. This was causing it to error out and stop execution entirely (not sure how I missed this before)
- Added an extra bit of logic to help re-enable a setting if a condition is not actively disabling it when deriving the target state the setting should be in. This fixes an issue where if a setting wasn't explicitly being disabled via the old "disable" rule AND only had conditional rules for disabling it, the setting could get disabled and would never be re-enabled. You could add a workaround to the SettingsList where you just add every option of the dependent setting and explicitly set the enable/visible flags as desired, but that would be very clunky and kind of silly to read.
PS. This also just starts to crack the surface towards breaking the current assumption that anything that could be disabled should be IF some other setting would enable it. That remains true for the old disable logic (and will still override with this change as normal), but now that some of the logic has been separated we can start to see what challenging that assumption may look like and how we'd implement it.
---
.../pages/generator/generator.component.ts | 58 +++++++++++--------
SettingsToJson.py | 8 +--
2 files changed, 39 insertions(+), 27 deletions(-)
diff --git a/GUI/src/app/pages/generator/generator.component.ts b/GUI/src/app/pages/generator/generator.component.ts
index 38b2b5b00..748a5f11a 100644
--- a/GUI/src/app/pages/generator/generator.component.ts
+++ b/GUI/src/app/pages/generator/generator.component.ts
@@ -1291,33 +1291,35 @@ export class GeneratorComponent implements OnInit {
// NOTE: We are treating any setting under "controls_visibility_setting" as one
// that should be disabled by the current option. Could be worth renaming...
let settingsDisabled = []; // Setting names in here are being disabled and take priority over any changes made by conditional logic
- targetSetting["controls_visibility_setting"].split(",").forEach(setting => {
+ if (targetSetting["controls_visibility_setting"] != null) {
+ targetSetting["controls_visibility_setting"].split(",").forEach(setting => {
- //Ignore settings that don't exist in this specific app
- if (!(setting in this.global.generator_settingsVisibilityMap))
- return;
+ //Ignore settings that don't exist in this specific app
+ if (!(setting in this.global.generator_settingsVisibilityMap))
+ return;
- let enabledChildren = false;
+ let enabledChildren = false;
- // We are about to disable this setting.
- // If this is currently enabled, attempt to re-enable any settings that it
- // may be disabling on its own. If it's disabled, it shouldn't also disable other settings.
- if (targetValue == false && this.settingIsEnabled(setting)) {
- enabledChildren = this.clearDeactivationsOfSetting(this.global.findSettingByName(setting));
- settingsDisabled.push(setting);
- }
+ // We are about to disable this setting.
+ // If this is currently enabled, attempt to re-enable any settings that it
+ // may be disabling on its own. If it's disabled, it shouldn't also disable other settings.
+ if (targetValue == false && this.settingIsEnabled(setting)) {
+ enabledChildren = this.clearDeactivationsOfSetting(this.global.findSettingByName(setting));
+ settingsDisabled.push(setting);
+ }
- // We are about to enable this setting.
- // If this setting is currently disabled, note that we are triggering a change.
- // Alternatively, if disabling this setting causes any other settings to be
- // enabled due to it being disabled, then also note that we are triggering a change.
- if ((targetValue == true && !this.settingIsEnabled(setting)) || (enabledChildren)) //Only trigger change if a (sub) setting gets re-enabled
- triggeredChange = true;
+ // We are about to enable this setting.
+ // If this setting is currently disabled, note that we are triggering a change.
+ // Alternatively, if disabling this setting causes any other settings to be
+ // enabled due to it being disabled, then also note that we are triggering a change.
+ if ((targetValue == true && !this.settingIsEnabled(setting)) || (enabledChildren)) //Only trigger change if a (sub) setting gets re-enabled
+ triggeredChange = true;
- // targetValue = true => This setting will be enabled.
- // targetValue = false => This setting will be disabled.
- this.global.generator_settingsVisibilityMap[setting] = targetValue;
- });
+ // targetValue = true => This setting will be enabled.
+ // targetValue = false => This setting will be disabled.
+ this.global.generator_settingsVisibilityMap[setting] = targetValue;
+ });
+ }
// If a setting won't be forcibly disabled, allow conditions to update the setting
for (let settingName in conditionalSettingUpdates) {
@@ -1335,7 +1337,7 @@ export class GeneratorComponent implements OnInit {
private getConditionallyChangedSettingsForOption(settingOption: any) {
let conditionalSettingUpdates = {};
if (settingOption["conditionally_controls_setting"] != null) {
- settingOption["conditionally_controls_setting"].split(",").forEach(setting => {
+ settingOption["conditionally_controls_setting"].forEach(setting => {
let dependentSetting = this.global.findSettingByName(setting);
if (dependentSetting.conditional_controls != null) {
@@ -1364,6 +1366,7 @@ export class GeneratorComponent implements OnInit {
// There may be multiple combinations of conditions that may alter this setting.
// We'll check each one, and if one of them passes we'll use that to determine the setting's state.
let settingConditions = setting.conditional_controls;
+ let conditionHasDisabled = false;
for (let conditionName in settingConditions) {
var conditionToTest = settingConditions[conditionName];
let conditionList = conditionToTest['conditions'];
@@ -1393,10 +1396,19 @@ export class GeneratorComponent implements OnInit {
targetSettingState['value'] = conditionToTest['value'] != null ? conditionToTest['value'] : targetSettingState['value'];
targetSettingState['enabled'] = conditionToTest['enabled'] != null ? conditionToTest['enabled'] : targetSettingState['enabled'];
targetSettingState['visible'] = conditionToTest['visible'] != null ? conditionToTest['visible'] : targetSettingState['visible'];
+ if (targetSettingState['enabled'] == false) {
+ conditionHasDisabled = true;
+ }
break; // First condition that passes wins and takes priority
}
}
+ // The setting is currently disabled, but no conditions are attempting to disable it.
+ // Let's re-enable it and the old "disable" logic can take priority if needed.
+ if (!conditionHasDisabled && targetSettingState['enabled'] == false) {
+ targetSettingState['enabled'] = true;
+ }
+
return targetSettingState;
}
diff --git a/SettingsToJson.py b/SettingsToJson.py
index bc318c3bf..e6423beca 100755
--- a/SettingsToJson.py
+++ b/SettingsToJson.py
@@ -298,9 +298,9 @@ def resolve_conditional_control_dependencies(output_json: dict) -> None:
setting_obj_option_json = setting_obj_json['options'][setting_value]
if setting_obj_option_json is not None:
if 'conditionally_controls_setting' not in setting_obj_option_json:
- setting_obj_option_json['conditionally_controls_setting'] = dependent_setting_name
+ setting_obj_option_json['conditionally_controls_setting'] = [dependent_setting_name]
elif dependent_setting_name not in setting_obj_option_json['conditionally_controls_setting']:
- setting_obj_option_json['conditionally_controls_setting'] += ',' + dependent_setting_name
+ setting_obj_option_json['conditionally_controls_setting'].append(dependent_setting_name)
# Handle the setting in the "array" portion of the json
setting_array_json = find_setting_array_json_in_output(setting_name, output_json)
@@ -308,9 +308,9 @@ def resolve_conditional_control_dependencies(output_json: dict) -> None:
for setting_array_option_json in setting_array_json['options']:
if setting_array_option_json['name'] is setting_value:
if 'conditionally_controls_setting' not in setting_array_option_json:
- setting_array_option_json['conditionally_controls_setting'] = dependent_setting_name
+ setting_array_option_json['conditionally_controls_setting'] = [dependent_setting_name]
elif dependent_setting_name not in setting_array_option_json['conditionally_controls_setting']:
- setting_array_option_json['conditionally_controls_setting'] += ',' + dependent_setting_name
+ setting_array_option_json['conditionally_controls_setting'].append(dependent_setting_name)
break # bail early(...bad idea?)
From 44c292bd929161a38a85fd78f47f3d66450ccf1c Mon Sep 17 00:00:00 2001
From: Link of Origin
Date: Thu, 12 Jun 2025 11:54:19 -0600
Subject: [PATCH 19/20] Updating architecture notes to reflect new Conditional
Controls
---
Notes/GUI/architecture.md | 20 +++++++++++++++++---
1 file changed, 17 insertions(+), 3 deletions(-)
diff --git a/Notes/GUI/architecture.md b/Notes/GUI/architecture.md
index e9ea774ca..c4d20b664 100644
--- a/Notes/GUI/architecture.md
+++ b/Notes/GUI/architecture.md
@@ -46,9 +46,23 @@ utils/settings_list.json contains every single setting of the GUI as an array of
1. name: Internal name of the option value
2. text: Name of the option as shown to the user
3. tooltip: The tooltip shown to the user when he hovers over the option. This is only supported by SearchBox at the moment. Line breaks are again added by a `
` tag
- 4. controls-visibility-tab: What tab(s) to disable when this option is selected. Multiple tabs can be separated by comma and are addressed by their internal name (see mapping.json structure)
- 5. controls-visibility-section: What section(s) to disable when this option is selected
- 6. controls-visibility-setting: What specific setting(s) to disable when this option is selected
+ 4. controls_visibility_tab: What tab(s) to disable when this option is selected. Multiple tabs can be separated by comma and are addressed by their internal name (see mapping.json structure)
+ 5. controls_visibility_section: What section(s) to disable when this option is selected
+ 6. controls_visibility_setting: What specific setting(s) to disable when this option is selected
+* conditional_controls → A list of "conditions" that can alter this setting's state. This allows other settings to alter this setting by altering its visibility, enabled state, and value.
+ 1. A condition is written as an object with a key to an inner object. The key has no functional purpose and is purely for human reabaility and debugging purposes.
+ 2. Conditions have 4 keys:
+ * value: The value the setting should be changed to if the condition passes.
+ * enabled: `True` will enable the setting if the condition passes, and `False` will disable the setting.
+ * visible: `True` will hide the setting from the UI if the condition passes, and `False` will show the setting.
+ * conditions: A list of "partial conditions" that determine if this condition passes or not.
+ 3. Partial conditions are listed as an array of objects representing other settings.
+ * Each object is a list of key -> value pairs of "setting_name" -> "setting_value".
+ * If at least one of these pairs matches the current state of the settings, the partial condition will pass. Otherwise it will fail. (This provides `OR` logic)
+ 4. All "partial conditions" must pass for the full condition to also pass. (This provides `AND` logic
+ 5. If the full condition passes, the setting will be updated to reflect the given value, enabled, and visible states. All of these are optional, any that are excluded will remain unchanged from the current setting state when the condition passes.
+ 6. A setting can have multiple conditions as you see fit. The first condition that passes will take priority in altering the setting and the following conditions will not be evaluated.
+ 7. If a setting would be disabled by some other logic (eg. 'controls_visibility_setting'), that will take priority over ALL condition-based logic. When this happens, the setting state will not be changed even if a passing condition would normally do so.
### The settings_mapping.json structure
From edf61bcb4724bb5d8fd57b3f34dd4a918c4b30e4 Mon Sep 17 00:00:00 2001
From: Link of Origin
Date: Thu, 12 Jun 2025 15:04:51 -0600
Subject: [PATCH 20/20] Cleanup and improvements to the docs
---
Notes/GUI/architecture.md | 27 ++++++++++-----------------
1 file changed, 10 insertions(+), 17 deletions(-)
diff --git a/Notes/GUI/architecture.md b/Notes/GUI/architecture.md
index c4d20b664..1c603691c 100644
--- a/Notes/GUI/architecture.md
+++ b/Notes/GUI/architecture.md
@@ -46,23 +46,16 @@ utils/settings_list.json contains every single setting of the GUI as an array of
1. name: Internal name of the option value
2. text: Name of the option as shown to the user
3. tooltip: The tooltip shown to the user when he hovers over the option. This is only supported by SearchBox at the moment. Line breaks are again added by a `
` tag
- 4. controls_visibility_tab: What tab(s) to disable when this option is selected. Multiple tabs can be separated by comma and are addressed by their internal name (see mapping.json structure)
- 5. controls_visibility_section: What section(s) to disable when this option is selected
- 6. controls_visibility_setting: What specific setting(s) to disable when this option is selected
-* conditional_controls → A list of "conditions" that can alter this setting's state. This allows other settings to alter this setting by altering its visibility, enabled state, and value.
- 1. A condition is written as an object with a key to an inner object. The key has no functional purpose and is purely for human reabaility and debugging purposes.
- 2. Conditions have 4 keys:
- * value: The value the setting should be changed to if the condition passes.
- * enabled: `True` will enable the setting if the condition passes, and `False` will disable the setting.
- * visible: `True` will hide the setting from the UI if the condition passes, and `False` will show the setting.
- * conditions: A list of "partial conditions" that determine if this condition passes or not.
- 3. Partial conditions are listed as an array of objects representing other settings.
- * Each object is a list of key -> value pairs of "setting_name" -> "setting_value".
- * If at least one of these pairs matches the current state of the settings, the partial condition will pass. Otherwise it will fail. (This provides `OR` logic)
- 4. All "partial conditions" must pass for the full condition to also pass. (This provides `AND` logic
- 5. If the full condition passes, the setting will be updated to reflect the given value, enabled, and visible states. All of these are optional, any that are excluded will remain unchanged from the current setting state when the condition passes.
- 6. A setting can have multiple conditions as you see fit. The first condition that passes will take priority in altering the setting and the following conditions will not be evaluated.
- 7. If a setting would be disabled by some other logic (eg. 'controls_visibility_setting'), that will take priority over ALL condition-based logic. When this happens, the setting state will not be changed even if a passing condition would normally do so.
+ 4. controls-visibility-tab: What tab(s) to disable when this option is selected. Multiple tabs can be separated by comma and are addressed by their internal name (see mapping.json structure)
+ 5. controls-visibility-section: What section(s) to disable when this option is selected
+ 6. controls-visibility-setting: What specific setting(s) to disable when this option is selected
+* conditional-controls → An object of "conditions" that can alter this setting's visibility, enabled state, and value based on the state of other settings. This object can contain multiple items that each define their own conditions and target state for the setting. The first condition that passes will take priority in altering the setting and the other conditions will not be evaluated. If this setting would be disabled by some other logic (eg. 'controls_visibility_setting'), that will take priority over ALL condition-based logic. When this happens, the setting state will not be changed even if a passing condition would normally do so. The format of a condition object is as follows:
+ * "key" -> {object}: "key" has no functional purpose and is purely for human readability and debugging purposes. "object" contains key/value pairs that define the behavior of the condition.
+ * value: If the condition passes, the setting will be changed to this value.
+ * enabled: If the condition passes, `True` will enable the setting and `False` will disable it.
+ * visible: If the condition passes, `True` will display the setting in the UI if the condition passes and `False` will hide it.
+ * conditions: A list of "partial condition" objects that determine if this condition passes or not. All "partial conditions" must pass for the full condition to also pass. (This provides `AND` logic)
+ * Each partial condition contains "key" -> "value" pairs in the format of "setting_name" -> "setting_value". If at least one of these pairs matches the current state of the settings, the partial condition will pass. Otherwise it will fail. (This provides `OR` logic)
### The settings_mapping.json structure