Polish interface artwork and controller behavior

This commit is contained in:
Alexander Sellite 2026-08-19 10:02:28 -04:00
parent c6b897718c
commit f8e02e3844
41 changed files with 1119 additions and 171 deletions

View file

@ -7,7 +7,9 @@ signal context_requested(slot: GeneralInventorySlot)
signal context_changed(slot: GeneralInventorySlot, text: String, active: bool)
const ItemDataType = preload("res://items/item_data.gd")
const LOCK_ICON: Texture2D = preload("res://ui/icons/pictograms/lock_light.png")
const LockedContentPresentationType = preload(
"res://ui/components/locked_content_presentation.gd"
)
var slot_index: int = -1
var container: int = -1
@ -104,8 +106,8 @@ func refresh() -> void:
disabled = _locked
_apply_icon_geometry()
if _locked:
_icon.texture = LOCK_ICON
_icon.modulate = Color(UtilityPageStyle.OCEAN_DISABLED, 0.18)
_icon.texture = LockedContentPresentationType.ICON
_icon.modulate = LockedContentPresentationType.icon_modulate()
var storage_slot := (
container == PlayerInventoryLayout.InventoryContainer.STORAGE
)
@ -305,7 +307,7 @@ func _apply_style() -> void:
Color(UtilityPageStyle.OCEAN_SELECTED, 0.92), radius
)
var locked := UtilityPageStyle.rounded_style(
Color(UtilityPageStyle.OCEAN_FIELD, 0.38), radius
LockedContentPresentationType.disabled_background_color(), radius
)
for state: StringName in [&"normal", &"pressed"]:
add_theme_stylebox_override(state, normal)
@ -332,7 +334,9 @@ func _apply_icon_geometry() -> void:
var is_large: bool = _presentation_size.x >= 70.0
var margin: float
if _locked:
var lock_size: float = 24.0 if is_large else 18.0
var lock_size: float = LockedContentPresentationType.icon_size_for(
_presentation_size
).x
margin = maxf(
(_presentation_size.x - lock_size) * 0.5,
0.0,

View file

@ -0,0 +1,67 @@
class_name LockedContentPresentation
extends RefCounted
const UtilityPageStyleType = preload("res://ui/utility_page_style.gd")
const ICON: Texture2D = preload("res://ui/icons/pictograms/lock_light.png")
const LARGE_ICON_SIZE: float = 24.0
const COMPACT_ICON_SIZE: float = 18.0
const LARGE_PRESENTATION_THRESHOLD: float = 70.0
const ICON_ALPHA: float = 0.18
const BACKGROUND_ALPHA: float = 0.38
static func icon_size_for(presentation_size: Vector2) -> Vector2:
var edge: float = (
LARGE_ICON_SIZE
if minf(presentation_size.x, presentation_size.y)
>= LARGE_PRESENTATION_THRESHOLD
else COMPACT_ICON_SIZE
)
return Vector2(edge, edge)
static func icon_modulate(alpha: float = ICON_ALPHA) -> Color:
return Color(UtilityPageStyleType.OCEAN_DISABLED, alpha)
static func disabled_background_color() -> Color:
return Color(UtilityPageStyleType.OCEAN_FIELD, BACKGROUND_ALPHA)
static func make_icon_texture(
icon_size: int = int(COMPACT_ICON_SIZE),
canvas_size: int = icon_size,
) -> Texture2D:
var source_image: Image = ICON.get_image()
if source_image == null or source_image.is_empty():
return ICON
var image := source_image.duplicate() as Image
if image.is_compressed() and image.decompress() != OK:
return ICON
image.convert(Image.FORMAT_RGBA8)
image.resize(
icon_size,
icon_size,
Image.INTERPOLATE_NEAREST,
)
if canvas_size > icon_size:
var canvas := Image.create(
canvas_size,
canvas_size,
false,
Image.FORMAT_RGBA8,
)
canvas.fill(Color.TRANSPARENT)
var inset := Vector2i.ONE * ((canvas_size - icon_size) / 2)
canvas.blend_rect(
image,
Rect2i(Vector2i.ZERO, Vector2i(icon_size, icon_size)),
inset,
)
image = canvas
var texture := ImageTexture.create_from_image(image)
texture.set_meta(&"locked_content_icon", true)
texture.set_meta(&"locked_content_icon_size", icon_size)
texture.set_meta(&"locked_content_canvas_size", canvas_size)
return texture

View file

@ -0,0 +1 @@
uid://c0188g32tfjk3

View file

@ -43,6 +43,13 @@ const HOVER_COLOR_REPLACEMENTS: Dictionary[StringName, StringName] = {
}
var _controller_active: bool = false
var _virtual_pointer_active: bool = false
var _navigation_focus_active: bool = false
var _neutral_focus_seed: Control
var _releasing_neutral_focus: bool = false
var _focus_clear_generation: int = 0
var _directional_input_generation: int = 0
var _directional_input_in_flight: bool = false
var _focused_control: Control
var _focused_popup: PopupMenu
var _popup_scroll_offset: float = 0.0
@ -64,6 +71,9 @@ func _ready() -> void:
get_viewport().gui_focus_changed.connect(_on_focus_changed)
set_process_input(true)
set_process(true)
var existing_focus: Control = _active_focus_owner()
if existing_focus != null:
_capture_neutral_focus_seed(existing_focus)
func _exit_tree() -> void:
@ -72,6 +82,17 @@ func _exit_tree() -> void:
func _input(event: InputEvent) -> void:
if _virtual_pointer_active and (
event is InputEventJoypadButton or event is InputEventJoypadMotion
):
return
var directional_navigation: bool = _is_directional_navigation(event)
if directional_navigation:
_directional_input_generation += 1
_directional_input_in_flight = true
_clear_directional_input_in_flight.call_deferred(
_directional_input_generation
)
if event is InputEventJoypadButton:
if (event as InputEventJoypadButton).pressed:
_set_controller_active(true)
@ -82,15 +103,32 @@ func _input(event: InputEvent) -> void:
_set_controller_active(true)
elif event is InputEventMouseMotion:
_set_controller_active(false)
_neutralize_current_navigation_focus()
elif event is InputEventMouseButton:
if (event as InputEventMouseButton).pressed:
_set_controller_active(false)
_neutralize_current_navigation_focus()
elif event is InputEventKey:
if (event as InputEventKey).pressed:
_set_controller_active(false)
if directional_navigation and _restore_neutral_focus_seed():
get_viewport().set_input_as_handled()
func set_virtual_pointer_active(active: bool) -> void:
if _virtual_pointer_active == active:
return
_virtual_pointer_active = active
if active:
_set_controller_active(false)
_clear_focus_presentation()
_restore_native_hover_highlight()
func _process(_delta: float) -> void:
if _virtual_pointer_active:
_set_controller_active(false)
return
_update_hover_suppression()
if _controller_active:
var focused_popup: PopupMenu = _active_focused_popup(get_viewport())
@ -164,6 +202,24 @@ func _set_controller_active(active: bool) -> void:
func _on_focus_changed(control: Control) -> void:
if _releasing_neutral_focus and control == null:
return
if control == null:
_focus_clear_generation += 1
_clear_focus_presentation()
_reset_navigation_session_after_focus_clear.call_deferred(
_focus_clear_generation
)
return
# A normal transfer between controls briefly reports a null owner. Cancel
# that deferred reset so active directional navigation remains continuous.
_focus_clear_generation += 1
if _directional_input_in_flight and _is_navigation_focus(control):
_navigation_focus_active = true
_neutral_focus_seed = null
elif not _navigation_focus_active and _is_navigation_focus(control):
_capture_neutral_focus_seed(control)
return
if _controller_active:
var focused_popup: PopupMenu = _active_focused_popup(get_viewport())
if focused_popup != null:
@ -174,6 +230,68 @@ func _on_focus_changed(control: Control) -> void:
_queue_focus_visibility_update(control)
func _reset_navigation_session_after_focus_clear(generation: int) -> void:
if generation != _focus_clear_generation or _active_focus_owner() != null:
return
_navigation_focus_active = false
_neutral_focus_seed = null
func _clear_directional_input_in_flight(generation: int) -> void:
if generation == _directional_input_generation:
_directional_input_in_flight = false
func _is_directional_navigation(event: InputEvent) -> bool:
if event is InputEventKey and (event as InputEventKey).echo:
return false
return (
event.is_action_pressed(&"ui_left")
or event.is_action_pressed(&"ui_right")
or event.is_action_pressed(&"ui_up")
or event.is_action_pressed(&"ui_down")
or event.is_action_pressed(&"ui_focus_next")
or event.is_action_pressed(&"ui_focus_prev")
)
func _is_navigation_focus(control: Control) -> bool:
# Text entry is an explicit interaction rather than menu navigation. It must
# retain direct focus for typing, including Chat and dialog fields.
return not (control is LineEdit or control is TextEdit)
func _capture_neutral_focus_seed(control: Control) -> void:
if not _focus_is_presentable(control) or not _is_navigation_focus(control):
return
_navigation_focus_active = false
_neutral_focus_seed = control
_clear_focus_presentation()
_releasing_neutral_focus = true
control.release_focus()
_releasing_neutral_focus = false
func _neutralize_current_navigation_focus() -> void:
var focus_owner: Control = _active_focus_owner()
if focus_owner != null and _is_navigation_focus(focus_owner):
_capture_neutral_focus_seed(focus_owner)
else:
_navigation_focus_active = false
_clear_focus_presentation()
func _restore_neutral_focus_seed() -> bool:
if not _focus_is_presentable(_neutral_focus_seed):
_neutral_focus_seed = null
return false
var target: Control = _neutral_focus_seed
_neutral_focus_seed = null
_navigation_focus_active = true
target.grab_focus()
return true
func _queue_focus_visibility_update(control: Control) -> void:
if control == null:
return
@ -314,15 +432,23 @@ func _bubble_focus_target_position(control: Control) -> Vector2:
func _presentation_canvas_scale() -> Vector2:
var host := get_parent() as Control
if host == null:
return Vector2.ONE
var host_scale: Vector2 = (
host.get_global_transform_with_canvas().get_scale().abs()
)
var target: Control = _focused_control
var target_scale := Vector2.ONE
if target != null and is_instance_valid(target):
target_scale = (
target.get_global_transform_with_canvas().get_scale().abs()
)
else:
# PopupMenu is a Window rather than a Control. Its cursor retains the
# canonical host scale used before cross-viewport focus support.
var host := get_parent() as Control
if host != null:
target_scale = (
host.get_global_transform_with_canvas().get_scale().abs()
)
return Vector2(
maxf(host_scale.x, 0.001),
maxf(host_scale.y, 0.001),
maxf(target_scale.x, 0.001),
maxf(target_scale.y, 0.001),
)
@ -535,8 +661,6 @@ func _update_hover_suppression() -> void:
_restore_native_hover_highlight()
if hovered != null:
_suppress_native_hover_highlight(hovered)
func _hovered_control_in_viewport(viewport: Viewport) -> Control:
var embedded_windows: Array[Window] = viewport.get_embedded_subwindows()
for index: int in range(embedded_windows.size() - 1, -1, -1):

View file

@ -27,6 +27,9 @@ const ShopInteractionType = preload(
)
const UtilityPageStyleType = preload("res://ui/utility_page_style.gd")
const OrganizerTabType = preload("res://ui/components/organizer_tab.gd")
const LockedContentPresentationType = preload(
"res://ui/components/locked_content_presentation.gd"
)
const UIMotionType = preload("res://ui/ui_motion.gd")
const ControllerMappingManagerType = preload(
"res://settings/controller_mapping_manager.gd"
@ -66,9 +69,6 @@ const ART_UPGRADE_ICONS: Dictionary[StringName, Texture2D] = {
const CURRENCY_ICON: Texture2D = preload(
"res://items/icons/shop/32_currency.png"
)
const LOCKED_ITEM_ICON: Texture2D = preload(
"res://ui/icons/pictograms/lock_light.png"
)
signal menu_visibility_changed(is_open: bool)
signal menu_exit_started
@ -120,8 +120,11 @@ const SUPPLY_PRICE_ICON_GAP: float = 3.0
const BAIT_SUPPLY_BADGE_Y: float = (
UtilityPageStyleType.SUPPLY_BADGE_EDGE_MARGIN
)
const LOCKED_ITEM_ICON_SIZE := Vector2(48.0, 48.0)
const LOCKED_ITEM_ICON_ALPHA: float = 0.75
const LOCK_BADGE_SIZE := Vector2(30.0, 30.0)
const LOCK_BADGE_MARGIN := Vector2(4.0, 4.0)
const LOCK_BADGE_ICON_SIZE := Vector2(18.0, 18.0)
const LOCK_BADGE_ALPHA: float = 0.9
const LOCK_BADGE_ICON_ALPHA: float = 0.88
const ROD_CARD_TILE_SIZE := Vector2(144.0, 144.0)
const ROD_CARD_HOST_SIZE := Vector2(152.0, 198.0)
const ROD_CAROUSEL_HEIGHT: float = 206.0
@ -1434,27 +1437,41 @@ func _configure_marker_icon_tile(
func _add_unlock_state_icon(
button: Button,
unlocked: bool,
tile_size: Vector2,
price_bubble_y: float = SUPPLY_PRICE_Y,
_tile_size: Vector2,
_price_bubble_y: float = SUPPLY_PRICE_Y,
) -> void:
if unlocked:
return
var badge := Panel.new()
badge.name = "UnlockStateBadge"
badge.position = LOCK_BADGE_MARGIN
badge.size = LOCK_BADGE_SIZE
badge.mouse_filter = Control.MOUSE_FILTER_IGNORE
badge.z_index = 3
var badge_style := UtilityPageStyleType.rounded_style(
Color(UtilityPageStyleType.OCEAN_FIELD, LOCK_BADGE_ALPHA),
int(LOCK_BADGE_SIZE.x * 0.5),
)
badge.add_theme_stylebox_override("panel", badge_style)
button.add_child(badge)
var icon := TextureRect.new()
icon.name = "UnlockStateIcon"
icon.texture = LOCKED_ITEM_ICON
icon.texture = LockedContentPresentationType.ICON
icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
icon.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
icon.z_index = 3
icon.set_meta(&"unlocked", false)
icon.size = LOCKED_ITEM_ICON_SIZE
icon.size = LOCK_BADGE_ICON_SIZE
icon.position = Vector2(
(tile_size.x - LOCKED_ITEM_ICON_SIZE.x) * 0.5,
(price_bubble_y - LOCKED_ITEM_ICON_SIZE.y) * 0.5,
(LOCK_BADGE_SIZE.x - LOCK_BADGE_ICON_SIZE.x) * 0.5,
(LOCK_BADGE_SIZE.y - LOCK_BADGE_ICON_SIZE.y) * 0.5,
)
icon.modulate.a = LOCKED_ITEM_ICON_ALPHA
button.add_child(icon)
icon.modulate = Color(
UtilityPageStyleType.OCEAN_TEXT_PRIMARY,
LOCK_BADGE_ICON_ALPHA,
)
badge.add_child(icon)
func _add_supply_icon_tile(

View file

@ -78,6 +78,7 @@ signal interactive_pointer_ui_changed(is_open: bool)
signal passive_pointer_ui_changed(is_enabled: bool)
signal player_menu_backdrop_visibility_changed(is_visible: bool)
signal shop_backdrop_visibility_changed(is_visible: bool)
signal virtual_pointer_mode_changed(is_active: bool)
const VIRTUAL_MOUSE_INPUT_OWNER: StringName = &"controller_virtual_mouse"
const VIRTUAL_MOUSE_TRIGGER_THRESHOLD: float = 0.55
@ -881,6 +882,7 @@ func _begin_virtual_mouse(device_id: int) -> void:
if _virtual_mouse_active:
return
_virtual_mouse_active = true
virtual_pointer_mode_changed.emit(true)
_virtual_mouse_device_id = maxi(device_id, 0)
_virtual_mouse_stick = (
_mapped_virtual_mouse_stick()
@ -921,6 +923,7 @@ func _end_virtual_mouse() -> void:
if _virtual_mouse_right_pressed:
_set_virtual_mouse_button(MOUSE_BUTTON_RIGHT, false)
_virtual_mouse_active = false
virtual_pointer_mode_changed.emit(false)
_virtual_mouse_trigger_strength = 0.0
_virtual_mouse_stick = Vector2.ZERO
_controller_virtual_cursor.visible = false
@ -994,6 +997,12 @@ func _poll_virtual_mouse_controller_state() -> void:
_virtual_mouse_trigger_strength = _virtual_mouse_strength_for_device(
_virtual_mouse_device_id
)
_sync_virtual_mouse_primary_button(
Input.is_joy_button_pressed(
_virtual_mouse_device_id,
JOY_BUTTON_RIGHT_SHOULDER,
)
)
_set_virtual_mouse_button(
MOUSE_BUTTON_RIGHT,
_secondary_click_strength_for_device(
@ -1034,6 +1043,11 @@ func _poll_mapped_virtual_mouse_controller_state() -> void:
_end_virtual_mouse()
return
_virtual_mouse_stick = _mapped_virtual_mouse_stick()
_sync_virtual_mouse_primary_button(
_controller_mapping_manager.get_role_strength(
ControllerMappingManagerType.ROLE_RB
) >= VIRTUAL_MOUSE_TRIGGER_THRESHOLD
)
_set_virtual_mouse_button(
MOUSE_BUTTON_RIGHT,
_controller_mapping_manager.get_role_strength(
@ -1151,6 +1165,13 @@ func _set_virtual_mouse_button(button: MouseButton, pressed: bool) -> void:
_update_virtual_cursor_position(_virtual_mouse_window_position)
func _sync_virtual_mouse_primary_button(pressed: bool) -> void:
# Embedded PopupMenu windows can consume the controller release event before
# GameUI._input receives it. Polling the physical role while virtual-pointer
# mode is held guarantees the synthetic left button cannot remain latched.
_set_virtual_mouse_button(MOUSE_BUTTON_LEFT, pressed)
func _emit_virtual_mouse_motion(relative_motion: Vector2) -> void:
var motion_event := InputEventMouseMotion.new()
motion_event.position = _virtual_mouse_window_position

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cujpq4ow85uvo"
path="res://.godot/imported/delete_save.png-109b364ded207c94c5c900cd8f4a9bcb.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/main_menu/delete_save.png"
dest_files=["res://.godot/imported/delete_save.png-109b364ded207c94c5c900cd8f4a9bcb.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bjcoxu6kr8to0"
path="res://.godot/imported/moderation_options_ban_light.png-664b6bd4fe95df65f41d3a12c9ee6a40.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/moderation_options/moderation_options_ban_light.png"
dest_files=["res://.godot/imported/moderation_options_ban_light.png-664b6bd4fe95df65f41d3a12c9ee6a40.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://htxi2g3qy1bh"
path="res://.godot/imported/moderation_options_block_light.png-48d6ddc186905a599655e8b153fa5b6c.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/moderation_options/moderation_options_block_light.png"
dest_files=["res://.godot/imported/moderation_options_block_light.png-48d6ddc186905a599655e8b153fa5b6c.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bkey68v7byxky"
path="res://.godot/imported/moderation_options_kick_light.png-3dc1d5aa8e3312c17f3a81edd965a73b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/moderation_options/moderation_options_kick_light.png"
dest_files=["res://.godot/imported/moderation_options_kick_light.png-3dc1d5aa8e3312c17f3a81edd965a73b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://f1cjsxeylc72"
path="res://.godot/imported/moderation_options_mute_light.png-10dd65374b10f6456b5d4ab813da36a0.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/moderation_options/moderation_options_mute_light.png"
dest_files=["res://.godot/imported/moderation_options_mute_light.png-10dd65374b10f6456b5d4ab813da36a0.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View file

@ -519,14 +519,17 @@ func _build_compose() -> Control:
page.add_child(_body)
_salutation = OptionButton.new()
_salutation.position = Vector2(12, 336)
_salutation.size = Vector2(180, 46)
_salutation.size = Vector2(230, 46)
_salutation.alignment = HORIZONTAL_ALIGNMENT_CENTER
for id: String in NetworkMailProtocol.SALUTATIONS:
_salutation.add_item(SALUTATION_LABELS[id])
_salutation.set_item_metadata(_salutation.item_count - 1, id)
page.add_child(_salutation)
_signature = Label.new()
_signature.position = Vector2(204, 342)
_signature.size = Vector2(288, 36)
_signature.position = Vector2(262, 336)
_signature.size = Vector2(230, 46)
_signature.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_signature.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
page.add_child(_signature)
_attachment_kind = OptionButton.new()
_attachment_kind.position = Vector2(ATTACHMENT_COLUMN_X, 48)

View file

@ -1,4 +1,4 @@
[gd_scene load_steps=16 format=3]
[gd_scene load_steps=17 format=3]
[ext_resource type="Script" path="res://ui/pause_menu.gd" id="1_script"]
[ext_resource type="PackedScene" path="res://ui/settings_panel.tscn" id="2_settings"]
@ -15,6 +15,7 @@
[ext_resource type="Texture2D" path="res://ui/icons/pictograms/settings_dark.png" id="13_settings_dark"]
[ext_resource type="Texture2D" path="res://ui/icons/main_menu/continue.png" id="14_continue"]
[ext_resource type="Texture2D" path="res://ui/icons/pictograms/x_dark.png" id="15_x_dark"]
[ext_resource type="Texture2D" path="res://ui/icons/main_menu/delete_save.png" id="16_delete_save"]
[node name="PauseMenu" type="Control"]
unique_name_in_owner = true
@ -162,7 +163,9 @@ motion_phase = 3.0
[node name="ResetProgressButton" parent="ResponsivePauseStage/PausePresentationScaleRoot/RootPage/BubbleCluster" instance=ExtResource("5_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
text = "reset\nprogress"
texture_filter = 1
icon = ExtResource("16_delete_save")
tooltip_text = "reset progress"
accessibility_name = "reset progress"
neutral_size = Vector2(126, 120)
desktop_anchor = Vector2(245, 365)

View file

@ -6,6 +6,9 @@ const FADE_DURATION: float = 0.55
const UIReferencePresentationType = preload(
"res://ui/ui_reference_presentation.gd"
)
const ControllerFocusPresentationType = preload(
"res://ui/controller_focus_presentation.gd"
)
signal reset_requested
signal return_to_settings_requested
@ -16,9 +19,16 @@ signal return_to_settings_requested
var _fade_tween: Tween
var _fade_generation: int = 0
var _settings_open: bool = false
var _controller_focus_presentation: ControllerFocusPresentationType
func _ready() -> void:
# This control intentionally lives outside the pixelated UI SubViewport, so
# it needs a cursor presenter in its own viewport. Reusing the shared class
# keeps its paw geometry and input policy identical to every other menu.
_controller_focus_presentation = ControllerFocusPresentationType.new()
_controller_focus_presentation.name = "ControllerFocusPresentation"
add_child(_controller_focus_presentation)
_reset_button.pressed.connect(reset_requested.emit)
_reset_button.gui_input.connect(_on_reset_button_gui_input)
get_viewport().size_changed.connect(_update_responsive_layout)

View file

@ -357,7 +357,6 @@ var _selected_inventory_kind: int = -1
var _selected_inventory_identity: StringName
var _inventory_move_kind: int = -1
var _inventory_move_identity: StringName
var _tackle_move_identity: StringName
var _context_tooltip: PanelContainer
var _context_tooltip_label: Label
var _context_tooltip_source: Control
@ -796,6 +795,12 @@ func _handle_controller_ownership_input(event: InputEvent) -> bool:
):
_cancel_active_item_move()
return _try_enter_notepad_controller_ownership()
if _current_section == Section.TACKLE_BOX and accept_event:
# Bait and lures are unlock collections, not movable inventory slots.
# Controller Y owns their contextual equip/notepad action; A is
# intentionally inert and consumed so it cannot activate the Button.
_cancel_controller_accept_hold()
return true
if (
event.is_action_pressed("ui_down")
and _controller_focus_is_on_last_inventory_row()
@ -862,13 +867,6 @@ func _activate_inventory_selection() -> void:
var slot := focus_owner as GeneralInventorySlotType
if slot != null:
_on_general_inventory_slot_activated(slot)
elif _current_section == Section.TACKLE_BOX:
if focus_owner != null and focus_owner.has_meta(
&"controller_tackle_item_id"
):
_activate_tackle_move(StringName(str(
focus_owner.get_meta(&"controller_tackle_item_id")
)))
func _cancel_controller_accept_hold() -> void:
@ -2500,7 +2498,7 @@ func _populate_tackle_column(
item.max_stack,
&"QuantityBadge",
)
button.pressed.connect(_activate_tackle_move.bind(owned.item_id))
button.pressed.connect(_select_tackle_item.bind(owned.item_id))
button.gui_input.connect(
_on_tackle_button_gui_input.bind(owned.item_id, button)
)
@ -2521,7 +2519,6 @@ func _populate_tackle_column(
)
item_list.add_child(button)
_tackle_item_buttons[owned.item_id] = button
_refresh_tackle_move_presentation()
func _tackle_context_text(item: ItemDataType, quantity: int) -> String:
@ -2537,51 +2534,6 @@ func _tackle_context_text(item: ItemDataType, quantity: int) -> String:
return "\n".join(lines)
func _activate_tackle_move(item_id: StringName) -> void:
if _bag == null or item_id.is_empty():
return
var target_slot: int = _bag.get_storage_slot(item_id)
if _tackle_move_identity.is_empty():
if target_slot < 0:
return
_tackle_move_identity = item_id
_selected_tackle_item_id = item_id
_update_tackle_detail()
_refresh_tackle_move_presentation()
var button := _tackle_item_buttons.get(item_id) as Button
if button != null:
_show_inventory_context_tooltip(
button,
"moving %s\nchoose another %s • B cancels" % [
button.accessibility_name
if not button.accessibility_name.is_empty()
else str(
button.get_meta(&"inventory_context_text", "item")
).get_slice("\n", 0),
"bait" if _item_catalog.get_item_by_id(item_id).is_bait()
else "lure",
],
)
return
var source_id: StringName = _tackle_move_identity
_tackle_move_identity = StringName()
_refresh_tackle_move_presentation()
_hide_inventory_context_tooltip()
if source_id == item_id or target_slot < 0:
return
_bag.move_item_to_storage_slot(source_id, target_slot)
func _refresh_tackle_move_presentation() -> void:
for item_id: StringName in _tackle_item_buttons:
var button: Button = _tackle_item_buttons[item_id]
button.modulate = (
Color(1.0, 1.0, 1.0, 0.42)
if item_id == _tackle_move_identity
else Color.WHITE
)
func _on_tackle_button_gui_input(
event: InputEvent,
item_id: StringName,
@ -3912,13 +3864,7 @@ func _cancel_inventory_move(hide_tooltip: bool = true) -> bool:
func _cancel_active_item_move() -> bool:
var canceled: bool = _cancel_inventory_move()
if not _tackle_move_identity.is_empty():
_tackle_move_identity = StringName()
_refresh_tackle_move_presentation()
_hide_inventory_context_tooltip()
canceled = true
return canceled
return _cancel_inventory_move()
func _open_inventory_notepad(

View file

@ -5,6 +5,20 @@ const TOGGLE_STATE_COLOR := Color("c3dfe6")
const DialogControllerNavigationType = preload(
"res://ui/file_dialog_controller_navigation.gd"
)
const MODERATION_BAN_ICON: Texture2D = preload(
"res://ui/icons/moderation_options/moderation_options_ban_light.png"
)
const MODERATION_BLOCK_ICON: Texture2D = preload(
"res://ui/icons/moderation_options/moderation_options_block_light.png"
)
const MODERATION_KICK_ICON: Texture2D = preload(
"res://ui/icons/moderation_options/moderation_options_kick_light.png"
)
const MODERATION_MUTE_ICON: Texture2D = preload(
"res://ui/icons/moderation_options/moderation_options_mute_light.png"
)
const MODERATION_BUTTON_SIZE := Vector2(52.0, 40.0)
const MODERATION_ICON_SIZE: int = 40
enum ControllerZone {
TABS,
@ -492,16 +506,15 @@ func _build_active_rows() -> void:
)
row.add_child(ping)
var mute := Button.new()
mute.text = "unmute" if entry.muted else "mute"
var mute_action: String = "unmute" if entry.muted else "mute"
mute.disabled = entry.is_local_player
mute.pressed.connect(_toggle_mute.bind(entry))
UtilityPageStyle.apply_compact_ocean_button(mute)
_configure_moderation_button(mute, MODERATION_MUTE_ICON, mute_action)
row.add_child(mute)
var block := Button.new()
block.text = "block"
block.disabled = entry.is_local_player
block.pressed.connect(_confirm_block.bind(entry))
UtilityPageStyle.apply_compact_ocean_button(block)
_configure_moderation_button(block, MODERATION_BLOCK_ICON, "block")
row.add_child(block)
if entry.can_manage_operator:
var operator := Button.new()
@ -510,19 +523,50 @@ func _build_active_rows() -> void:
UtilityPageStyle.apply_compact_ocean_button(operator)
row.add_child(operator)
var kick := Button.new()
kick.text = "kick"
kick.disabled = not entry.can_kick
kick.pressed.connect(_confirm_kick.bind(entry))
UtilityPageStyle.apply_compact_ocean_button(kick)
_configure_moderation_button(kick, MODERATION_KICK_ICON, "kick")
row.add_child(kick)
var ban := Button.new()
ban.text = "ban"
ban.disabled = not entry.can_ban
ban.pressed.connect(_confirm_ban.bind(entry))
UtilityPageStyle.apply_compact_ocean_button(ban)
_configure_moderation_button(ban, MODERATION_BAN_ICON, "ban")
row.add_child(ban)
func _configure_moderation_button(
button: Button,
button_icon: Texture2D,
action_label: String,
) -> void:
button.text = ""
button.icon = button_icon
button.tooltip_text = action_label
button.accessibility_name = action_label
button.expand_icon = true
button.icon_alignment = HORIZONTAL_ALIGNMENT_CENTER
button.vertical_icon_alignment = VERTICAL_ALIGNMENT_CENTER
button.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
UtilityPageStyle.apply_compact_ocean_button(button)
button.custom_minimum_size = MODERATION_BUTTON_SIZE
button.add_theme_constant_override("icon_max_width", MODERATION_ICON_SIZE)
button.add_theme_color_override(
"icon_disabled_color",
Color(1.0, 1.0, 1.0, 0.78),
)
for state: StringName in [
&"normal", &"hover", &"pressed", &"focus", &"disabled",
]:
var style := button.get_theme_stylebox(state).duplicate() as StyleBoxFlat
if style == null:
continue
style.content_margin_left = 6.0
style.content_margin_right = 6.0
style.content_margin_top = 0.0
style.content_margin_bottom = 0.0
button.add_theme_stylebox_override(state, style)
func _build_session_artwork_controls() -> void:
var counts: Vector2i = _service.get_session_artwork_counts()
var row := _make_row()

View file

@ -71,6 +71,7 @@ var _apply_button: Button
var _revert_button: Button
var _defaults_button: Button
var _reset_view_button: Button
var _reset_view_controller_hint: Label
var _discard_confirmation: PanelContainer
var _confirmation_label: Label
var _confirmation_confirm: Button
@ -226,6 +227,15 @@ func consume_escape() -> bool:
func handle_controller_input(event: InputEvent) -> bool:
if not _profile_active or not _profile_interactive:
return false
var reset_view_pressed := _event_matches_controller_press(
event,
&"sneak",
ControllerMappingManagerType.ROLE_RIGHT_STICK_CLICK,
JOY_BUTTON_RIGHT_STICK,
)
if reset_view_pressed:
_preview.reset_view()
return true
if (
event.is_action_pressed(&"ui_up")
or event.is_action_pressed(&"ui_down")
@ -794,6 +804,17 @@ func _build_ui() -> void:
UtilityPageStyle.apply_compact_ocean_button(_reset_view_button)
_reset_view_button.add_theme_font_size_override("font_size", 22)
preview_layer.add_child(_reset_view_button)
_reset_view_controller_hint = Label.new()
_reset_view_controller_hint.name = "ResetViewControllerHint"
_reset_view_controller_hint.text = "Reset View: RS"
_reset_view_controller_hint.mouse_filter = Control.MOUSE_FILTER_IGNORE
_reset_view_controller_hint.focus_mode = Control.FOCUS_NONE
_reset_view_controller_hint.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_reset_view_controller_hint.add_theme_font_size_override("font_size", 14)
_reset_view_controller_hint.add_theme_color_override(
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
)
preview_stack.add_child(_reset_view_controller_hint)
_discard_confirmation = PanelContainer.new()
_discard_confirmation.visible = false

View file

@ -116,6 +116,7 @@ var _on_screen_keyboard_enabled: bool = false
var _chat_dock_right: bool = false
var _chat_mobile_mode: bool = false
var _paint_dock_right: bool = true
var _presentation_layout_edited: bool = false
var _fullscreen_enabled: bool = false
var _master_volume: float = 1.0
var _music_volume: float = 1.0
@ -941,6 +942,8 @@ func _apply_settings() -> void:
edited.chat_dock_right = _chat_dock_right
edited.chat_mobile_mode = _chat_mobile_mode
edited.paint_dock_right = _paint_dock_right
if _presentation_layout_edited:
edited.presentation_layout_customized = true
edited.fullscreen_enabled = _fullscreen_enabled
edited.master_volume = _master_volume
edited.music_volume = _music_volume
@ -1000,6 +1003,7 @@ func _load_controls() -> void:
_auto_click_interval_slider.set_value_no_signal(
_auto_click_interval_value
)
_presentation_layout_edited = false
_settings_manager.restore_audio_levels()
_refresh_value_labels()
@ -1081,14 +1085,17 @@ func _set_ui_pixelation(pixel_size: int) -> void:
func _on_chat_dock_selected(index: int) -> void:
_chat_dock_right = _chat_dock.get_item_id(index) == 1
_presentation_layout_edited = true
func _on_chat_mode_selected(index: int) -> void:
_chat_mobile_mode = _chat_mode.get_item_id(index) == 1
_presentation_layout_edited = true
func _on_paint_dock_selected(index: int) -> void:
_paint_dock_right = _paint_dock.get_item_id(index) == 1
_presentation_layout_edited = true
func _set_fullscreen(enabled: bool) -> void:

View file

@ -5,6 +5,9 @@ const UtilityPageStyleType = preload("res://ui/utility_page_style.gd")
const ControllerVirtualCursorType = preload(
"res://ui/controller_virtual_cursor.gd"
)
const LockedContentPresentationType = preload(
"res://ui/components/locked_content_presentation.gd"
)
const MARKER_MODE_ICON: Texture2D = preload(
"res://items/icons/art/art_kit_marker.png"
)
@ -29,6 +32,10 @@ const GRID_SIZE_ICONS: Dictionary[int, Texture2D] = {
const TOOLBAR_WIDTH: float = 510.0
const TOOLBAR_HEIGHT: float = 373.0
const COLOR_RAIL_WIDTH: float = 46.0
const POPUP_LOCK_ICON_SIZE: int = 24
const POPUP_LOCK_CANVAS_SIZE: int = 40
const POPUP_LOCK_ICON_ALPHA: float = 0.72
const COLOR_LOCK_ICON_ALPHA: float = 0.34
@onready var _mode_button: Button = %ModeButton
@onready var _brush_option: OptionButton = %BrushOption
@ -50,6 +57,8 @@ var _marker_mode_material: ShaderMaterial
var _brush_popup_cursor: ControllerVirtualCursorType
var _grid_popup_cursor: ControllerVirtualCursorType
var _applied_marker_icon_color: Color = Color(-1.0, -1.0, -1.0, -1.0)
var _brush_option_icons: Dictionary[int, Texture2D] = {}
var _popup_lock_icon: Texture2D
func _ready() -> void:
@ -98,6 +107,10 @@ func _ready() -> void:
_configure_pointer_only_controls()
_brush_popup_cursor = _add_popup_cursor(_brush_option.get_popup())
_grid_popup_cursor = _add_popup_cursor(_grid_option.get_popup())
_popup_lock_icon = LockedContentPresentationType.make_icon_texture(
POPUP_LOCK_ICON_SIZE,
POPUP_LOCK_CANVAS_SIZE,
)
_build_options()
_apply_marker_color_to_brush_icons(
SurfaceDrawingPalette.get_color(SurfaceDrawingPalette.DEFAULT_COLOR_ID)
@ -307,19 +320,49 @@ func _build_options() -> void:
button.pressed.connect(_select_color.bind(color_id))
_color_list.add_child(button)
_color_buttons[color_id] = button
_add_color_lock_icon(button)
_apply_color_button_style(button, color_id, false)
func _add_color_lock_icon(button: Button) -> void:
var icon_size := Vector2.ONE * (
LockedContentPresentationType.COMPACT_ICON_SIZE
)
var icon := TextureRect.new()
icon.name = "UnlockStateIcon"
icon.texture = LockedContentPresentationType.ICON
icon.set_anchors_preset(Control.PRESET_CENTER)
icon.offset_left = -icon_size.x * 0.5
icon.offset_top = -icon_size.y * 0.5
icon.offset_right = icon_size.x * 0.5
icon.offset_bottom = icon_size.y * 0.5
icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
icon.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
icon.modulate = Color(
UtilityPageStyleType.OCEAN_TEXT_SECONDARY,
COLOR_LOCK_ICON_ALPHA,
)
icon.z_index = 1
icon.visible = false
button.add_child(icon)
func _apply_marker_color_to_brush_icons(marker_color: Color) -> void:
if _applied_marker_icon_color.is_equal_approx(marker_color):
return
_applied_marker_icon_color = marker_color
_brush_option_icons.clear()
for index: int in range(_brush_option.item_count):
var brush_size: int = _brush_option.get_item_id(index)
_brush_option.set_item_icon(
index,
_channel_masked_icon(BRUSH_SIZE_ICONS[brush_size], marker_color),
_brush_option_icons[brush_size] = _channel_masked_icon(
BRUSH_SIZE_ICONS[brush_size], marker_color
)
if _unlocks == null or _unlocks.is_brush_size_unlocked(brush_size):
_brush_option.set_item_icon(
index, _brush_option_icons[brush_size]
)
func _channel_masked_icon(source: Texture2D, marker_color: Color) -> Texture2D:
@ -371,18 +414,26 @@ func _refresh_unlocks() -> void:
var brush_popup: PopupMenu = _brush_option.get_popup()
for index: int in range(_brush_option.item_count):
var brush_size: int = _brush_option.get_item_id(index)
brush_popup.set_item_disabled(
index, not _unlocks.is_brush_size_unlocked(brush_size)
_apply_popup_unlock_state(
brush_popup,
index,
_unlocks.is_brush_size_unlocked(brush_size),
_brush_option_icons.get(brush_size, BRUSH_SIZE_ICONS[brush_size]),
)
var grid_popup: PopupMenu = _grid_option.get_popup()
for index: int in range(_grid_option.item_count):
var grid_size: int = _grid_option.get_item_id(index)
grid_popup.set_item_disabled(
index, not _unlocks.is_grid_size_unlocked(grid_size)
_apply_popup_unlock_state(
grid_popup,
index,
_unlocks.is_grid_size_unlocked(grid_size),
GRID_SIZE_ICONS[grid_size],
)
for color_id: StringName in _color_buttons:
var button: Button = _color_buttons[color_id]
button.disabled = not _unlocks.is_color_unlocked(color_id)
var lock_icon := button.get_node("UnlockStateIcon") as TextureRect
lock_icon.visible = button.disabled
_apply_color_button_style(
button,
color_id,
@ -394,6 +445,25 @@ func _refresh_unlocks() -> void:
)
func _apply_popup_unlock_state(
popup: PopupMenu,
index: int,
unlocked: bool,
unlocked_icon: Texture2D,
) -> void:
popup.set_item_disabled(index, not unlocked)
popup.set_item_icon(index, unlocked_icon if unlocked else _popup_lock_icon)
popup.set_item_icon_modulate(
index,
Color.WHITE
if unlocked
else Color(
UtilityPageStyleType.OCEAN_TEXT_SECONDARY,
POPUP_LOCK_ICON_ALPHA,
),
)
func _apply_color_button_style(
button: Button,
color_id: StringName,
@ -412,7 +482,9 @@ func _apply_color_button_style(
style.set_corner_radius_all(17)
button.add_theme_stylebox_override(style_name, style)
var disabled_style := StyleBoxFlat.new()
disabled_style.bg_color = UtilityPageStyleType.OCEAN_DISABLED
disabled_style.bg_color = (
LockedContentPresentationType.disabled_background_color()
)
disabled_style.set_corner_radius_all(17)
button.add_theme_stylebox_override("disabled", disabled_style)

View file

@ -10,6 +10,9 @@ const SettingsPanelType = preload("res://ui/settings_panel.gd")
const FullscreenMenuPresentationType = preload(
"res://ui/fullscreen_menu_presentation.gd"
)
const ControllerFocusNavigationType = preload(
"res://ui/controller_focus_navigation.gd"
)
const BubbleButtonType = preload(
"res://ui/components/bubble_menu/bubble_button.gd"
)
@ -85,7 +88,6 @@ const BUBBLE_COMPACT_HEIGHT_THRESHOLD: float = 560.0
const START_PROMPT_MIN_SCALE: float = 0.985
const START_PROMPT_MAX_SCALE: float = 1.015
const START_PROMPT_CYCLE_SECONDS: float = 3.0
const DISABLED_BUBBLE_LABEL_ALPHA: float = 0.55
const INTRO_PROMPT_FADE_DURATION: float = 0.55
const INTRO_BUBBLE_TRAVEL_DURATION: float = 2.40
const INTRO_BRANDING_TRAVEL_DURATION: float = 2.0
@ -110,7 +112,6 @@ enum ConfirmationAction {
@onready var _delete_button: BubbleButtonType = %DeleteSaveButton
@onready var _quit_button: BubbleButtonType = %QuitButton
@onready var _join_game_button: BubbleButtonType = %JoinGameButton
@onready var _delete_save_label: Label = %DeleteSaveLabel
@onready var _feedback_label: RichTextLabel = %FeedbackLabel
@onready var _confirmation_page: TitleConfirmationBubblePageType = (
%ConfirmationPage
@ -446,9 +447,6 @@ func _update_title_layout() -> void:
)
if not _title_settings_transition_active:
call_deferred("_capture_title_bubble_rest_position")
_delete_save_label.text = (
"delete\nsave" if compact_layout else "delete save"
)
if _awaiting_start_input and not _title_entry_transition_active:
_schedule_intro_presentation()
_update_world_preview_resolution()
@ -1415,6 +1413,10 @@ func _set_title_bubbles_interactive(interactive: bool) -> void:
if interactive
else Control.MOUSE_FILTER_IGNORE
)
if interactive:
ControllerFocusNavigationType.configure_spatial_neighbors(
_get_title_buttons()
)
func _capture_title_bubble_rest_position() -> void:
@ -1470,9 +1472,6 @@ func _refresh_save_inspection() -> void:
_inspection = _save_manager.inspect_save()
_continue_button.disabled = not _inspection.can_continue()
_delete_button.disabled = not _inspection.can_delete()
_delete_save_label.modulate.a = (
DISABLED_BUBBLE_LABEL_ALPHA if _delete_button.disabled else 1.0
)
_feedback_label.text = _center_feedback_text(_inspection.message)
if _inspection.status == SaveInspectionType.Status.VALID_SUPPORTED:
_feedback_label.text = _get_continue_stats_text()

View file

@ -1,4 +1,4 @@
[gd_scene load_steps=22 format=3]
[gd_scene load_steps=23 format=3]
[ext_resource type="Script" path="res://ui/title_screen.gd" id="1_script"]
[ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"]
@ -18,6 +18,7 @@
[ext_resource type="Texture2D" path="res://ui/icons/main_menu/continue.png" id="16_continue"]
[ext_resource type="Texture2D" path="res://ui/icons/main_menu/new_game.png" id="17_new_game"]
[ext_resource type="Texture2D" path="res://ui/icons/main_menu/credits.png" id="18_credits"]
[ext_resource type="Texture2D" path="res://ui/icons/main_menu/delete_save.png" id="19_delete_save"]
[sub_resource type="ShaderMaterial" id="ShaderMaterial_title_water"]
shader = ExtResource("4_water_shader")
@ -384,13 +385,16 @@ offset_left = -11.0
offset_top = 122.0
offset_right = 123.0
offset_bottom = 248.0
texture_filter = 1
icon = ExtResource("19_delete_save")
tooltip_text = "delete save"
accessibility_name = "delete save"
script = ExtResource("7_bubble_button")
profile = ExtResource("9_bubble_profile")
neutral_size = Vector2(134, 126)
desktop_anchor = Vector2(56, 185)
compact_anchor = Vector2(53, 196)
compact_minimum_size = Vector2(86, 86)
label_control_path = NodePath("DeleteSaveLabel")
minimum_font_size = 15
maximum_font_size = 23
vertical_amplitude = 5.1
@ -399,21 +403,6 @@ motion_phase = 3.7
deformation_amplitude = 0.018
deformation_period = 5.4
[node name="DeleteSaveLabel" type="Label" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent/ButtonCenter/BubbleLayoutSlot/BubbleMotionRoot/BubbleField/DeleteSaveButton"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.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 = -3
text = "delete save"
horizontal_alignment = 1
vertical_alignment = 1
[node name="QuitButton" type="Button" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent/ButtonCenter/BubbleLayoutSlot/BubbleMotionRoot/BubbleField"]
unique_name_in_owner = true
offset_left = 275.0

View file

@ -49,6 +49,9 @@ func _ready() -> void:
)
var controller_focus_presentation := ControllerFocusPresentationType.new()
_ui_root.add_child(controller_focus_presentation)
_game_ui.virtual_pointer_mode_changed.connect(
controller_focus_presentation.set_virtual_pointer_active
)
var root_viewport: Viewport = get_viewport()
root_viewport.size_changed.connect(_resize_presentation)
_resize_presentation()