RapidGameFramework
Reusable Godot managers for data-driven small games
Loading...
Searching...
No Matches
achievement_screen.gd
Go to the documentation of this file.
1extends RefCounted
2
3
6
7var layout_manager
8var settings_manager
9var ui_flow_manager
10var default_size := Vector2i(340, 500)
11var global_achievement_path := ""
12
13
14func configure(layout, settings = null, ui_flow = null, options:Dictionary = {}) -> void:
15 layout_manager = layout
16 settings_manager = settings
17 ui_flow_manager = ui_flow
18 default_size = _coerce_size(options.get("size", default_size), default_size)
19 global_achievement_path = str(options.get("global_achievement_path", global_achievement_path))
20
21
22
23func show_popup(parent:Node, stats_manager, filters:Dictionary = {}, options:Dictionary = {}) -> PopupPanel:
24 var popup = PopupPanel.new()
25 popup.name = str(options.get("name", "AchievementsOverlay"))
26 var popup_size = _coerce_size(options.get("size", _popup_size(parent, options)), default_size)
27 popup.size = popup_size
28 popup.min_size = _coerce_size(options.get("minimum", Vector2i(280, 220)), Vector2i(280, 220))
29 var margin := int(options.get("margin", 14))
30 var content_options := options.duplicate(true)
31 content_options["popup_owner"] = popup
32 var content = _build_content(stats_manager, filters, content_options)
33 content.set_anchors_preset(Control.PRESET_FULL_RECT)
34 content.offset_left = margin
35 content.offset_top = margin
36 content.offset_right = -margin
37 content.offset_bottom = -margin
38 popup.add_child(content)
39 if parent != null:
40 parent.add_child(popup)
41 if layout_manager != null:
42 layout_manager.apply_theme(popup, str(_settings_value("theme", "dark")))
43 if settings_manager != null and settings_manager.has_method("apply_text_size"):
44 settings_manager.apply_text_size(popup)
45 _fit_content(popup, popup_size, margin)
46 if bool(options.get("auto_popup", true)) and popup.is_inside_tree():
47 popup_centered(popup, _parent_viewport_size(parent), {
48 "size": popup_size,
49 "minimum": popup.min_size,
50 "content_margin": margin
51 })
52 call_deferred("_focus_popup", popup)
53 return popup
54
55
56
59func render_page(parent:Node, stats_manager, filters:Dictionary = {}, options:Dictionary = {}) -> VBoxContainer:
60 var content = _build_content(stats_manager, filters, options)
61 content.name = str(options.get("name", "AchievementsPage"))
62 _fit_page_content(parent, content, options)
63 if parent != null:
64 parent.add_child(content)
65 if layout_manager != null and parent != null:
66 layout_manager.apply_theme(parent, str(_settings_value("theme", "dark")))
67 if settings_manager != null and settings_manager.has_method("apply_text_size") and parent != null:
68 settings_manager.apply_text_size(parent)
69 call_deferred("_focus_popup", content)
70 return content
71
72
73
74func update_popup(popup:PopupPanel, stats_manager, filters:Dictionary = {}, options:Dictionary = {}) -> void:
75 if popup == null or not is_instance_valid(popup):
76 return
77 var margin := int(options.get("margin", 14))
78 for child in popup.get_children():
79 popup.remove_child(child)
80 child.queue_free()
81 var content_options := options.duplicate(true)
82 content_options["popup_owner"] = popup
83 var content = _build_content(stats_manager, filters, content_options)
84 content.set_anchors_preset(Control.PRESET_FULL_RECT)
85 content.offset_left = margin
86 content.offset_top = margin
87 content.offset_right = -margin
88 content.offset_bottom = -margin
89 popup.add_child(content)
90 if layout_manager != null:
91 layout_manager.apply_theme(popup, str(_settings_value("theme", "dark")))
92 if settings_manager != null and settings_manager.has_method("apply_text_size"):
93 settings_manager.apply_text_size(popup)
94 _fit_content(popup, _coerce_size(options.get("size", popup.size), popup.size), margin)
95 call_deferred("_focus_popup", popup)
96
97
98
100func popup_centered(popup:PopupPanel, viewport_size:Vector2, options:Dictionary = {}) -> void:
101 if popup == null or not is_instance_valid(popup):
102 return
103 var popup_size:Vector2i
104 if layout_manager != null and layout_manager.has_method("popup_centered_standard"):
105 layout_manager.popup_centered_standard(popup, viewport_size, options)
106 popup_size = popup.size
107 else:
108 popup_size = _fallback_fit_popup(popup, viewport_size, options)
109 popup.size = popup_size
110 _fit_content(popup, popup_size, int(options.get("content_margin", options.get("margin", 14))))
111 if (layout_manager == null or not layout_manager.has_method("popup_centered_standard")) and popup.is_inside_tree():
112 popup.popup_centered(popup_size)
113
114
115func _focus_popup(popup:Node) -> void:
116 if is_instance_valid(popup) and popup.is_inside_tree() and ui_flow_manager != null and ui_flow_manager.has_method("focus_first"):
117 ui_flow_manager.focus_first(popup)
118
119
120func _build_content(stats_manager, filters:Dictionary, options:Dictionary) -> VBoxContainer:
121 _refresh_global_achievement_state(stats_manager)
122 var content = VBoxContainer.new()
123 content.name = "Content"
124 content.size_flags_horizontal = Control.SIZE_EXPAND_FILL
125 content.size_flags_vertical = Control.SIZE_EXPAND_FILL
126 content.add_theme_constant_override("separation", int(options.get("separation", 8)))
127
128 var title = Label.new()
129 title.name = "Title"
130 title.text = str(options.get("title", "Achievements"))
131 title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
132 title.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
133 title.size_flags_horizontal = Control.SIZE_EXPAND_FILL
134 title.add_theme_font_size_override("font_size", int(options.get("title_size", 20)))
135 content.add_child(title)
136
137 if options.has("filter_options") and layout_manager != null:
138 var filter_options:Dictionary = options.get("filter_options", {})
139 var filter_callback:Callable = options.get("filter_callback", Callable())
140 var filter_bar = layout_manager.create_achievement_filter_bar(filter_options, filters, filter_callback)
141 filter_bar.name = "Filters"
142 content.add_child(filter_bar)
143
144 if layout_manager != null and stats_manager != null:
145 var collection_options:Dictionary = options.get("collection_options", {})
146 var collection = layout_manager.create_achievement_collection(stats_manager.get_achievement_collection(filters), collection_options)
147 collection.name = "Collection"
148 var collection_min_height := float(options.get("collection_min_height", collection_options.get("min_height", 220.0)))
149 collection.custom_minimum_size = Vector2(0, maxf(120.0, collection_min_height))
150 collection.size_flags_horizontal = Control.SIZE_EXPAND_FILL
151 collection.size_flags_vertical = Control.SIZE_EXPAND_FILL
152 content.add_child(collection)
153
154 var back = Button.new()
155 back.name = "Back"
156 back.text = str(options.get("back_label", "Back"))
157 back.custom_minimum_size = Vector2(0, int(options.get("button_height", 44)))
158 back.size_flags_horizontal = Control.SIZE_EXPAND_FILL
159 var back_callback:Callable = options.get("back_callback", Callable())
160 back.pressed.connect(func():
161 if options.has("popup_owner") and options["popup_owner"] is PopupPanel:
162 options["popup_owner"].hide()
163 if back_callback.is_valid():
164 back_callback.call()
165 )
166 content.add_child(back)
167 return content
168
169
170func _refresh_global_achievement_state(stats_manager) -> void:
171 if stats_manager != null and stats_manager.has_method("load_global_achievement_state"):
172 if global_achievement_path != "":
173 stats_manager.load_global_achievement_state(global_achievement_path)
174 else:
175 stats_manager.load_global_achievement_state()
176
177
178func _fit_page_content(parent:Node, content:Control, options:Dictionary) -> void:
179 if content == null:
180 return
181 var viewport_size := _parent_viewport_size(parent)
182 var width := float(options.get("page_width", 0.0))
183 var height := float(options.get("page_height", 0.0))
184 if viewport_size != Vector2.ZERO:
185 var margin_x := float(options.get("page_margin_x", 48.0))
186 var margin_y := float(options.get("page_margin_y", 220.0))
187 width = maxf(width, viewport_size.x - margin_x)
188 height = maxf(height, viewport_size.y - margin_y)
189 content.custom_minimum_size = Vector2(maxf(280.0, width), maxf(320.0, height))
190 content.size_flags_horizontal = Control.SIZE_EXPAND_FILL
191 content.size_flags_vertical = Control.SIZE_EXPAND_FILL
192 var collection = content.get_node_or_null("Collection")
193 if collection is Control:
194 var reserved_height := float(options.get("reserved_height", 156.0))
195 var collection_min := float(options.get("collection_min_height", 0.0))
196 if viewport_size != Vector2.ZERO:
197 collection_min = maxf(collection_min, viewport_size.y - float(options.get("page_margin_y", 220.0)) - reserved_height)
198 collection.custom_minimum_size.y = maxf(180.0, collection_min)
199
200
201func _popup_size(parent:Node, options:Dictionary) -> Vector2i:
202 var requested = _coerce_size(options.get("size", default_size), default_size)
203 var viewport_size := _parent_viewport_size(parent)
204 if viewport_size != Vector2.ZERO:
205 var available_width:int = max(260, int(viewport_size.x) - int(options.get("viewport_margin_x", 24)))
206 var available_height:int = max(260, int(viewport_size.y) - int(options.get("viewport_margin_y", 24)))
207 return Vector2i(
208 min(requested.x, available_width),
209 min(requested.y, available_height)
210 )
211 return requested
212
213
214func _parent_viewport_size(parent:Node) -> Vector2:
215 if parent is Control and parent.is_inside_tree():
216 return parent.get_viewport_rect().size
217 if parent != null and parent.is_inside_tree():
218 var viewport := parent.get_viewport()
219 if viewport != null:
220 return viewport.get_visible_rect().size
221 return Vector2.ZERO
222
223
224func _fallback_fit_popup(popup:PopupPanel, viewport_size:Vector2, options:Dictionary) -> Vector2i:
225 var requested = _coerce_size(options.get("size", popup.size), default_size)
226 var minimum = _coerce_size(options.get("minimum", popup.min_size), Vector2i(260, 180))
227 var margin := int(options.get("margin", 24))
228 var available = Vector2i(max(1, int(viewport_size.x) - margin), max(1, int(viewport_size.y) - margin))
229 var effective_min = Vector2i(min(minimum.x, available.x), min(minimum.y, available.y))
230 return Vector2i(
231 clamp(requested.x, effective_min.x, available.x),
232 clamp(requested.y, effective_min.y, available.y)
233 )
234
235
236func _coerce_size(value, fallback:Vector2i) -> Vector2i:
237 if value is Vector2i:
238 return value
239 if value is Vector2:
240 return Vector2i(int(value.x), int(value.y))
241 if value is Dictionary:
242 return Vector2i(int(value.get("x", fallback.x)), int(value.get("y", fallback.y)))
243 return fallback
244
245
246func _fit_content(popup:PopupPanel, popup_size:Vector2i, margin:int) -> void:
247 if popup == null or not is_instance_valid(popup):
248 return
249 var content = popup.get_node_or_null("Content")
250 if content is Control:
251 content.set_anchors_preset(Control.PRESET_FULL_RECT)
252 content.offset_left = margin
253 content.offset_top = margin
254 content.offset_right = -margin
255 content.offset_bottom = -margin
256 var collection = popup.get_node_or_null("Content/Collection")
257 if collection is Control:
258 collection.custom_minimum_size = Vector2(0, 0)
259 collection.size_flags_vertical = Control.SIZE_EXPAND_FILL
260
261
262func _settings_value(key:String, default_value):
263 if settings_manager != null and settings_manager.has_method("get_settings"):
264 return settings_manager.get_settings().get(key, default_value)
265 return default_value