RapidGameFramework
Reusable Godot managers for data-driven small games
Loading...
Searching...
No Matches
game_settings_panel.gd
Go to the documentation of this file.
1extends RefCounted
2
3
10
11const DataCacheManager = preload("res://scripts/systems/dataCacheManager/data_cache_manager.gd")
12
13var data_cache_manager = DataCacheManager.new()
14
15
16
17func load_definition(file_path:String) -> Dictionary:
18 return normalize_definition(data_cache_manager.load_json(file_path))
19
20
21
22func normalize_definition(data) -> Dictionary:
23 return data.duplicate(true) if data is Dictionary else {}
24
25
26
27func get_cache_stats() -> Dictionary:
28 return data_cache_manager.get_cache_stats()
29
30
31
32func get_default_state(definition:Dictionary) -> Dictionary:
33 var state := {}
34 for setting in definition.get("settings", []):
35 if setting is Dictionary:
36 var id = str(setting.get("id", ""))
37 if id != "":
38 state[id] = setting.get("default")
39 return state
40
41
42
43func create(definition:Dictionary, state:Dictionary = {}, changed_callback:Callable = Callable()) -> Dictionary:
44 var root = VBoxContainer.new()
45 root.name = str(definition.get("name", "GameSettingsPanel"))
46 root.size_flags_horizontal = Control.SIZE_EXPAND_FILL
47 root.add_theme_constant_override("separation", int(definition.get("separation", 8)))
48 var controls := {}
49 var title_text = str(definition.get("title", "Game Settings"))
50 if title_text != "":
51 var title = Label.new()
52 title.name = "Title"
53 title.text = title_text
54 title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
55 root.add_child(title)
56 for setting in definition.get("settings", []):
57 if not setting is Dictionary:
58 continue
59 var id = str(setting.get("id", ""))
60 if id == "":
61 continue
62 var row = _build_row(setting, state.get(id, setting.get("default")), changed_callback)
63 root.add_child(row.get("row"))
64 controls[id] = row.get("control")
65 return {"root": root, "controls": controls}
66
67
68
69func read_state(controls:Dictionary) -> Dictionary:
70 var state := {}
71 for id in controls.keys():
72 var control = controls[id]
73 if control is CheckBox:
74 state[str(id)] = control.button_pressed
75 elif control is HSlider:
76 state[str(id)] = control.value
77 elif control is SpinBox:
78 state[str(id)] = control.value
79 elif control is OptionButton:
80 state[str(id)] = control.get_item_metadata(control.selected) if control.selected >= 0 else ""
81 elif control is LineEdit:
82 state[str(id)] = control.text
83 return state
84
85
86
87func apply_state(controls:Dictionary, state:Dictionary) -> void:
88 for id in state.keys():
89 if not controls.has(id):
90 continue
91 var control = controls[id]
92 var value = state[id]
93 if control is CheckBox:
94 control.button_pressed = bool(value)
95 elif control is HSlider:
96 control.value = float(value)
97 elif control is SpinBox:
98 control.value = float(value)
99 elif control is OptionButton:
100 _select_option_by_value(control, value)
101 elif control is LineEdit:
102 control.text = str(value)
103
104
105func _build_row(setting:Dictionary, value, changed_callback:Callable) -> Dictionary:
106 var row = HBoxContainer.new()
107 row.name = str(setting.get("row_name", "%sRow" % _node_name(str(setting.get("id", "Setting")))))
108 row.size_flags_horizontal = Control.SIZE_EXPAND_FILL
109 row.add_theme_constant_override("separation", 8)
110 var label = Label.new()
111 label.name = "Label"
112 label.text = str(setting.get("label", setting.get("id", "")))
113 label.custom_minimum_size = Vector2(float(setting.get("label_width", 132.0)), 0.0)
114 label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
115 row.add_child(label)
116 var value_label:Label = null
117 if bool(setting.get("show_value", false)):
118 value_label = Label.new()
119 value_label.name = "Value"
120 value_label.custom_minimum_size = Vector2(float(setting.get("value_width", 64.0)), 0.0)
121 value_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
122 value_label.text = _format_value(setting, value if value != null else setting.get("default"))
123 var control = _make_control(setting, value, changed_callback, value_label)
124 row.add_child(control)
125 if value_label != null:
126 row.add_child(value_label)
127 return {"row": row, "control": control}
128
129
130func _make_control(setting:Dictionary, value, changed_callback:Callable, value_label:Label = null) -> Control:
131 var type = str(setting.get("type", "checkbox"))
132 var id = str(setting.get("id", "setting"))
133 match type:
134 "slider":
135 var slider = HSlider.new()
136 slider.name = str(setting.get("control_name", _node_name(id)))
137 slider.min_value = float(setting.get("min", 0.0))
138 slider.max_value = float(setting.get("max", 1.0))
139 slider.step = float(setting.get("step", 0.01))
140 slider.value = float(value if value != null else setting.get("default", slider.min_value))
141 slider.size_flags_horizontal = Control.SIZE_EXPAND_FILL
142 slider.value_changed.connect(func(next_value):
143 if value_label != null:
144 value_label.text = _format_value(setting, next_value)
145 _emit_change(changed_callback, id, next_value)
146 )
147 return slider
148 "number":
149 var spin = SpinBox.new()
150 spin.name = str(setting.get("control_name", _node_name(id)))
151 spin.min_value = float(setting.get("min", 0.0))
152 spin.max_value = float(setting.get("max", 100.0))
153 spin.step = float(setting.get("step", 1.0))
154 spin.value = float(value if value != null else setting.get("default", spin.min_value))
155 spin.size_flags_horizontal = Control.SIZE_EXPAND_FILL
156 spin.value_changed.connect(func(next_value):
157 if value_label != null:
158 value_label.text = _format_value(setting, next_value)
159 _emit_change(changed_callback, id, next_value)
160 )
161 return spin
162 "option":
163 var option = OptionButton.new()
164 option.name = str(setting.get("control_name", _node_name(id)))
165 option.size_flags_horizontal = Control.SIZE_EXPAND_FILL
166 for entry in setting.get("options", []):
167 if entry is Dictionary:
168 option.add_item(str(entry.get("label", entry.get("value", ""))))
169 option.set_item_metadata(option.item_count - 1, entry.get("value", entry.get("id", "")))
170 else:
171 option.add_item(str(entry))
172 option.set_item_metadata(option.item_count - 1, str(entry))
173 _select_option_by_value(option, value if value != null else setting.get("default", ""))
174 option.item_selected.connect(func(_index): _emit_change(changed_callback, id, option.get_item_metadata(option.selected)))
175 return option
176 "text":
177 var edit = LineEdit.new()
178 edit.name = str(setting.get("control_name", _node_name(id)))
179 edit.text = str(value if value != null else setting.get("default", ""))
180 edit.size_flags_horizontal = Control.SIZE_EXPAND_FILL
181 edit.text_changed.connect(func(next_text): _emit_change(changed_callback, id, next_text))
182 return edit
183 _:
184 var check = CheckBox.new()
185 check.name = str(setting.get("control_name", _node_name(id)))
186 check.text = str(setting.get("checkbox_text", ""))
187 check.button_pressed = bool(value if value != null else setting.get("default", false))
188 check.size_flags_horizontal = Control.SIZE_EXPAND_FILL
189 check.toggled.connect(func(next_value): _emit_change(changed_callback, id, next_value))
190 return check
191
192
193func _select_option_by_value(option:OptionButton, value) -> void:
194 for index in range(option.item_count):
195 if str(option.get_item_metadata(index)) == str(value):
196 option.select(index)
197 return
198 if option.item_count > 0:
199 option.select(0)
200
201
202func _emit_change(callback:Callable, id:String, value) -> void:
203 if callback.is_valid():
204 callback.call(id, value)
205
206
207func _format_value(setting:Dictionary, value) -> String:
208 match str(setting.get("format", "")):
209 "percent":
210 return "%d%%" % int(float(value) * 100.0)
211 "seconds":
212 return "%.1fs" % float(value)
213 "multiplier":
214 return "%.2fx" % float(value)
215 "integer":
216 return "%d" % int(float(value))
217 var suffix := str(setting.get("suffix", ""))
218 if suffix != "":
219 return "%s%s" % [str(value), suffix]
220 return str(value)
221
222
223func _node_name(value:String) -> String:
224 var parts = value.replace("-", "_").split("_")
225 var text := ""
226 for part in parts:
227 text += str(part).capitalize()
228 return text if text != "" else "Setting"