diff --git a/art/exported/environment/terrain/starter_island.glb b/art/exported/environment/terrain/starter_island.glb index 9612f8d..8c71154 100644 Binary files a/art/exported/environment/terrain/starter_island.glb and b/art/exported/environment/terrain/starter_island.glb differ diff --git a/art/exported/environment/textures/grass_olive_tile.png b/art/exported/environment/textures/grass_olive_tile.png index affb5dc..8d9761c 100644 Binary files a/art/exported/environment/textures/grass_olive_tile.png and b/art/exported/environment/textures/grass_olive_tile.png differ diff --git a/art/source/characters/base/netfishing_base_character.blend b/art/source/characters/base/netfishing_base_character.blend new file mode 100644 index 0000000..2da3679 Binary files /dev/null and b/art/source/characters/base/netfishing_base_character.blend differ diff --git a/art/source/characters/base/netfishing_base_character.blend.import b/art/source/characters/base/netfishing_base_character.blend.import new file mode 100644 index 0000000..f6df489 --- /dev/null +++ b/art/source/characters/base/netfishing_base_character.blend.import @@ -0,0 +1,63 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://cb4bcakhoprm8" +path="res://.godot/imported/netfishing_base_character.blend-65bf3bc5b77cc1a37fa3f81ebd808d37.scn" + +[deps] + +source_file="res://art/source/characters/base/netfishing_base_character.blend" +dest_files=["res://.godot/imported/netfishing_base_character.blend-65bf3bc5b77cc1a37fa3f81ebd808d37.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/root_script=null +mesh_library/use_node_names_as_mesh_names=false +array_mesh/deduplicate_surfaces=true +nodes/apply_root_scale=true +nodes/root_scale=1.0 +nodes/import_as_skeleton_bones=false +nodes/use_name_suffixes=true +nodes/use_node_type_suffixes=true +meshes/ensure_tangents=true +meshes/generate_lods=true +meshes/create_shadow_meshes=true +meshes/light_baking=1 +meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false +skins/use_named_skins=true +animation/import=true +animation/fps=30 +animation/trimming=false +animation/remove_immutable_tracks=true +animation/import_rest_as_RESET=false +import_script/path="" +materials/extract=0 +materials/extract_format=0 +materials/extract_path="" +_subresources={} +blender/nodes/visible=0 +blender/nodes/active_collection_only=false +blender/nodes/punctual_lights=true +blender/nodes/cameras=true +blender/nodes/custom_properties=true +blender/nodes/modifiers=1 +blender/meshes/vertex_colors=1 +blender/meshes/uvs=true +blender/meshes/normals=true +blender/meshes/export_geometry_nodes_instances=false +blender/meshes/gpu_instances=false +blender/meshes/tangents=true +blender/meshes/skins=2 +blender/meshes/export_bones_deforming_mesh_only=false +blender/materials/unpack_enabled=true +blender/materials/export_materials=1 +blender/animation/limit_playback=true +blender/animation/always_sample=true +blender/animation/group_tracks=true +gltf/naming_version=2 +gltf/texture_map_mode=1 diff --git a/art/source/environment/terrain/starter_island.blend b/art/source/environment/terrain/starter_island.blend index aeee334..3eb342d 100644 Binary files a/art/source/environment/terrain/starter_island.blend and b/art/source/environment/terrain/starter_island.blend differ diff --git a/ui/components/bubble_menu/bubble_confirmation_page.gd b/ui/components/bubble_menu/bubble_confirmation_page.gd new file mode 100644 index 0000000..cbb0ede --- /dev/null +++ b/ui/components/bubble_menu/bubble_confirmation_page.gd @@ -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 diff --git a/ui/components/bubble_menu/bubble_confirmation_page.gd.uid b/ui/components/bubble_menu/bubble_confirmation_page.gd.uid new file mode 100644 index 0000000..ef9111c --- /dev/null +++ b/ui/components/bubble_menu/bubble_confirmation_page.gd.uid @@ -0,0 +1 @@ +uid://c5q6yypya7fr1 diff --git a/ui/components/bubble_menu/bubble_confirmation_page.tscn b/ui/components/bubble_menu/bubble_confirmation_page.tscn new file mode 100644 index 0000000..8f3e0fe --- /dev/null +++ b/ui/components/bubble_menu/bubble_confirmation_page.tscn @@ -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 diff --git a/ui/components/bubble_menu/bubble_content_shell.gd b/ui/components/bubble_menu/bubble_content_shell.gd new file mode 100644 index 0000000..f20bfb5 --- /dev/null +++ b/ui/components/bubble_menu/bubble_content_shell.gd @@ -0,0 +1,49 @@ +class_name BubbleContentShell +extends PanelContainer + +@export var profile: BubbleMenuProfile +@export_range(0.0, 96.0, 1.0) var content_margin: float = 24.0 +@export_range(0, 512, 1) var corner_radius: int = 72 +@export_range(0, 12, 1) var border_width: int = 3 + +var _background_visible: bool = true + + +func _ready() -> void: + apply_profile() + + +func apply_profile() -> void: + if profile == null: + return + var style := StyleBoxFlat.new() + style.bg_color = profile.normal_fill + style.border_color = profile.normal_border + style.border_width_left = border_width + style.border_width_top = border_width + style.border_width_right = border_width + style.border_width_bottom = border_width + style.corner_radius_top_left = corner_radius + style.corner_radius_top_right = corner_radius + style.corner_radius_bottom_right = corner_radius + style.corner_radius_bottom_left = corner_radius + style.content_margin_left = content_margin + style.content_margin_top = content_margin + style.content_margin_right = content_margin + style.content_margin_bottom = content_margin + add_theme_stylebox_override("panel", style) + + +func set_background_visible(background_visible: bool) -> void: + _background_visible = background_visible + if not is_node_ready() or profile == null: + return + if _background_visible: + apply_profile() + return + var transparent_style := StyleBoxEmpty.new() + transparent_style.content_margin_left = content_margin + transparent_style.content_margin_top = content_margin + transparent_style.content_margin_right = content_margin + transparent_style.content_margin_bottom = content_margin + add_theme_stylebox_override("panel", transparent_style) diff --git a/ui/components/bubble_menu/bubble_content_shell.gd.uid b/ui/components/bubble_menu/bubble_content_shell.gd.uid new file mode 100644 index 0000000..fbbdbbf --- /dev/null +++ b/ui/components/bubble_menu/bubble_content_shell.gd.uid @@ -0,0 +1 @@ +uid://bsplayercontentshell diff --git a/ui/components/bubble_menu/bubble_content_shell.tscn b/ui/components/bubble_menu/bubble_content_shell.tscn new file mode 100644 index 0000000..2feac61 --- /dev/null +++ b/ui/components/bubble_menu/bubble_content_shell.tscn @@ -0,0 +1,8 @@ +[gd_scene load_steps=3 format=3] + +[ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_content_shell.gd" id="1_shell"] +[ext_resource type="Resource" path="res://ui/components/bubble_menu/bubble_menu_profile.tres" id="2_profile"] + +[node name="BubbleContentShell" type="PanelContainer"] +script = ExtResource("1_shell") +profile = ExtResource("2_profile") diff --git a/ui/components/bubble_menu/bubble_information_panel.gd b/ui/components/bubble_menu/bubble_information_panel.gd new file mode 100644 index 0000000..22989a9 --- /dev/null +++ b/ui/components/bubble_menu/bubble_information_panel.gd @@ -0,0 +1,21 @@ +class_name BubbleInformationPanel +extends PanelContainer + +@export var profile: BubbleMenuProfile +@export_range(0.0, 96.0, 1.0) var content_margin: float = 20.0 +@export_range(0, 512, 1) var corner_radius: int = 96 + + +func _ready() -> void: + if profile == null: + return + var style: StyleBoxFlat = profile.make_normal_style() + style.corner_radius_top_left = corner_radius + style.corner_radius_top_right = corner_radius + style.corner_radius_bottom_left = corner_radius + style.corner_radius_bottom_right = corner_radius + style.content_margin_left = content_margin + style.content_margin_top = content_margin + style.content_margin_right = content_margin + style.content_margin_bottom = content_margin + add_theme_stylebox_override("panel", style) diff --git a/ui/components/bubble_menu/bubble_information_panel.gd.uid b/ui/components/bubble_menu/bubble_information_panel.gd.uid new file mode 100644 index 0000000..7586d5c --- /dev/null +++ b/ui/components/bubble_menu/bubble_information_panel.gd.uid @@ -0,0 +1 @@ +uid://bubbleinformationpanel diff --git a/ui/components/bubble_menu/bubble_information_panel.tscn b/ui/components/bubble_menu/bubble_information_panel.tscn new file mode 100644 index 0000000..df6619c --- /dev/null +++ b/ui/components/bubble_menu/bubble_information_panel.tscn @@ -0,0 +1,9 @@ +[gd_scene load_steps=3 format=3] + +[ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_information_panel.gd" id="1_panel"] +[ext_resource type="Resource" path="res://ui/components/bubble_menu/bubble_menu_profile.tres" id="2_profile"] + +[node name="BubbleInformationPanel" type="PanelContainer"] +mouse_filter = 1 +script = ExtResource("1_panel") +profile = ExtResource("2_profile") diff --git a/ui/components/bubble_menu/bubble_inventory_cell.gd b/ui/components/bubble_menu/bubble_inventory_cell.gd new file mode 100644 index 0000000..96e14aa --- /dev/null +++ b/ui/components/bubble_menu/bubble_inventory_cell.gd @@ -0,0 +1,125 @@ +class_name BubbleInventoryCell +extends Button + +@export var profile: BubbleMenuProfile + +@onready var _icon: TextureRect = %Icon +@onready var _primary_label: Label = %PrimaryLabel +@onready var _secondary_label: Label = %SecondaryLabel +@onready var _favorite_marker: Label = %FavoriteMarker +@onready var _selected_marker: Label = %SelectedMarker + +var _selected: bool = false +var _focused_item: bool = false +var _compact: bool = false +var _icon_texture: Texture2D +var _primary_text: String = "" +var _secondary_text: String = "" +var _favorite: bool = false + + +func _ready() -> void: + focus_entered.connect(_refresh_style) + focus_exited.connect(_refresh_style) + _apply_compact_layout() + _apply_content() + _refresh_style() + + +func configure( + icon_texture: Texture2D, + primary_text: String, + secondary_text: String, +) -> void: + _icon_texture = icon_texture + _primary_text = primary_text + _secondary_text = secondary_text + if is_node_ready(): + _apply_content() + + +func set_item_state( + selected: bool, + focused_item: bool, + favorite: bool, +) -> void: + _selected = selected + _focused_item = focused_item + _favorite = favorite + if is_node_ready(): + _apply_content() + _refresh_style() + + +func set_compact(compact: bool) -> void: + _compact = compact + if not is_node_ready(): + custom_minimum_size = ( + Vector2(104.0, 98.0) + if compact + else Vector2(144.0, 136.0) + ) + return + _apply_compact_layout() + + +func _apply_compact_layout() -> void: + custom_minimum_size = ( + Vector2(104.0, 98.0) + if _compact + else Vector2(144.0, 136.0) + ) + _icon.custom_minimum_size = ( + Vector2(72.0, 44.0) + if _compact + else Vector2(104.0, 68.0) + ) + _primary_label.add_theme_font_size_override( + "font_size", + 15 if _compact else 18, + ) + _secondary_label.add_theme_font_size_override( + "font_size", + 13 if _compact else 15, + ) + + +func _apply_content() -> void: + _icon.texture = _icon_texture + _primary_label.text = _primary_text + _secondary_label.text = _secondary_text + _selected_marker.visible = _selected + _favorite_marker.visible = _favorite + + +func _has_point(point: Vector2) -> bool: + var radius: Vector2 = size * 0.5 + if radius.x <= 0.0 or radius.y <= 0.0: + return false + var normalized: Vector2 = (point - radius) / radius + return normalized.length_squared() <= 1.0 + + +func _refresh_style() -> void: + if profile == null: + return + var normal_style: StyleBoxFlat = profile.make_normal_style() + var border_width: int = ( + profile.emphasized_border_width + if _selected or _focused_item or has_focus() + else profile.normal_border_width + ) + normal_style.border_width_left = border_width + normal_style.border_width_top = border_width + normal_style.border_width_right = border_width + normal_style.border_width_bottom = border_width + if _selected: + normal_style.bg_color = profile.pressed_fill + if _focused_item: + normal_style.border_color = profile.text_color + add_theme_stylebox_override("normal", normal_style) + add_theme_stylebox_override("hover", profile.make_hover_style()) + add_theme_stylebox_override("focus", normal_style) + add_theme_stylebox_override("pressed", profile.make_pressed_style()) + add_theme_stylebox_override("disabled", profile.make_disabled_style()) + add_theme_color_override("font_color", profile.text_color) diff --git a/ui/components/bubble_menu/bubble_inventory_cell.gd.uid b/ui/components/bubble_menu/bubble_inventory_cell.gd.uid new file mode 100644 index 0000000..5455c81 --- /dev/null +++ b/ui/components/bubble_menu/bubble_inventory_cell.gd.uid @@ -0,0 +1 @@ +uid://bubbleinventorycell diff --git a/ui/components/bubble_menu/bubble_inventory_cell.tscn b/ui/components/bubble_menu/bubble_inventory_cell.tscn new file mode 100644 index 0000000..d6774a3 --- /dev/null +++ b/ui/components/bubble_menu/bubble_inventory_cell.tscn @@ -0,0 +1,78 @@ +[gd_scene load_steps=3 format=3] + +[ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_inventory_cell.gd" id="1_cell"] +[ext_resource type="Resource" path="res://ui/components/bubble_menu/bubble_menu_profile.tres" id="2_profile"] + +[node name="BubbleInventoryCell" type="Button"] +custom_minimum_size = Vector2(144, 136) +toggle_mode = true +script = ExtResource("1_cell") +profile = ExtResource("2_profile") + +[node name="Content" type="VBoxContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 17.0 +offset_top = 14.0 +offset_right = -17.0 +offset_bottom = -14.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 +theme_override_constants/separation = 2 + +[node name="Icon" type="TextureRect" parent="Content"] +unique_name_in_owner = true +custom_minimum_size = Vector2(104, 68) +layout_mode = 2 +size_flags_vertical = 3 +mouse_filter = 2 +texture_filter = 1 +expand_mode = 1 +stretch_mode = 5 + +[node name="PrimaryLabel" type="Label" parent="Content"] +unique_name_in_owner = true +layout_mode = 2 +mouse_filter = 2 +theme_override_font_sizes/font_size = 18 +horizontal_alignment = 1 +text_overrun_behavior = 3 + +[node name="SecondaryLabel" type="Label" parent="Content"] +unique_name_in_owner = true +layout_mode = 2 +mouse_filter = 2 +theme_override_colors/font_color = Color(0.16, 0.25, 0.29, 0.78) +theme_override_font_sizes/font_size = 15 +horizontal_alignment = 1 + +[node name="FavoriteMarker" type="Label" parent="."] +unique_name_in_owner = true +visible = false +layout_mode = 0 +offset_left = 102.0 +offset_top = 12.0 +offset_right = 132.0 +offset_bottom = 42.0 +mouse_filter = 2 +theme_override_colors/font_color = Color(1, 0.82, 0.4, 1) +theme_override_font_sizes/font_size = 23 +text = "★" +horizontal_alignment = 1 + +[node name="SelectedMarker" type="Label" parent="."] +unique_name_in_owner = true +visible = false +layout_mode = 0 +offset_left = 14.0 +offset_top = 10.0 +offset_right = 42.0 +offset_bottom = 38.0 +mouse_filter = 2 +theme_override_colors/font_color = Color(0.035, 0.145, 0.22, 1) +theme_override_font_sizes/font_size = 22 +text = "✓" +horizontal_alignment = 1 diff --git a/ui/components/bubble_menu/bubble_status_bubble.gd b/ui/components/bubble_menu/bubble_status_bubble.gd new file mode 100644 index 0000000..2a475fc --- /dev/null +++ b/ui/components/bubble_menu/bubble_status_bubble.gd @@ -0,0 +1,18 @@ +class_name BubbleStatusBubble +extends PanelContainer + +@export var profile: BubbleMenuProfile + +@onready var _heading: Label = %Heading +@onready var _value: Label = %Value + + +func _ready() -> void: + mouse_filter = Control.MOUSE_FILTER_IGNORE + if profile != null: + add_theme_stylebox_override("panel", profile.make_normal_style()) + + +func set_content(heading: String, value: String) -> void: + _heading.text = heading + _value.text = value diff --git a/ui/components/bubble_menu/bubble_status_bubble.gd.uid b/ui/components/bubble_menu/bubble_status_bubble.gd.uid new file mode 100644 index 0000000..b67ba57 --- /dev/null +++ b/ui/components/bubble_menu/bubble_status_bubble.gd.uid @@ -0,0 +1 @@ +uid://bubblestatusbubble diff --git a/ui/components/bubble_menu/bubble_status_bubble.tscn b/ui/components/bubble_menu/bubble_status_bubble.tscn new file mode 100644 index 0000000..87858ec --- /dev/null +++ b/ui/components/bubble_menu/bubble_status_bubble.tscn @@ -0,0 +1,32 @@ +[gd_scene load_steps=3 format=3] + +[ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_status_bubble.gd" id="1_status"] +[ext_resource type="Resource" path="res://ui/components/bubble_menu/bubble_menu_profile.tres" id="2_profile"] + +[node name="BubbleStatusBubble" type="PanelContainer"] +custom_minimum_size = Vector2(128, 88) +mouse_filter = 2 +script = ExtResource("1_status") +profile = ExtResource("2_profile") + +[node name="Labels" type="VBoxContainer" parent="."] +layout_mode = 2 +mouse_filter = 2 +theme_override_constants/separation = 0 +alignment = 1 + +[node name="Heading" type="Label" parent="Labels"] +unique_name_in_owner = true +layout_mode = 2 +mouse_filter = 2 +theme_override_font_sizes/font_size = 15 +text = "status" +horizontal_alignment = 1 + +[node name="Value" type="Label" parent="Labels"] +unique_name_in_owner = true +layout_mode = 2 +mouse_filter = 2 +theme_override_font_sizes/font_size = 21 +text = "0" +horizontal_alignment = 1 diff --git a/ui/pause_menu.gd b/ui/pause_menu.gd index 9d569af..c3101b8 100644 --- a/ui/pause_menu.gd +++ b/ui/pause_menu.gd @@ -15,6 +15,9 @@ const SettingsManagerType = preload( "res://settings/player_settings_manager.gd" ) const SettingsPanelType = preload("res://ui/settings_panel.gd") +const BubbleConfirmationPageType = preload( + "res://ui/components/bubble_menu/bubble_confirmation_page.gd" +) const FishingSpotType = preload("res://fishing/fishing_spot.gd") signal return_to_title_requested @@ -46,11 +49,9 @@ enum CloseReason { @onready var _transition_flurry: BubbleTransitionFlurry = ( %TransitionBubbleLayer ) -@onready var _confirmation_panel: PanelContainer = %ConfirmationPanel -@onready var _confirmation_title: Label = %ConfirmationTitle -@onready var _confirmation_text: Label = %ConfirmationText -@onready var _confirm_button: Button = %ConfirmButton -@onready var _cancel_button: Button = %CancelConfirmButton +@onready var _confirmation_page: BubbleConfirmationPageType = ( + %ConfirmationPage +) @onready var _feedback: Label = %FeedbackLabel @onready var _save_button: BubbleButton = %SaveButton @@ -70,9 +71,6 @@ var _root_transition_generation: int = 0 var _closing_menu: bool = false var _backdrop_fade: Tween var _backdrop_fade_generation: int = 0 -var _confirmation_fade: Tween -var _confirmation_fade_generation: int = 0 -var _confirmation_fade_active: bool = false func _ready() -> void: @@ -82,16 +80,15 @@ func _ready() -> void: %ReturnToTitleButton.pressed.connect(_confirm_return_to_title) %ResetProgressButton.pressed.connect(_confirm_reset_progress) %QuitButton.pressed.connect(_request_quit) - _confirm_button.pressed.connect(_accept_confirmation) - _cancel_button.pressed.connect(_close_confirmation) + _confirmation_page.confirmed.connect(_accept_confirmation) + _confirmation_page.cancelled.connect(_close_confirmation) _settings_panel.applied.connect(_on_settings_applied) _settings_panel.closed.connect(_on_settings_closed) _settings_panel.navigation_transition_started.connect( _emit_transition_flurry ) _dim_background.color.a = 0.0 - _confirmation_panel.modulate.a = 0.0 - _set_confirmation_interactive(false) + _confirmation_page.hide_page() resized.connect(_update_responsive_pause_stage) call_deferred("_update_responsive_pause_stage") @@ -124,7 +121,7 @@ func open_menu() -> void: Input.mouse_mode = Input.MOUSE_MODE_VISIBLE _feedback.text = "" _settings_panel.hide() - _confirmation_panel.hide() + _confirmation_page.hide_page() _confirmation_action = ConfirmationAction.NONE _action_in_progress = false _root_page.hide_page() @@ -168,9 +165,9 @@ func close_menu( func handle_escape() -> bool: if not visible: return false - if _root_transition_active or _confirmation_fade_active: + if _root_transition_active or _confirmation_page.is_transitioning(): return true - if _confirmation_panel.visible: + if _confirmation_page.visible: _close_confirmation() elif _settings_panel.visible: _settings_panel.handle_back() @@ -281,53 +278,57 @@ func _open_confirmation( if _action_in_progress or _root_transition_active: return _confirmation_action = action - _confirmation_title.text = title - _confirmation_text.text = message - _confirm_button.text = confirm_text - _confirm_button.theme_type_variation = ( - &"DangerButton" if dangerous else StringName() + _confirmation_page.configure( + "%s\n\n%s" % [title, message], + confirm_text, + "cancel", + ( + BubbleConfirmationPageType.InitialFocus.CANCEL + if dangerous + else BubbleConfirmationPageType.InitialFocus.CONFIRM + ) ) - _begin_root_exit(_show_confirmation.bind(dangerous)) + _begin_root_exit(_show_confirmation) -func _show_confirmation(dangerous: bool) -> void: - _fade_confirmation( - true, - _finish_confirmation_open.bind(dangerous) +func _show_confirmation() -> void: + _confirmation_page.transition_in( + PAGE_INCOMING_DURATION, + _finish_confirmation_open ) -func _finish_confirmation_open(dangerous: bool) -> void: - _set_confirmation_interactive(true) - if dangerous: - _cancel_button.grab_focus() - else: - _confirm_button.grab_focus() +func _finish_confirmation_open() -> void: + pass func _close_confirmation() -> void: - if _root_transition_active or _confirmation_fade_active: + if _root_transition_active or _confirmation_page.is_transitioning(): return _confirmation_action = ConfirmationAction.NONE - _fade_confirmation(false, _finish_confirmation_cancel) + _emit_transition_flurry() + _confirmation_page.transition_out( + PAGE_OUTGOING_DURATION, + _finish_confirmation_cancel + ) func _finish_confirmation_cancel() -> void: - _begin_root_entry(false) + _begin_root_entry(true) func _accept_confirmation() -> void: if ( _action_in_progress or _root_transition_active - or _confirmation_fade_active + or _confirmation_page.is_transitioning() ): return var action: ConfirmationAction = _confirmation_action _confirmation_action = ConfirmationAction.NONE _action_in_progress = true - _fade_confirmation( - false, + _confirmation_page.transition_out( + VISIBILITY_FADE_DURATION, _finish_confirmation_accept.bind(action) ) @@ -417,14 +418,11 @@ func _finish_close(reason: CloseReason, restore_controls: bool) -> void: _root_transition_generation += 1 _root_transition_active = false _cancel_backdrop_fade() - _cancel_confirmation_fade() _dim_background.color.a = 0.0 _dim_background.hide() - _confirmation_panel.modulate.a = 0.0 - _set_confirmation_interactive(false) _settings_panel.hide() _root_page.hide_page() - _confirmation_panel.hide() + _confirmation_page.hide_page() _confirmation_action = ConfirmationAction.NONE _action_in_progress = false _transition_flurry.clear_flurries() @@ -483,83 +481,6 @@ func _cancel_backdrop_fade() -> void: _backdrop_fade = null -func _fade_confirmation( - fade_in: bool, - completed: Callable, -) -> void: - _confirmation_fade_generation += 1 - _cancel_confirmation_fade() - _confirmation_fade_active = true - _set_confirmation_interactive(false) - _confirmation_panel.show() - var target_alpha: float = 1.0 if fade_in else 0.0 - if is_equal_approx(_confirmation_panel.modulate.a, target_alpha): - _finish_confirmation_fade( - _confirmation_fade_generation, - fade_in, - completed - ) - return - var generation: int = _confirmation_fade_generation - _confirmation_fade = create_tween() - _confirmation_fade.tween_property( - _confirmation_panel, - "modulate:a", - target_alpha, - VISIBILITY_FADE_DURATION - ).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN_OUT) - _confirmation_fade.finished.connect( - _finish_confirmation_fade.bind( - generation, - fade_in, - completed - ), - CONNECT_ONE_SHOT - ) - - -func _finish_confirmation_fade( - generation: int, - faded_in: bool, - completed: Callable, -) -> void: - if generation != _confirmation_fade_generation or not visible: - return - _confirmation_fade = null - _confirmation_fade_active = false - _confirmation_panel.modulate.a = 1.0 if faded_in else 0.0 - if not faded_in: - _confirmation_panel.hide() - completed.call() - - -func _cancel_confirmation_fade() -> void: - _confirmation_fade_generation += 1 - _confirmation_fade_active = false - if _confirmation_fade != null: - _confirmation_fade.kill() - _confirmation_fade = null - - -func _set_confirmation_interactive(interactive: bool) -> void: - _confirmation_panel.mouse_filter = ( - Control.MOUSE_FILTER_STOP - if interactive - else Control.MOUSE_FILTER_IGNORE - ) - for button: Button in [_confirm_button, _cancel_button]: - if not interactive and button.has_focus(): - button.release_focus() - button.focus_mode = ( - Control.FOCUS_ALL if interactive else Control.FOCUS_NONE - ) - button.mouse_filter = ( - Control.MOUSE_FILTER_STOP - if interactive - else Control.MOUSE_FILTER_IGNORE - ) - - func _update_responsive_pause_stage() -> void: if not is_node_ready(): return diff --git a/ui/pause_menu.tscn b/ui/pause_menu.tscn index 79d8f1c..318caa5 100644 --- a/ui/pause_menu.tscn +++ b/ui/pause_menu.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=9 format=3] +[gd_scene load_steps=10 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"] @@ -8,6 +8,7 @@ [ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_cluster.gd" id="6_cluster"] [ext_resource type="Resource" path="res://ui/components/bubble_menu/bubble_menu_profile.tres" id="7_profile"] [ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_transition_flurry.gd" id="8_flurry"] +[ext_resource type="PackedScene" path="res://ui/components/bubble_menu/bubble_confirmation_page.tscn" id="9_confirmation"] [node name="PauseMenu" type="Control"] unique_name_in_owner = true @@ -200,61 +201,11 @@ mouse_filter = 2 unique_name_in_owner = true layout_mode = 2 -[node name="ConfirmationCenter" type="CenterContainer" parent="."] -z_index = 3 +[node name="ConfirmationPage" parent="ResponsivePauseStage/PausePresentationScaleRoot" instance=ExtResource("9_confirmation")] +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 - -[node name="ConfirmationPanel" type="PanelContainer" parent="ConfirmationCenter"] -unique_name_in_owner = true -visible = false -custom_minimum_size = Vector2(700, 0) -layout_mode = 2 - -[node name="Margin" type="MarginContainer" parent="ConfirmationCenter/ConfirmationPanel"] -layout_mode = 2 -theme_override_constants/margin_left = 28 -theme_override_constants/margin_top = 24 -theme_override_constants/margin_right = 28 -theme_override_constants/margin_bottom = 24 - -[node name="Content" type="VBoxContainer" parent="ConfirmationCenter/ConfirmationPanel/Margin"] -layout_mode = 2 -theme_override_constants/separation = 15 - -[node name="ConfirmationTitle" type="Label" parent="ConfirmationCenter/ConfirmationPanel/Margin/Content"] -unique_name_in_owner = true -layout_mode = 2 -theme_override_colors/font_color = Color(0.937, 0.357, 0.384, 1) -theme_override_font_sizes/font_size = 36 -text = "confirm" -horizontal_alignment = 1 - -[node name="ConfirmationText" type="Label" parent="ConfirmationCenter/ConfirmationPanel/Margin/Content"] -unique_name_in_owner = true -layout_mode = 2 -text = "are you sure?" -horizontal_alignment = 1 -autowrap_mode = 2 - -[node name="Buttons" type="HBoxContainer" parent="ConfirmationCenter/ConfirmationPanel/Margin/Content"] -layout_mode = 2 -theme_override_constants/separation = 12 -alignment = 1 - -[node name="ConfirmButton" type="Button" parent="ConfirmationCenter/ConfirmationPanel/Margin/Content/Buttons"] -unique_name_in_owner = true -custom_minimum_size = Vector2(240, 64) -layout_mode = 2 -text = "confirm" - -[node name="CancelConfirmButton" type="Button" parent="ConfirmationCenter/ConfirmationPanel/Margin/Content/Buttons"] -unique_name_in_owner = true -custom_minimum_size = Vector2(180, 64) -layout_mode = 2 -text = "cancel" diff --git a/ui/player_menu.gd b/ui/player_menu.gd index bc7d207..9ebc27c 100644 --- a/ui/player_menu.gd +++ b/ui/player_menu.gd @@ -25,6 +25,30 @@ const PlayerCoolerCapacityType = preload( const FishBatchSelectionType = preload( "res://ui/fish_batch_selection.gd" ) +const BubbleButtonType = preload( + "res://ui/components/bubble_menu/bubble_button.gd" +) +const BubbleClusterType = preload( + "res://ui/components/bubble_menu/bubble_cluster.gd" +) +const BubbleContentShellType = preload( + "res://ui/components/bubble_menu/bubble_content_shell.gd" +) +const BubbleInventoryCellType = preload( + "res://ui/components/bubble_menu/bubble_inventory_cell.gd" +) +const BubbleInventoryCellScene = preload( + "res://ui/components/bubble_menu/bubble_inventory_cell.tscn" +) +const BubbleStatusBubbleType = preload( + "res://ui/components/bubble_menu/bubble_status_bubble.gd" +) + +const MENU_ENTER_DURATION: float = 0.58 +const MENU_EXIT_DURATION: float = 0.52 +const PAGE_OUT_DURATION: float = 0.34 +const PAGE_IN_DURATION: float = 0.42 +const TRANSITION_SAFE_MARGIN: float = 28.0 signal menu_visibility_changed(is_open: bool) @@ -49,16 +73,32 @@ enum CloseReason { TEARDOWN, } -@onready var _inventory_tab: Button = %InventoryTab -@onready var _bag_tab: Button = %BagTab -@onready var _logbook_tab: Button = %LogbookTab -@onready var _close_button: Button = %CloseButton +@onready var _navigation_cluster: BubbleClusterType = %NavigationCluster +@onready var _inventory_tab: BubbleButtonType = %InventoryTab +@onready var _bag_tab: BubbleButtonType = %BagTab +@onready var _logbook_tab: BubbleButtonType = %LogbookTab +@onready var _close_button: BubbleButtonType = %CloseButton +@onready var _content_shell: BubbleContentShellType = %MenuPanel +@onready var _content_stage: Control = %Content +@onready var _cooler_scroll: ScrollContainer = %CoolerScroll +@onready var _cooler_host: VBoxContainer = %CoolerHost @onready var _wallet_balance: Label = %WalletBalance +@onready var _header: Control = %Header +@onready var _separator: Control = %Separator +@onready var _wallet_status: BubbleStatusBubbleType = %WalletStatus +@onready var _capacity_status: BubbleStatusBubbleType = %CapacityStatus +@onready var _held_value_status: BubbleStatusBubbleType = %HeldValueStatus +@onready var _selection_status: BubbleStatusBubbleType = %SelectionStatus +@onready var _offer_status: BubbleStatusBubbleType = %OfferStatus @onready var _inventory_section: Control = %InventorySection +@onready var _inventory_body: BoxContainer = %InventoryBody +@onready var _inventory_scroll: ScrollContainer = %InventoryScroll @onready var _bag_section: Control = %BagSection @onready var _logbook_section: Control = %LogbookSection @onready var _bag_empty: Label = %BagEmpty @onready var _bag_grid: GridContainer = %BagGrid +@onready var _bag_list: Control = %BagList +@onready var _bag_detail: Control = %BagDetail @onready var _bag_detail_texture: TextureRect = %BagDetailTexture @onready var _bag_detail_name: Label = %BagDetailName @onready var _bag_detail_data: Label = %BagDetailData @@ -68,6 +108,8 @@ enum CloseReason { @onready var _cooler_count: Label = %CoolerCount @onready var _inventory_empty: Label = %InventoryEmpty @onready var _inventory_grid: GridContainer = %InventoryGrid +@onready var _inventory_list: Control = %InventoryList +@onready var _detail_panel: Control = %DetailPanel @onready var _detail_texture: TextureRect = %DetailTexture @onready var _detail_name: Label = %DetailName @onready var _detail_data: Label = %DetailData @@ -82,7 +124,10 @@ enum CloseReason { @onready var _cancel_sale_button: Button = %CancelSaleButton @onready var _logbook_empty: Label = %LogbookEmpty @onready var _logbook_grid: GridContainer = %LogbookGrid +@onready var _status_shoal: HBoxContainer = %StatusShoal +@onready var _sort_bar: HBoxContainer = %SortBar +var _compact_layout: bool = false var _player: PlayerType var _inventory: FishInventoryType var _collection_log: CollectionLogType @@ -111,9 +156,24 @@ var _confirmation_buyer: FishBuyerProfileType var _confirmation_buyer_id: StringName var _confirmation_generation: int = -1 var _sale_in_progress: bool = false +var _presentation_tween: Tween +var _page_tween: Tween +var _transition_generation: int = 0 +var _page_transition_generation: int = 0 +var _transitioning: bool = false +var _page_transitioning: bool = false +var _presentation_rest_position: Vector2 = Vector2.ZERO +var _content_rest_position: Vector2 = Vector2.ZERO func _ready() -> void: + _inventory_section.reparent(_cooler_host) + _inventory_section.size_flags_horizontal = Control.SIZE_EXPAND_FILL + _inventory_section.size_flags_vertical = Control.SIZE_EXPAND_FILL + _inventory_section.move_child( + _sort_bar, + _inventory_section.get_child_count() - 1, + ) _inventory_tab.pressed.connect( _show_section.bind(Section.COOLER) ) @@ -133,7 +193,18 @@ func _ready() -> void: _sort_option.add_item("rarity", SortMode.RARITY) _sort_option.select(SortMode.CATCH_ORDER) _update_sort_direction_text() - _show_section(_current_section) + _navigation_cluster.configure([ + _inventory_tab, + _bag_tab, + _logbook_tab, + _close_button, + ]) + _configure_navigation_focus() + _apply_cooler_control_styles() + resized.connect(_update_shell_layout) + _show_section_immediate(_current_section) + call_deferred("_update_shell_layout") + set_process(false) func setup( @@ -185,7 +256,8 @@ func _input(event: InputEvent) -> void: return if visible: if event.is_action_pressed("open_backpack"): - close_menu() + if not _transitioning and not _page_transitioning: + close_menu() get_viewport().set_input_as_handled() return if ( @@ -200,6 +272,8 @@ func _input(event: InputEvent) -> void: func consume_escape() -> bool: if not visible: return false + if _transitioning or _page_transitioning: + return true if _sale_confirmation.visible: _close_sale_confirmation() else: @@ -216,6 +290,9 @@ func open_menu() -> void: ): return _menu_generation += 1 + _transition_generation += 1 + _cancel_presentation_tween() + _cancel_page_tween() _prior_movement_enabled = _player.is_movement_enabled() _prior_camera_input_enabled = _player.is_camera_input_enabled() _prior_mouse_mode = Input.mouse_mode @@ -227,8 +304,9 @@ func open_menu() -> void: Input.mouse_mode = Input.MOUSE_MODE_VISIBLE visible = true _refresh_all() - _show_section(_current_section) - _focus_current_section() + _show_section_immediate(_current_section) + _update_shell_layout() + _begin_menu_entry() menu_visibility_changed.emit(true) @@ -238,6 +316,10 @@ func close_menu( ) -> void: if not visible: return + if _transitioning: + if reason != CloseReason.USER: + _finish_close(reason, restore_controls, _menu_generation) + return get_viewport().gui_cancel_drag() _close_sale_confirmation() if reason in [ @@ -247,18 +329,7 @@ func close_menu( CloseReason.TEARDOWN, ]: _fish_selection.clear() - var closing_generation: int = _menu_generation - visible = false - get_viewport().gui_release_focus() - if _fishing_spot != null and is_instance_valid(_fishing_spot): - _fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, false) - if restore_controls: - _restore_player_controls(closing_generation) - else: - _control_snapshot_stored = false - _apply_mouse_close_policy(reason) - _menu_generation += 1 - menu_visibility_changed.emit(false) + _begin_menu_exit(reason, restore_controls) func close_for_water_recovery() -> void: @@ -276,8 +347,10 @@ func close_for_session_end() -> void: func _exit_tree() -> void: + _cancel_presentation_tween() + _cancel_page_tween() if visible: - close_menu(CloseReason.TEARDOWN, false) + _finish_close(CloseReason.TEARDOWN, false, _menu_generation) _fish_selection.clear() _menu_generation += 1 @@ -319,17 +392,32 @@ func _on_bite_activated() -> void: func _show_section(section: Section) -> void: + if ( + section == _current_section + or _transitioning + or _page_transitioning + or _sale_confirmation.visible + or get_viewport().gui_is_dragging() + ): + return + _begin_page_transition(section) + + +func _show_section_immediate(section: Section) -> void: _current_section = section if not is_node_ready(): return _inventory_section.visible = section == Section.COOLER + _cooler_scroll.visible = section == Section.COOLER _bag_section.visible = section == Section.BAG _logbook_section.visible = section == Section.LOGBOOK + _content_shell.set_background_visible(section != Section.COOLER) + _header.visible = section != Section.COOLER + _separator.visible = section != Section.COOLER _inventory_tab.button_pressed = section == Section.COOLER _bag_tab.button_pressed = section == Section.BAG _logbook_tab.button_pressed = section == Section.LOGBOOK - if visible: - _focus_current_section() + _update_navigation_selection() func _focus_current_section() -> void: @@ -341,6 +429,346 @@ func _focus_current_section() -> void: _logbook_tab.grab_focus() +func _process(delta: float) -> void: + if visible: + _navigation_cluster.advance_motion(delta) + + +func _configure_navigation_focus() -> void: + var navigation: Array[BubbleButtonType] = [ + _inventory_tab, + _bag_tab, + _logbook_tab, + _close_button, + ] + for index: int in navigation.size(): + var bubble: BubbleButtonType = navigation[index] + var previous: BubbleButtonType = navigation[ + (index - 1 + navigation.size()) % navigation.size() + ] + var next: BubbleButtonType = navigation[ + (index + 1) % navigation.size() + ] + bubble.focus_neighbor_left = bubble.get_path_to(previous) + bubble.focus_neighbor_right = bubble.get_path_to(next) + bubble.focus_neighbor_top = bubble.focus_neighbor_left + bubble.focus_neighbor_bottom = bubble.focus_neighbor_right + + +func _apply_cooler_control_styles() -> void: + var profile: BubbleMenuProfile = _inventory_tab.profile + if profile == null: + return + _sort_option.add_theme_stylebox_override( + "normal", + profile.make_normal_style(), + ) + _sort_option.add_theme_stylebox_override( + "hover", + profile.make_hover_style(), + ) + _sort_option.add_theme_stylebox_override( + "focus", + profile.make_hover_style(), + ) + _sort_option.add_theme_stylebox_override( + "pressed", + profile.make_pressed_style(), + ) + _sort_option.add_theme_color_override("font_color", profile.text_color) + _sort_option.add_theme_color_override( + "font_hover_color", + profile.text_hover_color, + ) + _sort_option.add_theme_color_override( + "font_focus_color", + profile.text_hover_color, + ) + + +func _update_navigation_selection() -> void: + _set_navigation_target(_current_section) + + +func _set_navigation_target(section: Section) -> void: + _inventory_tab.button_pressed = section == Section.COOLER + _bag_tab.button_pressed = section == Section.BAG + _logbook_tab.button_pressed = section == Section.LOGBOOK + + +func _update_shell_layout() -> void: + if not is_node_ready(): + return + var compact: bool = size.y < 560.0 or size.x < 760.0 + _compact_layout = compact + var side_margin: float = 10.0 if compact else 42.0 + var top_margin: float = 96.0 if compact else 104.0 + var bottom_margin: float = 96.0 if compact else 138.0 + _content_shell.position = Vector2(side_margin, top_margin) + _content_shell.size = Vector2( + maxf(1.0, size.x - side_margin * 2.0), + maxf(1.0, size.y - top_margin - bottom_margin) + ) + _presentation_rest_position = _content_shell.position + var navigation_size := Vector2( + minf(size.x - 20.0, 610.0 if compact else 760.0), + 52.0 if compact else 96.0 + ) + _navigation_cluster.position = Vector2( + (size.x - navigation_size.x) * 0.5, + 4.0 if compact else 14.0 + ) + _navigation_cluster.size = navigation_size + _navigation_cluster.apply_layout(navigation_size, compact) + _inventory_grid.columns = 2 if compact else 3 + _bag_grid.columns = 2 if compact else 3 + _logbook_grid.columns = 3 if compact else 4 + _inventory_list.custom_minimum_size.x = 292.0 if compact else 350.0 + _inventory_list.size_flags_horizontal = Control.SIZE_EXPAND_FILL + _detail_panel.custom_minimum_size.x = 176.0 if compact else 210.0 + _bag_list.custom_minimum_size.x = 300.0 if compact else 360.0 + _bag_detail.custom_minimum_size.x = 176.0 if compact else 220.0 + _content_stage.custom_minimum_size.y = 220.0 if compact else 260.0 + call_deferred("_sync_cooler_width") + _inventory_body.vertical = compact + _inventory_scroll.vertical_scroll_mode = ( + ScrollContainer.SCROLL_MODE_DISABLED + if compact + else ScrollContainer.SCROLL_MODE_AUTO + ) + _inventory_list.custom_minimum_size.y = 240.0 if compact else 0.0 + _detail_panel.custom_minimum_size = ( + Vector2(0.0, 310.0) + if compact + else Vector2(430.0, 0.0) + ) + _status_shoal.add_theme_constant_override( + "separation", + 4 if compact else 8, + ) + _inventory_body.queue_sort() + _content_rest_position = _content_stage.position + if not _transitioning: + _content_shell.position = _presentation_rest_position + if not _page_transitioning: + _content_stage.position = _content_rest_position + + +func _sync_cooler_width() -> void: + if not is_node_ready(): + return + _cooler_host.custom_minimum_size.x = minf( + _content_stage.size.x, + 1380.0, + ) + _cooler_host.size_flags_horizontal = Control.SIZE_SHRINK_CENTER + + +func _begin_menu_entry() -> void: + _transitioning = true + _set_shell_interactive(false) + set_process(true) + var generation: int = _transition_generation + var start_offset: float = size.y + TRANSITION_SAFE_MARGIN + _content_shell.position = _presentation_rest_position + Vector2.DOWN * start_offset + var navigation_rest: Vector2 = _navigation_cluster.position + _navigation_cluster.position = navigation_rest + Vector2.DOWN * start_offset + _presentation_tween = create_tween().set_parallel(true) + _presentation_tween.tween_property( + _content_shell, + "position", + _presentation_rest_position, + MENU_ENTER_DURATION + ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) + _presentation_tween.tween_property( + _navigation_cluster, + "position", + navigation_rest, + MENU_ENTER_DURATION + ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) + _presentation_tween.chain().tween_callback( + _finish_menu_entry.bind(generation) + ) + + +func _finish_menu_entry(generation: int) -> void: + if generation != _transition_generation or not visible: + return + _presentation_tween = null + _transitioning = false + _set_shell_interactive(true) + _focus_current_section() + + +func _begin_menu_exit(reason: CloseReason, restore_controls: bool) -> void: + if reason != CloseReason.USER: + _finish_close(reason, restore_controls, _menu_generation) + return + _transition_generation += 1 + _cancel_presentation_tween() + _transitioning = true + _set_shell_interactive(false) + get_viewport().gui_release_focus() + var generation: int = _transition_generation + var closing_generation: int = _menu_generation + var end_y: float = -maxf( + _content_shell.size.y, + _navigation_cluster.size.y + ) - TRANSITION_SAFE_MARGIN + _presentation_tween = create_tween().set_parallel(true) + _presentation_tween.tween_property( + _content_shell, + "position:y", + end_y, + MENU_EXIT_DURATION + ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) + _presentation_tween.tween_property( + _navigation_cluster, + "position:y", + -_navigation_cluster.size.y - TRANSITION_SAFE_MARGIN, + MENU_EXIT_DURATION + ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) + _presentation_tween.chain().tween_callback( + _finish_menu_exit.bind( + generation, + reason, + restore_controls, + closing_generation, + ) + ) + + +func _finish_menu_exit( + generation: int, + reason: CloseReason, + restore_controls: bool, + closing_generation: int, +) -> void: + if generation != _transition_generation or not visible: + return + _presentation_tween = null + _finish_close(reason, restore_controls, closing_generation) + + +func _finish_close( + reason: CloseReason, + restore_controls: bool, + closing_generation: int, +) -> void: + _cancel_presentation_tween() + _cancel_page_tween() + _transitioning = false + _page_transitioning = false + visible = false + set_process(false) + get_viewport().gui_release_focus() + if _fishing_spot != null and is_instance_valid(_fishing_spot): + _fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, false) + if restore_controls: + _restore_player_controls(closing_generation) + else: + _control_snapshot_stored = false + _apply_mouse_close_policy(reason) + _menu_generation += 1 + _transition_generation += 1 + menu_visibility_changed.emit(false) + + +func _begin_page_transition(section: Section) -> void: + _page_transition_generation += 1 + _cancel_page_tween() + _page_transitioning = true + _set_content_interactive(false) + _set_navigation_target(section) + var generation: int = _page_transition_generation + _page_tween = create_tween() + _page_tween.tween_property( + _content_stage, + "modulate:a", + 0.0, + PAGE_OUT_DURATION + ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) + _page_tween.parallel().tween_property( + _content_stage, + "position:y", + _content_rest_position.y - 18.0, + PAGE_OUT_DURATION + ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) + _page_tween.tween_callback( + _swap_page.bind(section, generation) + ) + _page_tween.tween_property( + _content_stage, + "modulate:a", + 1.0, + PAGE_IN_DURATION + ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) + _page_tween.parallel().tween_property( + _content_stage, + "position", + _content_rest_position, + PAGE_IN_DURATION + ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) + _page_tween.finished.connect( + _finish_page_transition.bind(generation), + CONNECT_ONE_SHOT + ) + + +func _swap_page(section: Section, generation: int) -> void: + if generation != _page_transition_generation or not visible: + return + _show_section_immediate(section) + _content_stage.position = _content_rest_position + Vector2.DOWN * 18.0 + + +func _finish_page_transition(generation: int) -> void: + if generation != _page_transition_generation or not visible: + return + _page_tween = null + _page_transitioning = false + _content_stage.position = _content_rest_position + _content_stage.modulate.a = 1.0 + _set_content_interactive(true) + _focus_current_section() + + +func _set_shell_interactive(interactive: bool) -> void: + _set_content_interactive(interactive) + for bubble: BubbleButtonType in [ + _inventory_tab, + _bag_tab, + _logbook_tab, + _close_button, + ]: + 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 _set_content_interactive(interactive: bool) -> void: + _content_stage.mouse_filter = ( + Control.MOUSE_FILTER_PASS + if interactive + else Control.MOUSE_FILTER_IGNORE + ) + + +func _cancel_presentation_tween() -> void: + if _presentation_tween != null: + _presentation_tween.kill() + _presentation_tween = null + + +func _cancel_page_tween() -> void: + if _page_tween != null: + _page_tween.kill() + _page_tween = null + + func _on_sort_selected(index: int) -> void: _sort_mode = _sort_option.get_item_id(index) _refresh_inventory() @@ -486,6 +914,12 @@ func _refresh_economy_summary() -> void: else 0 ) _wallet_balance.text = "wallet: $%d" % balance + _wallet_status.set_content("wallet", "$%d" % balance) + _capacity_status.set_content("cooler", "%d / %d" % [ + _inventory.get_all_catches().size() if _inventory != null else 0, + _cooler_capacity.get_capacity() if _cooler_capacity != null else 0, + ]) + _held_value_status.set_content("held value", "$%d" % held_total) _held_value.text = "held fish base value: $%d" % held_total _cooler_count.text = "%d / %d" % [ _inventory.get_all_catches().size() if _inventory != null else 0, @@ -518,11 +952,32 @@ func _refresh_inventory() -> void: selected = _inventory.get_catch(_fish_selection.get_focused_id()) for fish_catch: FishCatchType in catches: _inventory_grid.add_child(_create_inventory_card(fish_catch)) + _configure_inventory_card_focus() _update_inventory_detail(selected) _update_sale_summary() _update_sort_direction_text() +func _configure_inventory_card_focus() -> void: + var cards: Array[Control] = [] + for child: Node in _inventory_grid.get_children(): + if child is Control: + cards.append(child as Control) + var columns: int = maxi(_inventory_grid.columns, 1) + for index: int in cards.size(): + var card: Control = cards[index] + var row: int = floori(float(index) / float(columns)) + var column: int = index % columns + var left_index: int = row * columns + maxi(column - 1, 0) + var right_index: int = mini(index + 1, cards.size() - 1) + var top_index: int = maxi(index - columns, 0) + var bottom_index: int = mini(index + columns, cards.size() - 1) + card.focus_neighbor_left = card.get_path_to(cards[left_index]) + card.focus_neighbor_right = card.get_path_to(cards[right_index]) + card.focus_neighbor_top = card.get_path_to(cards[top_index]) + card.focus_neighbor_bottom = card.get_path_to(cards[bottom_index]) + + func _compare_catches(left: FishCatchType, right: FishCatchType) -> bool: var comparison: int = 0 match _sort_mode: @@ -540,92 +995,23 @@ func _compare_catches(left: FishCatchType, right: FishCatchType) -> bool: func _create_inventory_card(fish_catch: FishCatchType) -> Button: - var card := Button.new() - card.theme_type_variation = &"CardButton" - card.custom_minimum_size = Vector2(132.0, 118.0) - card.toggle_mode = true + var card := BubbleInventoryCellScene.instantiate() as BubbleInventoryCellType + card.set_compact(_compact_layout) var is_selected: bool = _fish_selection.is_selected(fish_catch.catch_id) var is_focused: bool = ( fish_catch.catch_id == _fish_selection.get_focused_id() ) - card.button_pressed = is_selected - card.pressed.connect(_on_catch_card_pressed.bind(fish_catch.catch_id)) - _apply_inventory_card_styles( - card, - UIPalette.get_rarity_color(fish_catch.fish.rarity), - is_selected, - is_focused - ) - - var content := VBoxContainer.new() - content.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT, Control.PRESET_MODE_MINSIZE, 8) - content.add_theme_constant_override("separation", 3) - content.mouse_filter = Control.MOUSE_FILTER_IGNORE - card.add_child(content) - content.add_child(_create_texture_frame( + card.configure( fish_catch.fish.display_texture, - Vector2(108.0, 62.0) - )) - var name_label := Label.new() - name_label.text = fish_catch.fish.display_name - name_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - name_label.mouse_filter = Control.MOUSE_FILTER_IGNORE - content.add_child(name_label) - var weight_label := Label.new() - weight_label.text = "%.2f lb" % fish_catch.weight_lb - weight_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - weight_label.add_theme_color_override( - "font_color", - UIPalette.MUTED_TEXT + fish_catch.fish.display_name, + "%.2f lb" % fish_catch.weight_lb, ) - weight_label.mouse_filter = Control.MOUSE_FILTER_IGNORE - content.add_child(weight_label) - if fish_catch.is_favorited: - var favorite_marker := Label.new() - favorite_marker.text = "★" - favorite_marker.add_theme_color_override( - "font_color", - UIPalette.SECONDARY - ) - favorite_marker.add_theme_font_size_override("font_size", 27) - favorite_marker.set_anchors_preset(Control.PRESET_TOP_RIGHT) - favorite_marker.offset_left = -28.0 - favorite_marker.offset_top = 5.0 - favorite_marker.offset_right = -8.0 - favorite_marker.offset_bottom = 29.0 - favorite_marker.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - favorite_marker.mouse_filter = Control.MOUSE_FILTER_IGNORE - card.add_child(favorite_marker) - if is_selected: - var selected_marker := Label.new() - selected_marker.text = "✓" - selected_marker.add_theme_color_override( - "font_color", - UIPalette.SUCCESS - ) - selected_marker.add_theme_font_size_override("font_size", 27) - selected_marker.set_anchors_preset(Control.PRESET_TOP_LEFT) - selected_marker.offset_left = 8.0 - selected_marker.offset_top = 5.0 - selected_marker.offset_right = 30.0 - selected_marker.offset_bottom = 29.0 - selected_marker.mouse_filter = Control.MOUSE_FILTER_IGNORE - card.add_child(selected_marker) - if is_focused: - var focus_marker := Label.new() - focus_marker.text = "◆" - focus_marker.add_theme_color_override( - "font_color", - UIPalette.PRIMARY - ) - focus_marker.set_anchors_preset(Control.PRESET_BOTTOM_RIGHT) - focus_marker.offset_left = -26.0 - focus_marker.offset_top = -25.0 - focus_marker.offset_right = -8.0 - focus_marker.offset_bottom = -7.0 - focus_marker.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - focus_marker.mouse_filter = Control.MOUSE_FILTER_IGNORE - card.add_child(focus_marker) + card.set_item_state( + is_selected, + is_focused, + fish_catch.is_favorited, + ) + card.pressed.connect(_on_catch_card_pressed.bind(fish_catch.catch_id)) return card @@ -762,6 +1148,8 @@ func _update_sale_summary() -> void: ) if selected_count == 0: _selection_summary.text = "no fish selected" + _selection_status.set_content("selected", "none") + _offer_status.set_content("pelican offer", "—") _sell_button.text = "sell fish" _sell_button.disabled = true _sale_unavailable.text = "" @@ -778,6 +1166,7 @@ func _update_sale_summary() -> void: else "%d fish selected" % selected_count ) _selection_summary.text = count_text + _selection_status.set_content("selected", str(selected_count)) if ( preview != null and preview.payout >= 0 @@ -791,6 +1180,9 @@ func _update_sale_summary() -> void: _default_buyer.display_name, preview.payout, ] + _offer_status.set_content("pelican offer", "$%d" % preview.payout) + else: + _offer_status.set_content("pelican offer", "unavailable") _sell_button.disabled = preview == null or not preview.is_success() if preview != null and preview.status == FishSaleResultType.Status.FAVORITED: _sale_unavailable.text = ( diff --git a/ui/player_menu.tscn b/ui/player_menu.tscn index 824eda6..61a075e 100644 --- a/ui/player_menu.tscn +++ b/ui/player_menu.tscn @@ -1,7 +1,15 @@ -[gd_scene load_steps=3 format=3] +[gd_scene load_steps=9 format=3] [ext_resource type="Script" path="res://ui/player_menu.gd" id="1_menu"] [ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"] +[ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_content_shell.gd" id="3_shell"] +[ext_resource type="Resource" path="res://ui/components/bubble_menu/bubble_menu_profile.tres" id="4_profile"] +[ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_cluster.gd" id="5_cluster"] +[ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_button.gd" id="6_bubble"] +[ext_resource type="PackedScene" path="res://ui/components/bubble_menu/bubble_status_bubble.tscn" id="7_status"] +[ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_information_panel.gd" id="8_info"] + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_collection"] [node name="PlayerMenu" type="Control"] visible = false @@ -28,13 +36,16 @@ mouse_filter = 2 color = Color(0.015, 0.02, 0.03, 0.72) [node name="MenuPanel" type="PanelContainer" parent="."] -layout_mode = 1 -anchor_left = 0.02 -anchor_top = 0.025 -anchor_right = 0.98 -anchor_bottom = 0.96 -grow_horizontal = 2 -grow_vertical = 2 +unique_name_in_owner = true +layout_mode = 0 +offset_left = 42.0 +offset_top = 104.0 +offset_right = 1238.0 +offset_bottom = 582.0 +script = ExtResource("3_shell") +profile = ExtResource("4_profile") +content_margin = 22.0 +corner_radius = 74 [node name="Margin" type="MarginContainer" parent="MenuPanel"] layout_mode = 2 @@ -48,50 +59,19 @@ layout_mode = 2 theme_override_constants/separation = 8 [node name="Header" type="HBoxContainer" parent="MenuPanel/Margin/Layout"] +unique_name_in_owner = true layout_mode = 2 theme_override_constants/separation = 8 -[node name="Title" type="Label" parent="MenuPanel/Margin/Layout/Header"] -layout_mode = 2 -size_flags_horizontal = 3 -theme_override_colors/font_color = Color(0.208, 0.725, 0.78, 1) -theme_override_font_sizes/font_size = 33 -text = "player menu" - [node name="WalletBalance" type="Label" parent="MenuPanel/Margin/Layout/Header"] unique_name_in_owner = true layout_mode = 2 +size_flags_horizontal = 3 text = "wallet: $0" theme_override_colors/font_color = Color(1, 0.82, 0.4, 1) -[node name="InventoryTab" type="Button" parent="MenuPanel/Margin/Layout/Header"] -unique_name_in_owner = true -layout_mode = 2 -toggle_mode = true -text = "cooler" -custom_minimum_size = Vector2(130, 58) - -[node name="BagTab" type="Button" parent="MenuPanel/Margin/Layout/Header"] -unique_name_in_owner = true -layout_mode = 2 -toggle_mode = true -text = "bag" -custom_minimum_size = Vector2(110, 58) - -[node name="LogbookTab" type="Button" parent="MenuPanel/Margin/Layout/Header"] -unique_name_in_owner = true -layout_mode = 2 -toggle_mode = true -text = "logbook" -custom_minimum_size = Vector2(140, 58) - -[node name="CloseButton" type="Button" parent="MenuPanel/Margin/Layout/Header"] -unique_name_in_owner = true -layout_mode = 2 -text = "close" -custom_minimum_size = Vector2(110, 58) - [node name="Separator" type="HSeparator" parent="MenuPanel/Margin/Layout"] +unique_name_in_owner = true layout_mode = 2 [node name="TransactionFeedback" type="Label" parent="MenuPanel/Margin/Layout"] @@ -103,10 +83,26 @@ theme_override_colors/font_color = Color(1, 0.82, 0.4, 1) theme_override_font_sizes/font_size = 21 [node name="Content" type="Control" parent="MenuPanel/Margin/Layout"] +unique_name_in_owner = true custom_minimum_size = Vector2(0, 260) layout_mode = 2 size_flags_vertical = 3 +[node name="CoolerScroll" type="ScrollContainer" parent="MenuPanel/Margin/Layout/Content"] +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 +horizontal_scroll_mode = 0 + +[node name="CoolerHost" type="VBoxContainer" parent="MenuPanel/Margin/Layout/Content/CoolerScroll"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + [node name="InventorySection" type="VBoxContainer" parent="MenuPanel/Margin/Layout/Content"] unique_name_in_owner = true layout_mode = 1 @@ -117,7 +113,26 @@ grow_horizontal = 2 grow_vertical = 2 theme_override_constants/separation = 7 +[node name="StatusShoal" type="HBoxContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection"] +unique_name_in_owner = true +layout_mode = 2 +theme_override_constants/separation = 8 +alignment = 1 + +[node name="WalletStatus" parent="MenuPanel/Margin/Layout/Content/InventorySection/StatusShoal" instance=ExtResource("7_status")] +unique_name_in_owner = true +layout_mode = 2 + +[node name="CapacityStatus" parent="MenuPanel/Margin/Layout/Content/InventorySection/StatusShoal" instance=ExtResource("7_status")] +unique_name_in_owner = true +layout_mode = 2 + +[node name="HeldValueStatus" parent="MenuPanel/Margin/Layout/Content/InventorySection/StatusShoal" instance=ExtResource("7_status")] +unique_name_in_owner = true +layout_mode = 2 + [node name="SortBar" type="HBoxContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection"] +unique_name_in_owner = true layout_mode = 2 theme_override_constants/separation = 6 @@ -128,13 +143,21 @@ text = "sort:" [node name="SortOption" type="OptionButton" parent="MenuPanel/Margin/Layout/Content/InventorySection/SortBar"] unique_name_in_owner = true layout_mode = 2 -custom_minimum_size = Vector2(190, 58) +custom_minimum_size = Vector2(136, 104) [node name="SortDirection" type="Button" parent="MenuPanel/Margin/Layout/Content/InventorySection/SortBar"] unique_name_in_owner = true layout_mode = 2 -custom_minimum_size = Vector2(190, 58) +custom_minimum_size = Vector2(136, 104) text = "newest first" +script = ExtResource("6_bubble") +profile = ExtResource("4_profile") +neutral_size = Vector2(136, 104) +minimum_font_size = 15 +maximum_font_size = 20 +horizontal_amplitude = 0.0 +vertical_amplitude = 0.0 +deformation_amplitude = 0.0 [node name="SortSpacer" type="Control" parent="MenuPanel/Margin/Layout/Content/InventorySection/SortBar"] layout_mode = 2 @@ -142,25 +165,29 @@ size_flags_horizontal = 3 [node name="CoolerCount" type="Label" parent="MenuPanel/Margin/Layout/Content/InventorySection/SortBar"] unique_name_in_owner = true +visible = false layout_mode = 2 text = "0 / 12" theme_override_colors/font_color = Color(0.208, 0.725, 0.78, 1) [node name="HeldValue" type="Label" parent="MenuPanel/Margin/Layout/Content/InventorySection/SortBar"] unique_name_in_owner = true +visible = false layout_mode = 2 text = "held fish base value: $0" theme_override_colors/font_color = Color(0.682, 0.733, 0.761, 1) -[node name="InventoryBody" type="HSplitContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection"] +[node name="InventoryBody" type="BoxContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection"] +unique_name_in_owner = true layout_mode = 2 size_flags_vertical = 3 -split_offset = 510 theme_override_constants/separation = 8 [node name="InventoryList" type="PanelContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody"] +unique_name_in_owner = true custom_minimum_size = Vector2(350, 0) layout_mode = 2 +theme_override_styles/panel = SubResource("StyleBoxEmpty_collection") [node name="InventoryListMargin" type="MarginContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/InventoryList"] layout_mode = 2 @@ -180,6 +207,7 @@ text = "your cooler is empty." horizontal_alignment = 1 [node name="InventoryScroll" type="ScrollContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/InventoryList/InventoryListMargin/InventoryStack"] +unique_name_in_owner = true layout_mode = 2 size_flags_vertical = 3 horizontal_scroll_mode = 0 @@ -193,8 +221,13 @@ theme_override_constants/v_separation = 7 columns = 3 [node name="DetailPanel" type="PanelContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody"] +unique_name_in_owner = true custom_minimum_size = Vector2(210, 0) layout_mode = 2 +script = ExtResource("8_info") +profile = ExtResource("4_profile") +content_margin = 12.0 +corner_radius = 96 [node name="DetailMargin" type="MarginContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel"] layout_mode = 2 @@ -221,33 +254,75 @@ layout_mode = 2 theme_override_colors/font_color = Color(0.208, 0.725, 0.78, 1) theme_override_font_sizes/font_size = 29 horizontal_alignment = 1 +theme_override_colors/font_color = Color(0.035, 0.145, 0.22, 1) [node name="DetailData" type="Label" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack"] unique_name_in_owner = true layout_mode = 2 autowrap_mode = 2 horizontal_alignment = 1 +theme_override_colors/font_color = Color(0.035, 0.145, 0.22, 1) [node name="SelectionSummary" type="Label" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack"] unique_name_in_owner = true +visible = false layout_mode = 2 text = "no fish selected" autowrap_mode = 2 horizontal_alignment = 1 theme_override_colors/font_color = Color(0.208, 0.725, 0.78, 1) -[node name="FavoriteButton" type="Button" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack"] +[node name="ActionRow" type="HBoxContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack"] +layout_mode = 2 +theme_override_constants/separation = 8 +alignment = 1 + +[node name="FavoriteButton" type="Button" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack/ActionRow"] unique_name_in_owner = true layout_mode = 2 disabled = true text = "favorite" +custom_minimum_size = Vector2(112, 104) +size_flags_horizontal = 4 +script = ExtResource("6_bubble") +profile = ExtResource("4_profile") +neutral_size = Vector2(112, 104) +minimum_font_size = 15 +maximum_font_size = 20 +horizontal_amplitude = 0.0 +vertical_amplitude = 0.0 +deformation_amplitude = 0.0 -[node name="SellButton" type="Button" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack"] +[node name="SellButton" type="Button" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack/ActionRow"] unique_name_in_owner = true layout_mode = 2 disabled = true text = "sell" -theme_type_variation = &"DangerButton" +custom_minimum_size = Vector2(112, 104) +size_flags_horizontal = 4 +script = ExtResource("6_bubble") +profile = ExtResource("4_profile") +neutral_size = Vector2(112, 104) +minimum_font_size = 15 +maximum_font_size = 20 +horizontal_amplitude = 0.0 +vertical_amplitude = 0.0 +deformation_amplitude = 0.0 + +[node name="ContextStatus" type="HBoxContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack"] +layout_mode = 2 +theme_override_constants/separation = 6 +alignment = 1 + +[node name="SelectionStatus" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack/ContextStatus" instance=ExtResource("7_status")] +unique_name_in_owner = true +custom_minimum_size = Vector2(108, 78) +layout_mode = 2 + +[node name="OfferStatus" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack/ContextStatus" instance=ExtResource("7_status")] +unique_name_in_owner = true +custom_minimum_size = Vector2(128, 78) +layout_mode = 2 [node name="SaleUnavailable" type="Label" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack"] unique_name_in_owner = true @@ -272,6 +347,7 @@ theme_override_constants/separation = 7 layout_mode = 2 text = "drag hotbar-compatible items onto a slot below. right-click a slot to clear it." horizontal_alignment = 1 +autowrap_mode = 2 [node name="BagBody" type="HSplitContainer" parent="MenuPanel/Margin/Layout/Content/BagSection"] layout_mode = 2 @@ -280,6 +356,7 @@ split_offset = 540 theme_override_constants/separation = 8 [node name="BagList" type="PanelContainer" parent="MenuPanel/Margin/Layout/Content/BagSection/BagBody"] +unique_name_in_owner = true custom_minimum_size = Vector2(360, 0) layout_mode = 2 @@ -314,6 +391,7 @@ theme_override_constants/v_separation = 7 columns = 3 [node name="BagDetail" type="PanelContainer" parent="MenuPanel/Margin/Layout/Content/BagSection/BagBody"] +unique_name_in_owner = true custom_minimum_size = Vector2(220, 0) layout_mode = 2 @@ -429,3 +507,99 @@ unique_name_in_owner = true layout_mode = 2 text = "cancel" custom_minimum_size = Vector2(170, 64) + +[node name="NavigationCluster" type="Control" parent="."] +unique_name_in_owner = true +layout_mode = 0 +offset_left = 260.0 +offset_top = 14.0 +offset_right = 1020.0 +offset_bottom = 110.0 +mouse_filter = 1 +script = ExtResource("5_cluster") +profile = ExtResource("4_profile") +desktop_reference_size = Vector2(760, 96) +compact_reference_size = Vector2(610, 74) + +[node name="InventoryTab" type="Button" parent="NavigationCluster"] +unique_name_in_owner = true +custom_minimum_size = Vector2(148, 138) +layout_mode = 0 +toggle_mode = true +text = "cooler" +script = ExtResource("6_bubble") +profile = ExtResource("4_profile") +neutral_size = Vector2(148, 138) +desktop_anchor = Vector2(118, 68) +compact_anchor = Vector2(118, 68) +compact_minimum_size = Vector2(84, 78) +minimum_font_size = 16 +maximum_font_size = 27 +horizontal_amplitude = 1.2 +vertical_amplitude = 2.4 +motion_period = 5.6 +motion_phase = 0.35 +deformation_amplitude = 0.012 +deformation_period = 6.4 + +[node name="BagTab" type="Button" parent="NavigationCluster"] +unique_name_in_owner = true +custom_minimum_size = Vector2(122, 116) +layout_mode = 0 +toggle_mode = true +text = "bag" +script = ExtResource("6_bubble") +profile = ExtResource("4_profile") +neutral_size = Vector2(122, 116) +desktop_anchor = Vector2(300, 62) +compact_anchor = Vector2(300, 62) +compact_minimum_size = Vector2(70, 68) +minimum_font_size = 16 +maximum_font_size = 25 +horizontal_amplitude = 1.0 +vertical_amplitude = 2.0 +motion_period = 5.1 +motion_phase = 1.65 +deformation_amplitude = 0.011 +deformation_period = 6.0 + +[node name="LogbookTab" type="Button" parent="NavigationCluster"] +unique_name_in_owner = true +custom_minimum_size = Vector2(158, 146) +layout_mode = 0 +toggle_mode = true +text = "logbook" +script = ExtResource("6_bubble") +profile = ExtResource("4_profile") +neutral_size = Vector2(158, 146) +desktop_anchor = Vector2(478, 70) +compact_anchor = Vector2(478, 70) +compact_minimum_size = Vector2(88, 80) +minimum_font_size = 16 +maximum_font_size = 27 +horizontal_amplitude = 1.3 +vertical_amplitude = 2.5 +motion_period = 5.9 +motion_phase = 2.85 +deformation_amplitude = 0.012 +deformation_period = 6.7 + +[node name="CloseButton" type="Button" parent="NavigationCluster"] +unique_name_in_owner = true +custom_minimum_size = Vector2(112, 106) +layout_mode = 0 +text = "close" +script = ExtResource("6_bubble") +profile = ExtResource("4_profile") +neutral_size = Vector2(112, 106) +desktop_anchor = Vector2(652, 61) +compact_anchor = Vector2(652, 61) +compact_minimum_size = Vector2(66, 64) +minimum_font_size = 15 +maximum_font_size = 23 +horizontal_amplitude = 1.0 +vertical_amplitude = 1.9 +motion_period = 5.3 +motion_phase = 4.2 +deformation_amplitude = 0.01 +deformation_period = 6.2