Merge remote-tracking branch 'origin/main'
# Conflicts: # export_presets.cfg # scripts/build_playtest.sh
This commit is contained in:
commit
9729c278dc
54 changed files with 1627 additions and 261 deletions
|
|
@ -41,6 +41,8 @@ func _input(event: InputEvent) -> void:
|
|||
CONTROLLER_MOTION_THRESHOLD
|
||||
):
|
||||
_set_controller_active(true)
|
||||
elif event is InputEventMouseMotion:
|
||||
_set_controller_active(false)
|
||||
elif event is InputEventMouseButton:
|
||||
if (event as InputEventMouseButton).pressed:
|
||||
_set_controller_active(false)
|
||||
|
|
|
|||
|
|
@ -3,11 +3,28 @@ extends Node
|
|||
|
||||
const CONTROLLER_AXIS_THRESHOLD: float = 0.35
|
||||
const SEMANTIC_MATCH_BONUS: float = 1000000.0
|
||||
const KEYBOARD_NAVIGATION_ACTIONS: Array[StringName] = [
|
||||
&"ui_accept",
|
||||
&"ui_cancel",
|
||||
&"ui_up",
|
||||
&"ui_down",
|
||||
&"ui_left",
|
||||
&"ui_right",
|
||||
&"ui_focus_next",
|
||||
&"ui_focus_prev",
|
||||
&"ui_page_up",
|
||||
&"ui_page_down",
|
||||
&"ui_home",
|
||||
&"ui_end",
|
||||
]
|
||||
|
||||
var _controller_active: bool = false
|
||||
var _focus_navigation_active: bool = false
|
||||
var _last_focus_center: Vector2 = Vector2.ZERO
|
||||
var _last_focus_key: String = ""
|
||||
var _scope_chain: Array[WeakRef] = []
|
||||
var _pending_focus: WeakRef
|
||||
var _pointer_button_down: bool = false
|
||||
var _recovery_generation: int = 0
|
||||
|
||||
|
||||
|
|
@ -27,23 +44,51 @@ func _input(event: InputEvent) -> void:
|
|||
if event is InputEventJoypadButton:
|
||||
var button_event := event as InputEventJoypadButton
|
||||
if button_event.pressed:
|
||||
_pointer_button_down = false
|
||||
_controller_active = true
|
||||
_focus_navigation_active = true
|
||||
if button_event.button_index == JOY_BUTTON_LEFT_SHOULDER:
|
||||
_recovery_generation += 1
|
||||
_scope_chain.clear()
|
||||
_pending_focus = null
|
||||
return
|
||||
_request_pending_focus()
|
||||
return
|
||||
if event is InputEventJoypadMotion:
|
||||
if absf((event as InputEventJoypadMotion).axis_value) >= (
|
||||
CONTROLLER_AXIS_THRESHOLD
|
||||
):
|
||||
_pointer_button_down = false
|
||||
_controller_active = true
|
||||
_focus_navigation_active = true
|
||||
_request_pending_focus()
|
||||
return
|
||||
if event is InputEventMouseMotion:
|
||||
_pointer_button_down = (
|
||||
(event as InputEventMouseMotion).button_mask != 0
|
||||
)
|
||||
_leave_focus_navigation(not _pointer_button_down)
|
||||
return
|
||||
if event is InputEventMouseButton:
|
||||
if (event as InputEventMouseButton).pressed:
|
||||
_controller_active = false
|
||||
var mouse_button := event as InputEventMouseButton
|
||||
if mouse_button.button_index in [
|
||||
MOUSE_BUTTON_LEFT,
|
||||
MOUSE_BUTTON_RIGHT,
|
||||
MOUSE_BUTTON_MIDDLE,
|
||||
]:
|
||||
_pointer_button_down = mouse_button.pressed
|
||||
_leave_focus_navigation(false)
|
||||
if not mouse_button.pressed:
|
||||
_release_current_pointer_focus.call_deferred()
|
||||
return
|
||||
if event is InputEventKey and (event as InputEventKey).pressed:
|
||||
_pointer_button_down = false
|
||||
_controller_active = false
|
||||
if _is_keyboard_navigation_event(event):
|
||||
_focus_navigation_active = true
|
||||
_request_pending_focus()
|
||||
else:
|
||||
_leave_focus_navigation()
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
|
|
@ -58,8 +103,16 @@ func _process(_delta: float) -> void:
|
|||
func _on_gui_focus_changed(control: Control) -> void:
|
||||
_recovery_generation += 1
|
||||
if control != null:
|
||||
if _controller_active and control is BaseButton:
|
||||
_remember_focus(control)
|
||||
if _focus_navigation_active:
|
||||
_pending_focus = null
|
||||
if _controller_active and control is BaseButton:
|
||||
_remember_focus(control)
|
||||
return
|
||||
if _keeps_pointer_focus(control):
|
||||
return
|
||||
if _is_focusable(control):
|
||||
_pending_focus = weakref(control)
|
||||
_release_focus_if_inactive.call_deferred(control)
|
||||
return
|
||||
if not _controller_active or _scope_chain.is_empty():
|
||||
return
|
||||
|
|
@ -92,13 +145,18 @@ func _recover_focus(generation: int) -> void:
|
|||
scope == null
|
||||
or not is_instance_valid(scope)
|
||||
or not scope.is_inside_tree()
|
||||
or not scope.is_visible_in_tree()
|
||||
):
|
||||
continue
|
||||
if not scope.is_visible_in_tree():
|
||||
_scope_chain.clear()
|
||||
return
|
||||
var replacement := _best_replacement_in(scope)
|
||||
if replacement != null:
|
||||
replacement.grab_focus()
|
||||
return
|
||||
else:
|
||||
_scope_chain.clear()
|
||||
return
|
||||
_scope_chain.clear()
|
||||
|
||||
|
||||
func _best_replacement_in(scope: Control) -> Control:
|
||||
|
|
@ -124,7 +182,7 @@ func _is_focusable(control: Control) -> bool:
|
|||
control == null
|
||||
or not control.is_inside_tree()
|
||||
or not control.is_visible_in_tree()
|
||||
or control.focus_mode == Control.FOCUS_NONE
|
||||
or control.focus_mode not in [Control.FOCUS_CLICK, Control.FOCUS_ALL]
|
||||
):
|
||||
return false
|
||||
var button := control as BaseButton
|
||||
|
|
@ -140,3 +198,80 @@ func _semantic_key(control: Control) -> String:
|
|||
tooltip = tooltip.trim_suffix(" (show variants)")
|
||||
tooltip = tooltip.trim_suffix(" (hide variants)")
|
||||
return "%s|%s|%s" % [control.get_class(), label, tooltip]
|
||||
|
||||
|
||||
func _leave_focus_navigation(release_focus: bool = true) -> void:
|
||||
_controller_active = false
|
||||
_focus_navigation_active = false
|
||||
_recovery_generation += 1
|
||||
_scope_chain.clear()
|
||||
var focus_owner: Control = get_viewport().gui_get_focus_owner()
|
||||
if focus_owner == null or _keeps_pointer_focus(focus_owner):
|
||||
return
|
||||
if _is_focusable(focus_owner):
|
||||
_pending_focus = weakref(focus_owner)
|
||||
if release_focus:
|
||||
_release_focus_if_inactive.call_deferred(focus_owner)
|
||||
|
||||
|
||||
func _release_focus_if_inactive(control: Control) -> void:
|
||||
if (
|
||||
_focus_navigation_active
|
||||
or _pointer_button_down
|
||||
or control == null
|
||||
or not is_instance_valid(control)
|
||||
or get_viewport().gui_get_focus_owner() != control
|
||||
or _keeps_pointer_focus(control)
|
||||
):
|
||||
return
|
||||
get_viewport().gui_release_focus()
|
||||
|
||||
|
||||
func _release_current_pointer_focus() -> void:
|
||||
var focus_owner: Control = get_viewport().gui_get_focus_owner()
|
||||
if focus_owner != null:
|
||||
_release_focus_if_inactive(focus_owner)
|
||||
|
||||
|
||||
func _request_pending_focus() -> void:
|
||||
if (
|
||||
not _focus_navigation_active
|
||||
or _pending_focus == null
|
||||
or get_viewport().gui_get_focus_owner() != null
|
||||
):
|
||||
return
|
||||
_restore_pending_focus.call_deferred(_recovery_generation)
|
||||
|
||||
|
||||
func _restore_pending_focus(generation: int) -> void:
|
||||
if (
|
||||
generation != _recovery_generation
|
||||
or not _focus_navigation_active
|
||||
or _pending_focus == null
|
||||
or get_viewport().gui_get_focus_owner() != null
|
||||
):
|
||||
return
|
||||
var target := _pending_focus.get_ref() as Control
|
||||
if not _is_focusable(target):
|
||||
_pending_focus = null
|
||||
return
|
||||
_pending_focus = null
|
||||
target.grab_focus()
|
||||
|
||||
|
||||
func _is_keyboard_navigation_event(event: InputEvent) -> bool:
|
||||
if event.is_action_pressed(&"open_backpack"):
|
||||
return false
|
||||
if (
|
||||
event.is_action_pressed(&"ui_cancel")
|
||||
and get_viewport().gui_get_focus_owner() == null
|
||||
):
|
||||
return false
|
||||
for action: StringName in KEYBOARD_NAVIGATION_ACTIONS:
|
||||
if event.is_action_pressed(action):
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _keeps_pointer_focus(control: Control) -> bool:
|
||||
return control is LineEdit or control is TextEdit
|
||||
|
|
|
|||
|
|
@ -74,6 +74,8 @@ const SUPPLY_PRICE_HORIZONTAL_PADDING: float = 12.0
|
|||
|
||||
@onready var _wallet_label: Label = %WalletLabel
|
||||
@onready var _shop_panel: PanelContainer = %ShopPanel
|
||||
@onready var _shop_panel_margin: MarginContainer = $ShopPanel/Margin
|
||||
@onready var _shop_panel_layout: VBoxContainer = $ShopPanel/Margin/Layout
|
||||
@onready var _shop_cooler_page: Control = %ShopCoolerPage
|
||||
@onready var _shop_cooler_mount: Control = %ShopCoolerMount
|
||||
@onready var _shop_body: HBoxContainer = $ShopPanel/Margin/Layout/Body
|
||||
|
|
@ -367,6 +369,7 @@ func get_shop_cooler_mount() -> Control:
|
|||
func activate_shop_cooler_page() -> void:
|
||||
_cooler_page_active = true
|
||||
_shop_panel.show()
|
||||
_set_shop_panel_background_pointer_blocking(false)
|
||||
_shop_body.hide()
|
||||
_feedback.hide()
|
||||
_shop_cooler_page.show()
|
||||
|
|
@ -377,6 +380,7 @@ func deactivate_shop_cooler_page() -> void:
|
|||
_shop_tab_bar.show()
|
||||
_cooler_page_active = false
|
||||
_cooler_modal_open = false
|
||||
_set_shop_panel_background_pointer_blocking(true)
|
||||
for tab: OrganizerTab in _shop_tabs:
|
||||
tab.disabled = false
|
||||
_shop_cooler_page.hide()
|
||||
|
|
@ -385,6 +389,17 @@ func deactivate_shop_cooler_page() -> void:
|
|||
_feedback.show()
|
||||
|
||||
|
||||
func _set_shop_panel_background_pointer_blocking(blocking: bool) -> void:
|
||||
var mouse_filter := (
|
||||
Control.MOUSE_FILTER_STOP
|
||||
if blocking
|
||||
else Control.MOUSE_FILTER_IGNORE
|
||||
)
|
||||
_shop_panel.mouse_filter = mouse_filter
|
||||
_shop_panel_margin.mouse_filter = mouse_filter
|
||||
_shop_panel_layout.mouse_filter = mouse_filter
|
||||
|
||||
|
||||
func set_shop_cooler_modal_open(is_open: bool) -> void:
|
||||
_cooler_modal_open = is_open
|
||||
for tab: OrganizerTab in _shop_tabs:
|
||||
|
|
|
|||
|
|
@ -351,7 +351,10 @@ func _open_confirmation(
|
|||
else BubbleConfirmationPageType.InitialFocus.CONFIRM
|
||||
)
|
||||
)
|
||||
_begin_root_exit(_show_confirmation)
|
||||
# Confirmation actions reject input while the root page is still leaving.
|
||||
# Finish that exit before exposing an interactive confirmation page so a
|
||||
# pointer click can never land in the overlap window and be discarded.
|
||||
_begin_root_exit(_show_confirmation, false)
|
||||
|
||||
|
||||
func _show_confirmation() -> void:
|
||||
|
|
@ -450,7 +453,10 @@ func _finish_root_entry(generation: int) -> void:
|
|||
_root_transition_active = false
|
||||
|
||||
|
||||
func _begin_root_exit(completed: Callable) -> void:
|
||||
func _begin_root_exit(
|
||||
completed: Callable,
|
||||
overlap_next_page: bool = true,
|
||||
) -> void:
|
||||
if _root_transition_active or not _root_page.visible:
|
||||
return
|
||||
_root_transition_generation += 1
|
||||
|
|
@ -458,9 +464,14 @@ func _begin_root_exit(completed: Callable) -> void:
|
|||
_emit_transition_flurry()
|
||||
var generation: int = _root_transition_generation
|
||||
_root_page.transition_out(
|
||||
_finish_root_exit.bind(generation),
|
||||
_finish_root_exit.bind(
|
||||
generation,
|
||||
Callable() if overlap_next_page else completed,
|
||||
),
|
||||
0.0
|
||||
)
|
||||
if not overlap_next_page:
|
||||
return
|
||||
await get_tree().create_timer(
|
||||
UIMotion.BUBBLE_TRANSITION_OVERLAP_DELAY
|
||||
).timeout
|
||||
|
|
@ -471,10 +482,13 @@ func _begin_root_exit(completed: Callable) -> void:
|
|||
|
||||
func _finish_root_exit(
|
||||
generation: int,
|
||||
completed: Callable,
|
||||
) -> void:
|
||||
if generation != _root_transition_generation or not visible:
|
||||
return
|
||||
_root_transition_active = false
|
||||
if completed.is_valid():
|
||||
completed.call()
|
||||
|
||||
|
||||
func _finish_user_close() -> void:
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ func _set_host_toggle_state(
|
|||
|
||||
|
||||
func _select_tab(index: int) -> void:
|
||||
if index == 2 and (_service == null or not _service.is_local_host()):
|
||||
if index == 2 and (_service == null or not _service.is_local_moderator()):
|
||||
return
|
||||
_current_tab = index
|
||||
_refresh()
|
||||
|
|
@ -207,10 +207,12 @@ func _refresh() -> void:
|
|||
_count_label.text = "%d / %d connected" % [
|
||||
_service.get_connected_count(), _service.get_max_players(),
|
||||
]
|
||||
if _current_tab == 2 and not _service.is_local_moderator():
|
||||
_current_tab = 0
|
||||
for index: int in _tabs.get_child_count():
|
||||
var button := _tabs.get_child(index) as Button
|
||||
button.button_pressed = index == _current_tab
|
||||
button.visible = index != 2 or _service.is_local_host()
|
||||
button.visible = index != 2 or _service.is_local_moderator()
|
||||
_refresh_host_settings()
|
||||
for child: Node in _list.get_children():
|
||||
child.queue_free()
|
||||
|
|
@ -330,7 +332,7 @@ func _on_host_status_changed(message: String, is_error: bool) -> void:
|
|||
|
||||
|
||||
func _build_active_rows() -> void:
|
||||
if _service.is_local_host():
|
||||
if _service.is_local_moderator():
|
||||
_build_session_artwork_controls()
|
||||
var entries := _service.get_entries()
|
||||
if entries.is_empty():
|
||||
|
|
@ -339,10 +341,12 @@ func _build_active_rows() -> void:
|
|||
for entry: PlayerListEntry in entries:
|
||||
var row := _make_row()
|
||||
var identity := Label.new()
|
||||
identity.custom_minimum_size.x = 515
|
||||
identity.custom_minimum_size.x = 430
|
||||
var markers: Array[String] = []
|
||||
if entry.is_host:
|
||||
markers.append("host")
|
||||
if entry.is_operator:
|
||||
markers.append("operator")
|
||||
if entry.is_local_player:
|
||||
markers.append("You")
|
||||
identity.text = "%s%s · %s\n%s" % [
|
||||
|
|
@ -351,7 +355,7 @@ func _build_active_rows() -> void:
|
|||
entry.compact_fingerprint,
|
||||
entry.continuity_state,
|
||||
]
|
||||
identity.tooltip_text = NetworkIdentityCrypto.format_fingerprint(
|
||||
identity.tooltip_text = "Full identity fingerprint:\n%s" % (
|
||||
entry.full_fingerprint
|
||||
)
|
||||
identity.add_theme_color_override(
|
||||
|
|
@ -381,6 +385,12 @@ func _build_active_rows() -> void:
|
|||
block.pressed.connect(_confirm_block.bind(entry))
|
||||
UtilityPageStyle.apply_ocean_button(block)
|
||||
row.add_child(block)
|
||||
if entry.can_manage_operator:
|
||||
var operator := Button.new()
|
||||
operator.text = "deop" if entry.is_operator else "op"
|
||||
operator.pressed.connect(_confirm_operator.bind(entry))
|
||||
UtilityPageStyle.apply_ocean_button(operator)
|
||||
row.add_child(operator)
|
||||
var kick := Button.new()
|
||||
kick.text = "kick"
|
||||
kick.disabled = not entry.can_kick
|
||||
|
|
@ -539,6 +549,24 @@ func _confirm_ban(entry: PlayerListEntry) -> void:
|
|||
)
|
||||
|
||||
|
||||
func _confirm_operator(entry: PlayerListEntry) -> void:
|
||||
var enabled: bool = not entry.is_operator
|
||||
_confirm(
|
||||
(
|
||||
"Grant operator access to %s for this room session?"
|
||||
if enabled
|
||||
else "Remove operator access from %s?"
|
||||
) % entry.display_name,
|
||||
func() -> void:
|
||||
_service.set_operator(
|
||||
entry.peer_id,
|
||||
entry.full_fingerprint,
|
||||
enabled,
|
||||
entry.revision,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
func _confirm_unban(fingerprint: String) -> void:
|
||||
_confirm("Unban %s?" % NetworkIdentityCrypto.compact_suffix(fingerprint), func() -> void:
|
||||
_service.unban(fingerprint)
|
||||
|
|
|
|||
|
|
@ -156,6 +156,7 @@ func _ready() -> void:
|
|||
%ChangeDataFolder.pressed.connect(_choose_data_folder)
|
||||
%ExportPlayerIdentity.pressed.connect(_choose_identity_export.bind("player"))
|
||||
%ImportPlayerIdentity.pressed.connect(_choose_identity_import.bind("player"))
|
||||
%CopyPlayerFingerprint.pressed.connect(_copy_player_fingerprint)
|
||||
%ExportHostIdentity.pressed.connect(_choose_identity_export.bind("host"))
|
||||
%ImportHostIdentity.pressed.connect(_choose_identity_import.bind("host"))
|
||||
for index: int in _world_options.size():
|
||||
|
|
@ -485,6 +486,16 @@ func _refresh_data_page() -> void:
|
|||
_data_root.override_active
|
||||
or (_network_session != null and _network_session.is_session_active())
|
||||
)
|
||||
var fingerprint: String = (
|
||||
_player_identity.fingerprint if _player_identity != null else ""
|
||||
)
|
||||
%CopyPlayerFingerprint.disabled = not NetworkIdentityCrypto.valid_fingerprint(
|
||||
fingerprint
|
||||
)
|
||||
%CopyPlayerFingerprint.text = (
|
||||
"Copy Player Fingerprint · %s"
|
||||
% NetworkIdentityCrypto.compact_suffix(fingerprint)
|
||||
)
|
||||
|
||||
|
||||
func _open_data_folder() -> void:
|
||||
|
|
@ -492,6 +503,17 @@ func _open_data_folder() -> void:
|
|||
_feedback.text = "Could not open the data folder."
|
||||
|
||||
|
||||
func _copy_player_fingerprint() -> void:
|
||||
var fingerprint: String = (
|
||||
_player_identity.fingerprint if _player_identity != null else ""
|
||||
)
|
||||
if not NetworkIdentityCrypto.valid_fingerprint(fingerprint):
|
||||
_feedback.text = "Player identity is unavailable."
|
||||
return
|
||||
DisplayServer.clipboard_set(fingerprint)
|
||||
_feedback.text = "Full player fingerprint copied for server operator setup."
|
||||
|
||||
|
||||
func _choose_data_folder() -> void:
|
||||
if _data_root == null or _data_root.override_active:
|
||||
_feedback.text = "The data folder is externally managed."
|
||||
|
|
|
|||
|
|
@ -913,6 +913,11 @@ layout_mode = 2
|
|||
text = "Active identity keys stay on this device.\nEncrypted backups can be stored in your synced data folder.\nDo not play the same profile on two devices at the same time."
|
||||
autowrap_mode = 2
|
||||
|
||||
[node name="CopyPlayerFingerprint" type="Button" parent="DataPage/Paper/Content"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Copy Player Fingerprint"
|
||||
|
||||
[node name="IdentityGrid" type="GridContainer" parent="DataPage/Paper/Content"]
|
||||
layout_mode = 2
|
||||
columns = 2
|
||||
|
|
|
|||
|
|
@ -209,7 +209,7 @@ unique_name_in_owner = true
|
|||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.682, 0.733, 0.761, 1)
|
||||
theme_override_font_sizes/font_size = 22
|
||||
text = "v0.6.5-alpha"
|
||||
text = "v0.6.7-alpha"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="Spacer" type="Control" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent"]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue