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)
11
12
13func configure(layout, settings = null, ui_flow = null, options:Dictionary = {}) -> void:
14 layout_manager = layout
15 settings_manager = settings
16 ui_flow_manager = ui_flow
17 default_size = _coerce_size(options.get("size", default_size), default_size)
18
19
20
21func show_popup(parent:Node, stats_manager, filters:Dictionary = {}, options:Dictionary = {}) -> PopupPanel:
22 var popup = PopupPanel.new()
23 popup.name = str(options.get("name", "AchievementsOverlay"))
24 var popup_size = _coerce_size(options.get("size", _popup_size(parent, options)), default_size)
25 popup.size = popup_size
26 popup.min_size = _coerce_size(options.get("minimum", Vector2i(280, 220)), Vector2i(280, 220))
27 var margin := int(options.get("margin", 14))
28 var content_options := options.duplicate(true)
29 content_options["popup_owner"] = popup
30 var content = _build_content(stats_manager, filters, content_options)
31 content.set_anchors_preset(Control.PRESET_FULL_RECT)
32 content.offset_left = margin
33 content.offset_top = margin
34 content.offset_right = -margin
35 content.offset_bottom = -margin
36 popup.add_child(content)
37 if parent != null:
38 parent.add_child(popup)
39 if layout_manager != null:
40 layout_manager.apply_theme(popup, str(_settings_value("theme", "dark")))
41 if settings_manager != null and settings_manager.has_method("apply_text_size"):
42 settings_manager.apply_text_size(popup)
43 _fit_content(popup, popup_size, margin)
44 if bool(options.get("auto_popup", true)) and popup.is_inside_tree():
45 popup_centered(popup, _parent_viewport_size(parent), {
46 "size": popup_size,
47 "minimum": popup.min_size,
48 "content_margin": margin
49 })
50 call_deferred("_focus_popup", popup)
51 return popup
52
53
54
57func render_page(parent:Node, stats_manager, filters:Dictionary = {}, options:Dictionary = {}) -> VBoxContainer:
58 var content = _build_content(stats_manager, filters, options)
59 content.name = str(options.get("name", "AchievementsPage"))
60 if parent != null:
61 parent.add_child(content)
62 if layout_manager != null and parent != null:
63 layout_manager.apply_theme(parent, str(_settings_value("theme", "dark")))
64 if settings_manager != null and settings_manager.has_method("apply_text_size") and parent != null:
65 settings_manager.apply_text_size(parent)
66 call_deferred("_focus_popup", content)
67 return content
68
69
70
71func update_popup(popup:PopupPanel, stats_manager, filters:Dictionary = {}, options:Dictionary = {}) -> void:
72 if popup == null or not is_instance_valid(popup):
73 return
74 var margin := int(options.get("margin", 14))
75 for child in popup.get_children():
76 popup.remove_child(child)
77 child.queue_free()
78 var content_options := options.duplicate(true)
79 content_options["popup_owner"] = popup
80 var content = _build_content(stats_manager, filters, content_options)
81 content.set_anchors_preset(Control.PRESET_FULL_RECT)
82 content.offset_left = margin
83 content.offset_top = margin
84 content.offset_right = -margin
85 content.offset_bottom = -margin
86 popup.add_child(content)
87 if layout_manager != null:
88 layout_manager.apply_theme(popup, str(_settings_value("theme", "dark")))
89 if settings_manager != null and settings_manager.has_method("apply_text_size"):
90 settings_manager.apply_text_size(popup)
91 _fit_content(popup, _coerce_size(options.get("size", popup.size), popup.size), margin)
92 call_deferred("_focus_popup", popup)
93
94
95
97func popup_centered(popup:PopupPanel, viewport_size:Vector2, options:Dictionary = {}) -> void:
98 if popup == null or not is_instance_valid(popup):
99 return
100 var popup_size:Vector2i
101 if layout_manager != null and layout_manager.has_method("fit_popup_to_viewport"):
102 popup_size = layout_manager.fit_popup_to_viewport(popup, viewport_size, options)
103 else:
104 popup_size = _fallback_fit_popup(popup, viewport_size, options)
105 popup.size = popup_size
106 _fit_content(popup, popup_size, int(options.get("content_margin", options.get("margin", 14))))
107 if popup.is_inside_tree():
108 popup.popup_centered(popup_size)
109
110
111func _focus_popup(popup:Node) -> void:
112 if is_instance_valid(popup) and popup.is_inside_tree() and ui_flow_manager != null and ui_flow_manager.has_method("focus_first"):
113 ui_flow_manager.focus_first(popup)
114
115
116func _build_content(stats_manager, filters:Dictionary, options:Dictionary) -> VBoxContainer:
117 var content = VBoxContainer.new()
118 content.name = "Content"
119 content.size_flags_horizontal = Control.SIZE_EXPAND_FILL
120 content.size_flags_vertical = Control.SIZE_EXPAND_FILL
121 content.add_theme_constant_override("separation", int(options.get("separation", 8)))
122
123 var title = Label.new()
124 title.name = "Title"
125 title.text = str(options.get("title", "Achievements"))
126 title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
127 title.add_theme_font_size_override("font_size", int(options.get("title_size", 20)))
128 content.add_child(title)
129
130 if options.has("filter_options") and layout_manager != null:
131 var filter_options:Dictionary = options.get("filter_options", {})
132 var filter_callback:Callable = options.get("filter_callback", Callable())
133 var filter_bar = layout_manager.create_achievement_filter_bar(filter_options, filters, filter_callback)
134 filter_bar.name = "Filters"
135 content.add_child(filter_bar)
136
137 if layout_manager != null and stats_manager != null:
138 var collection_options:Dictionary = options.get("collection_options", {})
139 var collection = layout_manager.create_achievement_collection(stats_manager.get_achievement_collection(filters), collection_options)
140 collection.name = "Collection"
141 collection.custom_minimum_size = Vector2(0, 0)
142 collection.size_flags_horizontal = Control.SIZE_EXPAND_FILL
143 collection.size_flags_vertical = Control.SIZE_EXPAND_FILL
144 content.add_child(collection)
145
146 var back = Button.new()
147 back.name = "Back"
148 back.text = str(options.get("back_label", "Back"))
149 back.custom_minimum_size = Vector2(0, int(options.get("button_height", 44)))
150 back.size_flags_horizontal = Control.SIZE_EXPAND_FILL
151 var back_callback:Callable = options.get("back_callback", Callable())
152 back.pressed.connect(func():
153 if options.has("popup_owner") and options["popup_owner"] is PopupPanel:
154 options["popup_owner"].hide()
155 if back_callback.is_valid():
156 back_callback.call()
157 )
158 content.add_child(back)
159 return content
160
161
162func _popup_size(parent:Node, options:Dictionary) -> Vector2i:
163 var requested = _coerce_size(options.get("size", default_size), default_size)
164 var viewport_size := _parent_viewport_size(parent)
165 if viewport_size != Vector2.ZERO:
166 var available_width:int = max(260, int(viewport_size.x) - int(options.get("viewport_margin_x", 24)))
167 var available_height:int = max(260, int(viewport_size.y) - int(options.get("viewport_margin_y", 24)))
168 return Vector2i(
169 min(requested.x, available_width),
170 min(requested.y, available_height)
171 )
172 return requested
173
174
175func _parent_viewport_size(parent:Node) -> Vector2:
176 if parent is Control and parent.is_inside_tree():
177 return parent.get_viewport_rect().size
178 if parent != null and parent.is_inside_tree():
179 var viewport := parent.get_viewport()
180 if viewport != null:
181 return viewport.get_visible_rect().size
182 return Vector2.ZERO
183
184
185func _fallback_fit_popup(popup:PopupPanel, viewport_size:Vector2, options:Dictionary) -> Vector2i:
186 var requested = _coerce_size(options.get("size", popup.size), default_size)
187 var minimum = _coerce_size(options.get("minimum", popup.min_size), Vector2i(260, 180))
188 var margin := int(options.get("margin", 24))
189 var available = Vector2i(max(1, int(viewport_size.x) - margin), max(1, int(viewport_size.y) - margin))
190 var effective_min = Vector2i(min(minimum.x, available.x), min(minimum.y, available.y))
191 return Vector2i(
192 clamp(requested.x, effective_min.x, available.x),
193 clamp(requested.y, effective_min.y, available.y)
194 )
195
196
197func _coerce_size(value, fallback:Vector2i) -> Vector2i:
198 if value is Vector2i:
199 return value
200 if value is Vector2:
201 return Vector2i(int(value.x), int(value.y))
202 if value is Dictionary:
203 return Vector2i(int(value.get("x", fallback.x)), int(value.get("y", fallback.y)))
204 return fallback
205
206
207func _fit_content(popup:PopupPanel, popup_size:Vector2i, margin:int) -> void:
208 if popup == null or not is_instance_valid(popup):
209 return
210 var content = popup.get_node_or_null("Content")
211 if content is Control:
212 content.set_anchors_preset(Control.PRESET_FULL_RECT)
213 content.offset_left = margin
214 content.offset_top = margin
215 content.offset_right = -margin
216 content.offset_bottom = -margin
217 var collection = popup.get_node_or_null("Content/Collection")
218 if collection is Control:
219 collection.custom_minimum_size = Vector2(0, 0)
220 collection.size_flags_vertical = Control.SIZE_EXPAND_FILL
221
222
223func _settings_value(key:String, default_value):
224 if settings_manager != null and settings_manager.has_method("get_settings"):
225 return settings_manager.get_settings().get(key, default_value)
226 return default_value