Migrate pause confirmations to bubble UI
This commit is contained in:
parent
22f7119b72
commit
37a3a0051e
5 changed files with 433 additions and 171 deletions
250
ui/components/bubble_menu/bubble_confirmation_page.gd
Normal file
250
ui/components/bubble_menu/bubble_confirmation_page.gd
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
class_name BubbleConfirmationPage
|
||||
extends Control
|
||||
|
||||
signal confirmed
|
||||
signal cancelled
|
||||
|
||||
enum InitialFocus {
|
||||
CONFIRM,
|
||||
CANCEL,
|
||||
}
|
||||
|
||||
@export var cluster_path: NodePath = ^"BubbleCluster"
|
||||
@export var message_path: NodePath = ^"BubbleCluster/MessageBubble"
|
||||
@export var message_label_path: NodePath = (
|
||||
^"BubbleCluster/MessageBubble/MessageLabel"
|
||||
)
|
||||
@export var confirm_path: NodePath = ^"BubbleCluster/ConfirmButton"
|
||||
@export var confirm_label_path: NodePath = (
|
||||
^"BubbleCluster/ConfirmButton/ConfirmLabel"
|
||||
)
|
||||
@export var cancel_path: NodePath = ^"BubbleCluster/CancelButton"
|
||||
@export var cancel_label_path: NodePath = (
|
||||
^"BubbleCluster/CancelButton/CancelLabel"
|
||||
)
|
||||
@export var maximum_layout_size: Vector2 = Vector2(720.0, 520.0)
|
||||
@export var compact_maximum_layout_size: Vector2 = Vector2(544.0, 400.0)
|
||||
@export var compact_width_threshold: float = 680.0
|
||||
@export var compact_height_threshold: float = 500.0
|
||||
@export_range(0.0, 128.0, 1.0) var transition_safe_margin: float = 24.0
|
||||
|
||||
var _cluster: BubbleCluster
|
||||
var _message: BubbleButton
|
||||
var _message_label: Label
|
||||
var _confirm_button: BubbleButton
|
||||
var _confirm_label: Label
|
||||
var _cancel_button: BubbleButton
|
||||
var _cancel_label: Label
|
||||
var _bubbles: Array[BubbleButton] = []
|
||||
var _initial_focus: InitialFocus = InitialFocus.CONFIRM
|
||||
var _resting_cluster_position: Vector2 = Vector2.ZERO
|
||||
var _transition: Tween
|
||||
var _transition_generation: int = 0
|
||||
var _is_transitioning: bool = false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_cluster = get_node(cluster_path) as BubbleCluster
|
||||
_message = get_node(message_path) as BubbleButton
|
||||
_message_label = get_node(message_label_path) as Label
|
||||
_confirm_button = get_node(confirm_path) as BubbleButton
|
||||
_confirm_label = get_node(confirm_label_path) as Label
|
||||
_cancel_button = get_node(cancel_path) as BubbleButton
|
||||
_cancel_label = get_node(cancel_label_path) as Label
|
||||
_bubbles = [_message, _confirm_button, _cancel_button]
|
||||
_cluster.configure(_bubbles)
|
||||
_message.focus_mode = Control.FOCUS_NONE
|
||||
_message.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_confirm_button.pressed.connect(confirmed.emit)
|
||||
_cancel_button.pressed.connect(cancelled.emit)
|
||||
_configure_focus()
|
||||
resized.connect(_update_layout)
|
||||
call_deferred("_update_layout")
|
||||
hide_page()
|
||||
|
||||
|
||||
func configure(
|
||||
message_text: String,
|
||||
confirm_text: String,
|
||||
cancel_text: String = "cancel",
|
||||
initial_focus: InitialFocus = InitialFocus.CONFIRM,
|
||||
) -> void:
|
||||
_message_label.text = message_text
|
||||
_confirm_label.text = confirm_text
|
||||
_cancel_label.text = cancel_text
|
||||
_initial_focus = initial_focus
|
||||
|
||||
|
||||
func transition_in(duration: float, completed: Callable) -> void:
|
||||
_transition_generation += 1
|
||||
_cancel_transition()
|
||||
show()
|
||||
_is_transitioning = true
|
||||
_set_interactive(false)
|
||||
set_process(true)
|
||||
_cluster.position = Vector2(
|
||||
_resting_cluster_position.x,
|
||||
size.y + transition_safe_margin
|
||||
)
|
||||
var generation: int = _transition_generation
|
||||
_transition = create_tween()
|
||||
_transition.tween_property(
|
||||
_cluster,
|
||||
"position",
|
||||
_resting_cluster_position,
|
||||
duration
|
||||
).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
|
||||
_transition.finished.connect(
|
||||
_finish_transition_in.bind(generation, completed),
|
||||
CONNECT_ONE_SHOT
|
||||
)
|
||||
|
||||
|
||||
func transition_out(duration: float, completed: Callable) -> void:
|
||||
if not visible or _is_transitioning:
|
||||
return
|
||||
_transition_generation += 1
|
||||
_cancel_transition()
|
||||
_is_transitioning = true
|
||||
_set_interactive(false)
|
||||
var generation: int = _transition_generation
|
||||
_transition = create_tween()
|
||||
_transition.tween_property(
|
||||
_cluster,
|
||||
"position:y",
|
||||
-_cluster.size.y - transition_safe_margin,
|
||||
duration
|
||||
).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
|
||||
_transition.finished.connect(
|
||||
_finish_transition_out.bind(generation, completed),
|
||||
CONNECT_ONE_SHOT
|
||||
)
|
||||
|
||||
|
||||
func hide_page() -> void:
|
||||
_transition_generation += 1
|
||||
_cancel_transition()
|
||||
_is_transitioning = false
|
||||
_set_interactive(false)
|
||||
set_process(false)
|
||||
hide()
|
||||
if _cluster != null:
|
||||
_cluster.position = _resting_cluster_position
|
||||
|
||||
|
||||
func lock_interaction() -> void:
|
||||
_set_interactive(false)
|
||||
|
||||
|
||||
func is_transitioning() -> bool:
|
||||
return _is_transitioning
|
||||
|
||||
|
||||
func focus_initial() -> void:
|
||||
var target: BubbleButton = (
|
||||
_cancel_button
|
||||
if _initial_focus == InitialFocus.CANCEL
|
||||
else _confirm_button
|
||||
)
|
||||
if target.visible and target.focus_mode != Control.FOCUS_NONE:
|
||||
target.grab_focus()
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if visible:
|
||||
_cluster.advance_motion(delta)
|
||||
|
||||
|
||||
func _update_layout() -> void:
|
||||
if not is_node_ready() or _cluster == null:
|
||||
return
|
||||
var compact: bool = (
|
||||
size.x < compact_width_threshold
|
||||
or size.y < compact_height_threshold
|
||||
)
|
||||
var layout_maximum: Vector2 = (
|
||||
compact_maximum_layout_size
|
||||
if compact
|
||||
else maximum_layout_size
|
||||
)
|
||||
var field_size := Vector2(
|
||||
minf(size.x, layout_maximum.x),
|
||||
minf(size.y, layout_maximum.y)
|
||||
)
|
||||
field_size.x = maxf(1.0, field_size.x)
|
||||
field_size.y = maxf(1.0, field_size.y)
|
||||
_resting_cluster_position = (size - field_size) * 0.5
|
||||
if not _is_transitioning:
|
||||
_cluster.position = _resting_cluster_position
|
||||
_cluster.size = field_size
|
||||
_cluster.apply_layout(field_size, compact)
|
||||
|
||||
|
||||
func _configure_focus() -> void:
|
||||
var confirm_to_cancel: NodePath = _confirm_button.get_path_to(
|
||||
_cancel_button
|
||||
)
|
||||
var cancel_to_confirm: NodePath = _cancel_button.get_path_to(
|
||||
_confirm_button
|
||||
)
|
||||
_confirm_button.focus_neighbor_left = confirm_to_cancel
|
||||
_confirm_button.focus_neighbor_right = confirm_to_cancel
|
||||
_confirm_button.focus_neighbor_top = confirm_to_cancel
|
||||
_confirm_button.focus_neighbor_bottom = confirm_to_cancel
|
||||
_cancel_button.focus_neighbor_left = cancel_to_confirm
|
||||
_cancel_button.focus_neighbor_right = cancel_to_confirm
|
||||
_cancel_button.focus_neighbor_top = cancel_to_confirm
|
||||
_cancel_button.focus_neighbor_bottom = cancel_to_confirm
|
||||
|
||||
|
||||
func _set_interactive(interactive: bool) -> void:
|
||||
mouse_filter = (
|
||||
Control.MOUSE_FILTER_PASS
|
||||
if interactive
|
||||
else Control.MOUSE_FILTER_IGNORE
|
||||
)
|
||||
for bubble: BubbleButton in [_confirm_button, _cancel_button]:
|
||||
if not interactive and bubble.has_focus():
|
||||
bubble.release_focus()
|
||||
bubble.focus_mode = (
|
||||
Control.FOCUS_ALL if interactive else Control.FOCUS_NONE
|
||||
)
|
||||
bubble.mouse_filter = (
|
||||
Control.MOUSE_FILTER_STOP
|
||||
if interactive
|
||||
else Control.MOUSE_FILTER_IGNORE
|
||||
)
|
||||
|
||||
|
||||
func _finish_transition_in(
|
||||
generation: int,
|
||||
completed: Callable,
|
||||
) -> void:
|
||||
if generation != _transition_generation or not visible:
|
||||
return
|
||||
_transition = null
|
||||
_is_transitioning = false
|
||||
_cluster.position = _resting_cluster_position
|
||||
_set_interactive(true)
|
||||
focus_initial()
|
||||
completed.call()
|
||||
|
||||
|
||||
func _finish_transition_out(
|
||||
generation: int,
|
||||
completed: Callable,
|
||||
) -> void:
|
||||
if generation != _transition_generation or not visible:
|
||||
return
|
||||
_transition = null
|
||||
_is_transitioning = false
|
||||
hide()
|
||||
_cluster.position = _resting_cluster_position
|
||||
set_process(false)
|
||||
completed.call()
|
||||
|
||||
|
||||
func _cancel_transition() -> void:
|
||||
if _transition != null:
|
||||
_transition.kill()
|
||||
_transition = null
|
||||
|
|
@ -0,0 +1 @@
|
|||
uid://c5q6yypya7fr1
|
||||
139
ui/components/bubble_menu/bubble_confirmation_page.tscn
Normal file
139
ui/components/bubble_menu/bubble_confirmation_page.tscn
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
[gd_scene load_steps=7 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_confirmation_page.gd" id="1_page"]
|
||||
[ext_resource type="PackedScene" path="res://ui/components/bubble_menu/bubble_button.tscn" id="2_bubble"]
|
||||
[ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_cluster.gd" id="3_cluster"]
|
||||
[ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_menu_profile.gd" id="4_profile_script"]
|
||||
[ext_resource type="Resource" path="res://ui/components/bubble_menu/bubble_menu_profile.tres" id="5_profile"]
|
||||
|
||||
[sub_resource type="Resource" id="BubbleMenuProfile_confirmation_action"]
|
||||
script = ExtResource("4_profile_script")
|
||||
normal_border_width = 3
|
||||
emphasized_border_width = 4
|
||||
|
||||
[sub_resource type="Resource" id="BubbleMenuProfile_confirmation_message"]
|
||||
script = ExtResource("4_profile_script")
|
||||
corner_radius = 256
|
||||
|
||||
[node name="BubbleConfirmationPage" type="Control"]
|
||||
visible = false
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
script = ExtResource("1_page")
|
||||
|
||||
[node name="BubbleCluster" type="Control" parent="."]
|
||||
layout_mode = 0
|
||||
offset_right = 720.0
|
||||
offset_bottom = 520.0
|
||||
script = ExtResource("3_cluster")
|
||||
profile = ExtResource("5_profile")
|
||||
desktop_reference_size = Vector2(720, 520)
|
||||
compact_reference_size = Vector2(544, 400)
|
||||
|
||||
[node name="MessageBubble" parent="BubbleCluster" instance=ExtResource("2_bubble")]
|
||||
custom_minimum_size = Vector2(0, 0)
|
||||
profile = SubResource("BubbleMenuProfile_confirmation_message")
|
||||
neutral_size = Vector2(356, 350)
|
||||
desktop_anchor = Vector2(360, 214)
|
||||
compact_anchor = Vector2(360, 196)
|
||||
compact_minimum_size = Vector2(292, 288)
|
||||
label_control_path = NodePath("MessageLabel")
|
||||
minimum_font_size = 15
|
||||
maximum_font_size = 22
|
||||
horizontal_amplitude = 0.8
|
||||
vertical_amplitude = 2.2
|
||||
motion_period = 6.8
|
||||
motion_phase = 0.45
|
||||
deformation_amplitude = 0.008
|
||||
deformation_period = 7.4
|
||||
|
||||
[node name="MessageLabel" type="Label" parent="BubbleCluster/MessageBubble"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 38.0
|
||||
offset_top = 36.0
|
||||
offset_right = -38.0
|
||||
offset_bottom = -36.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
theme_override_colors/font_color = Color(0.035, 0.145, 0.22, 1)
|
||||
theme_override_constants/line_spacing = -2
|
||||
text = "confirm?"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
autowrap_mode = 2
|
||||
|
||||
[node name="ConfirmButton" parent="BubbleCluster" instance=ExtResource("2_bubble")]
|
||||
custom_minimum_size = Vector2(0, 0)
|
||||
profile = SubResource("BubbleMenuProfile_confirmation_action")
|
||||
neutral_size = Vector2(142, 136)
|
||||
desktop_anchor = Vector2(258, 432)
|
||||
compact_anchor = Vector2(210, 432)
|
||||
compact_minimum_size = Vector2(108, 104)
|
||||
label_control_path = NodePath("ConfirmLabel")
|
||||
minimum_font_size = 14
|
||||
maximum_font_size = 22
|
||||
horizontal_amplitude = 1.7
|
||||
vertical_amplitude = 4.2
|
||||
motion_period = 5.6
|
||||
motion_phase = 2.15
|
||||
deformation_amplitude = 0.014
|
||||
deformation_period = 5.9
|
||||
|
||||
[node name="ConfirmLabel" type="Label" parent="BubbleCluster/ConfirmButton"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 14.0
|
||||
offset_top = 14.0
|
||||
offset_right = -14.0
|
||||
offset_bottom = -14.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
text = "confirm"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
autowrap_mode = 2
|
||||
|
||||
[node name="CancelButton" parent="BubbleCluster" instance=ExtResource("2_bubble")]
|
||||
custom_minimum_size = Vector2(0, 0)
|
||||
neutral_size = Vector2(124, 120)
|
||||
desktop_anchor = Vector2(462, 426)
|
||||
compact_anchor = Vector2(510, 426)
|
||||
compact_minimum_size = Vector2(98, 94)
|
||||
label_control_path = NodePath("CancelLabel")
|
||||
minimum_font_size = 14
|
||||
maximum_font_size = 20
|
||||
horizontal_amplitude = 1.5
|
||||
vertical_amplitude = 3.8
|
||||
motion_period = 5.1
|
||||
motion_phase = 4.0
|
||||
deformation_amplitude = 0.015
|
||||
deformation_period = 5.4
|
||||
|
||||
[node name="CancelLabel" type="Label" parent="BubbleCluster/CancelButton"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 12.0
|
||||
offset_top = 12.0
|
||||
offset_right = -12.0
|
||||
offset_bottom = -12.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
text = "cancel"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
autowrap_mode = 2
|
||||
Loading…
Add table
Add a link
Reference in a new issue