Expand bubble menu presentation
This commit is contained in:
parent
359a96f6e2
commit
f04d8db8ac
10 changed files with 1016 additions and 165 deletions
252
ui/title_confirmation_bubble_page.gd
Normal file
252
ui/title_confirmation_bubble_page.gd
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
class_name TitleConfirmationBubblePage
|
||||
extends Control
|
||||
|
||||
signal confirmed
|
||||
signal cancelled
|
||||
|
||||
@export var cluster_path: NodePath = ^"BubbleCluster"
|
||||
@export var prompt_path: NodePath = ^"BubbleCluster/PromptBubble"
|
||||
@export var prompt_label_path: NodePath = ^"BubbleCluster/PromptBubble/PromptLabel"
|
||||
@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 multiline_confirm_neutral_size: Vector2 = Vector2(116.0, 116.0)
|
||||
@export var multiline_confirm_compact_minimum_size: Vector2 = (
|
||||
Vector2(90.0, 90.0)
|
||||
)
|
||||
@export var compact_height_threshold: float = 250.0
|
||||
@export_range(0.0, 128.0, 1.0) var transition_safe_margin: float = 24.0
|
||||
|
||||
var _cluster: BubbleCluster
|
||||
var _prompt: BubbleButton
|
||||
var _prompt_label: Label
|
||||
var _confirm_button: BubbleButton
|
||||
var _confirm_label: Label
|
||||
var _cancel_button: BubbleButton
|
||||
var _default_confirm_neutral_size: Vector2
|
||||
var _default_confirm_compact_minimum_size: Vector2
|
||||
var _bubbles: Array[BubbleButton] = []
|
||||
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
|
||||
_prompt = get_node(prompt_path) as BubbleButton
|
||||
_prompt_label = get_node(prompt_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
|
||||
_default_confirm_neutral_size = _confirm_button.neutral_size
|
||||
_default_confirm_compact_minimum_size = (
|
||||
_confirm_button.compact_minimum_size
|
||||
)
|
||||
_bubbles = [_prompt, _confirm_button, _cancel_button]
|
||||
_cluster.configure(_bubbles)
|
||||
_prompt.focus_mode = Control.FOCUS_NONE
|
||||
_prompt.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_confirm_button.pressed.connect(confirmed.emit)
|
||||
_cancel_button.pressed.connect(cancelled.emit)
|
||||
_configure_focus()
|
||||
hide_page()
|
||||
|
||||
|
||||
func configure(
|
||||
prompt_text: String,
|
||||
confirm_text: String,
|
||||
use_multiline_action_layout: bool = false,
|
||||
) -> void:
|
||||
_prompt_label.text = prompt_text
|
||||
if use_multiline_action_layout:
|
||||
_confirm_button.text = ""
|
||||
_confirm_label.text = confirm_text
|
||||
_confirm_label.show()
|
||||
_confirm_button.label_control_path = ^"ConfirmLabel"
|
||||
else:
|
||||
_confirm_label.text = ""
|
||||
_confirm_label.hide()
|
||||
_confirm_button.label_control_path = NodePath()
|
||||
_confirm_button.text = confirm_text
|
||||
_confirm_button.apply_profile()
|
||||
_confirm_button.neutral_size = (
|
||||
multiline_confirm_neutral_size
|
||||
if use_multiline_action_layout
|
||||
else _default_confirm_neutral_size
|
||||
)
|
||||
_confirm_button.compact_minimum_size = (
|
||||
multiline_confirm_compact_minimum_size
|
||||
if use_multiline_action_layout
|
||||
else _default_confirm_compact_minimum_size
|
||||
)
|
||||
|
||||
|
||||
func set_stage_rect(stage_rect: Rect2) -> void:
|
||||
if not is_node_ready():
|
||||
return
|
||||
var field_size := Vector2(
|
||||
maxf(1.0, stage_rect.size.x),
|
||||
maxf(1.0, stage_rect.size.y)
|
||||
)
|
||||
_resting_cluster_position = stage_rect.position
|
||||
_cluster.size = field_size
|
||||
_cluster.apply_layout(
|
||||
field_size,
|
||||
field_size.y < compact_height_threshold
|
||||
)
|
||||
if not _is_transitioning:
|
||||
_cluster.position = _resting_cluster_position
|
||||
|
||||
|
||||
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_confirm() -> void:
|
||||
if (
|
||||
_confirm_button.visible
|
||||
and _confirm_button.focus_mode != Control.FOCUS_NONE
|
||||
):
|
||||
_confirm_button.grab_focus()
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if visible:
|
||||
_cluster.advance_motion(delta)
|
||||
|
||||
|
||||
func _configure_focus() -> void:
|
||||
_confirm_button.focus_neighbor_left = _confirm_button.get_path_to(
|
||||
_cancel_button
|
||||
)
|
||||
_confirm_button.focus_neighbor_right = _confirm_button.focus_neighbor_left
|
||||
_confirm_button.focus_neighbor_top = _confirm_button.focus_neighbor_left
|
||||
_confirm_button.focus_neighbor_bottom = _confirm_button.focus_neighbor_left
|
||||
_cancel_button.focus_neighbor_left = _cancel_button.get_path_to(
|
||||
_confirm_button
|
||||
)
|
||||
_cancel_button.focus_neighbor_right = _cancel_button.focus_neighbor_left
|
||||
_cancel_button.focus_neighbor_top = _cancel_button.focus_neighbor_left
|
||||
_cancel_button.focus_neighbor_bottom = _cancel_button.focus_neighbor_left
|
||||
|
||||
|
||||
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 bubble == null:
|
||||
continue
|
||||
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
|
||||
_set_interactive(true)
|
||||
focus_confirm()
|
||||
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
|
||||
Loading…
Add table
Add a link
Reference in a new issue