7var popup_layout_manager = preload(
"res://scripts/systems/layoutManager/layout_manager.gd").new()
11func create(summary:Dictionary, actions:Array = [], options:Dictionary = {}) -> Control:
12 var root = VBoxContainer.new()
13 root.name = str(options.get(
"name",
"ResultsScreen"))
14 root.size_flags_horizontal = Control.SIZE_EXPAND_FILL
15 root.size_flags_vertical = Control.SIZE_EXPAND_FILL
16 root.custom_minimum_size = options.get(
"minimum_size", Vector2.ZERO)
17 root.add_theme_constant_override(
"separation", int(options.get(
"separation", 10)))
18 var title = Label.new()
20 title.text = str(options.get(
"title", _title_for(summary)))
21 title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
22 title.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
23 title.size_flags_horizontal = Control.SIZE_EXPAND_FILL
24 title.add_theme_font_size_override(
"font_size", int(options.get(
"title_size", 22)))
26 var message = Label.new()
27 message.name =
"Message"
28 message.text = str(options.get(
"message", summary.get(
"result", {}).get(
"message",
"")))
29 message.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
30 message.horizontal_alignment = int(options.get(
"message_alignment", HORIZONTAL_ALIGNMENT_LEFT))
31 message.visible = message.text !=
""
32 var details_scroll = ScrollContainer.new()
33 details_scroll.name =
"DetailsScroll"
34 details_scroll.size_flags_horizontal = Control.SIZE_EXPAND_FILL
35 details_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
36 details_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
37 root.add_child(details_scroll)
38 var details = VBoxContainer.new()
39 details.name =
"Details"
40 details.size_flags_horizontal = Control.SIZE_EXPAND_FILL
41 details.add_theme_constant_override(
"separation", int(options.get(
"detail_separation", 8)))
42 details_scroll.add_child(details)
43 details.add_child(message)
44 if bool(options.get(
"show_sections", true)):
45 details.add_child(_section(
"Counters", _format_counts(summary.get(
"counters", {}),
"No counters recorded.")))
46 details.add_child(_section(
"Collected", _format_counts(summary.get(
"collected", {}),
"Nothing collected.")))
47 details.add_child(_section(
"Rewards", _format_counts(summary.get(
"rewards", {}),
"No rewards earned.")))
48 var objective_text =
"%d / %d required objectives" % [
49 int(summary.get(
"completed_required_objectives", 0)),
50 int(summary.get(
"required_objectives", 0))
52 details.add_child(_section(
"Objectives", objective_text))
53 var action_row = HBoxContainer.new()
54 action_row.name =
"Actions"
55 action_row.size_flags_horizontal = Control.SIZE_EXPAND_FILL
56 action_row.add_theme_constant_override(
"separation", 8)
57 var free_on_action :=
bool(options.get(
"free_on_action", false))
58 for action
in actions:
59 if not action
is Dictionary:
61 var button = Button.new()
62 button.name = str(action.get(
"name", _node_name(str(action.get(
"id",
"Action")))))
63 button.text = str(action.get(
"label", action.get(
"id",
"Action")))
64 button.disabled =
bool(action.get(
"disabled", false))
65 button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
66 button.custom_minimum_size =
Vector2(0, int(options.get(
"action_button_height", 44)))
67 var callback = action.get(
"callback", Callable())
68 button.pressed.connect(func():
69 _close_parent_popup(button, free_on_action)
70 if callback
is Callable
and callback.is_valid():
73 action_row.add_child(button)
74 root.add_child(action_row)
79func create_popup(owner:Node, summary:Dictionary, actions:Array = [], options:Dictionary = {}) -> PopupPanel:
80 var popup = PopupPanel.new()
81 popup.name = str(options.get(
"popup_name",
"ResultsPopup"))
83 var content_options = options.duplicate(true)
84 content_options[
"name"] = str(options.get(
"content_name",
"Content"))
85 content_options[
"minimum_size"] = options.get(
"minimum_size", Vector2.ZERO)
86 var content = create(summary, actions, content_options)
87 content.set_anchors_preset(Control.PRESET_FULL_RECT)
88 content.offset_left = int(options.get(
"margin", 18))
89 content.offset_top = int(options.get(
"margin", 18))
90 content.offset_right = -int(options.get(
"margin", 18))
91 content.offset_bottom = -int(options.get(
"margin", 18))
92 content.custom_minimum_size = Vector2.ZERO
93 popup.add_child(content)
95 owner.add_child(popup)
99func popup_centered(popup:PopupPanel, viewport_size:Vector2, options:Dictionary = {}) -> void:
100 if popup == null
or not is_instance_valid(popup):
102 popup_layout_manager.popup_centered_standard(popup, viewport_size, options)
103 var popup_size := popup.size
104 _fit_content(popup, popup_size, int(options.get(
"content_margin", 18)))
108func show_popup(owner:Node, summary:Dictionary, actions:Array = [], options:Dictionary = {}) -> PopupPanel:
109 var popup:PopupPanel = create_popup(owner, summary, actions, options)
110 var viewport_size:Vector2 = _coerce_vector2(options.get(
"viewport_size", Vector2.ZERO), Vector2.ZERO)
111 if viewport_size == Vector2.ZERO
and owner != null
and owner.is_inside_tree():
112 viewport_size = owner.get_viewport_rect().size
113 if viewport_size == Vector2.ZERO:
114 var fallback_size:Vector2i = _coerce_size(options.get(
"size",
Vector2i(360, 320)),
Vector2i(360, 320))
115 viewport_size =
Vector2(float(fallback_size.x), float(fallback_size.y))
116 popup_centered(popup, viewport_size, options)
117 apply_theme_and_focus(
119 options.get(
"layout_manager", null),
120 options.get(
"settings_manager", null),
121 options.get(
"ui_flow_manager", null),
122 str(options.get(
"theme_id",
"dark")),
129func fit_popup_to_viewport(popup:PopupPanel, viewport_size:Vector2, options:Dictionary = {}) -> Vector2i:
130 return popup_layout_manager.fit_popup_to_viewport(popup, viewport_size, options)
133func _fit_content(popup:PopupPanel, popup_size:Vector2i, content_margin:int) -> void:
134 if popup == null
or not is_instance_valid(popup):
136 var content = popup.get_node_or_null(
"Content")
137 if not content
is Control:
139 content.set_anchors_preset(Control.PRESET_FULL_RECT)
140 content.offset_left = content_margin
141 content.offset_top = content_margin
142 content.offset_right = -content_margin
143 content.offset_bottom = -content_margin
144 content.custom_minimum_size = Vector2.ZERO
145 content.size_flags_horizontal = Control.SIZE_EXPAND_FILL
146 content.size_flags_vertical = Control.SIZE_EXPAND_FILL
147 var details_scroll = content.get_node_or_null(
"DetailsScroll")
148 if details_scroll
is Control:
149 details_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
152func apply_theme_and_focus(popup:PopupPanel, layout_manager, settings_manager = null, ui_flow_manager = null, theme_id:String =
"dark", options:Dictionary = {}) -> Control:
153 if popup == null
or not is_instance_valid(popup):
155 if layout_manager
is Object
and layout_manager.has_method(
"apply_theme"):
156 layout_manager.apply_theme(popup, theme_id)
157 if settings_manager
is Object
and settings_manager.has_method(
"apply_text_size"):
158 settings_manager.apply_text_size(popup)
159 if not (ui_flow_manager
is Object):
161 var focused:Control = null
162 if options.has(
"focus_node")
and ui_flow_manager.has_method(
"focus_named"):
163 focused = ui_flow_manager.focus_named(popup, str(options.get(
"focus_node",
"")))
164 elif options.has(
"focus_labels")
and ui_flow_manager.has_method(
"focus_button_by_text"):
165 focused = ui_flow_manager.focus_button_by_text(popup, options.get(
"focus_labels", []))
166 elif ui_flow_manager.has_method(
"focus_first"):
167 focused = ui_flow_manager.focus_first(popup)
169 popup.set_meta(
"focused_node", focused.name)
173func _title_for(summary:Dictionary) -> String:
174 var mode = str(summary.get(
"mode",
"Run")).replace(
"_",
" ").capitalize()
175 var status = str(summary.get(
"status",
"complete")).replace(
"_",
" ").capitalize()
176 return "%s %s" % [mode, status]
179func _section(title_text:String, body_text:String) -> Control:
180 var panel = VBoxContainer.new()
181 panel.name = _node_name(title_text)
182 panel.add_theme_constant_override(
"separation", 2)
183 var title = Label.new()
185 title.text = title_text
186 title.add_theme_font_size_override(
"font_size", 16)
187 panel.add_child(title)
188 var body = Label.new()
190 body.text = body_text
191 body.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
192 panel.add_child(body)
196func _format_counts(counts, empty_label:String) -> String:
197 if not counts
is Dictionary:
200 var keys = counts.keys()
203 var amount = int(counts[key])
206 parts.append(
"%dx %s" % [amount, str(key).replace(
"_",
" ").capitalize()])
207 return empty_label
if parts.is_empty()
else ", ".join(parts)
210func _node_name(value:String) -> String:
211 var parts = value.replace(
"-",
"_").replace(
" ",
"_").split(
"_")
214 text += str(part).capitalize()
215 return text
if text !=
"" else "Node"
218func _coerce_size(value, fallback:Vector2i) -> Vector2i:
219 if value
is Vector2i:
222 return Vector2i(int(value.x), int(value.y))
223 if value
is Dictionary:
224 return Vector2i(int(value.get(
"x", fallback.x)), int(value.get(
"y", fallback.y)))
228func _coerce_vector2(value, fallback:Vector2) -> Vector2:
231 if value
is Vector2i:
232 return Vector2(float(value.x), float(value.y))
233 if value
is Dictionary:
234 return Vector2(float(value.get(
"x", fallback.x)), float(value.get(
"y", fallback.y)))
238func _close_parent_popup(node:Node, should_free:bool = false) -> void:
239 if node == null
or not is_instance_valid(node):
242 while current != null:
243 if current
is PopupPanel:
248 current = current.get_parent()