2026-07-30 23:16:28 -04:00
|
|
|
|
class_name LogbookPage
|
|
|
|
|
|
extends Control
|
|
|
|
|
|
|
|
|
|
|
|
const CollectionLogType = preload("res://collection/collection_log.gd")
|
|
|
|
|
|
const FishDataType = preload("res://fish/fish_data.gd")
|
2026-08-02 17:44:11 -04:00
|
|
|
|
const FishQualityType = preload("res://fish/fish_quality.gd")
|
2026-07-30 23:16:28 -04:00
|
|
|
|
const FishInventoryType = preload("res://inventory/fish_inventory.gd")
|
|
|
|
|
|
const FishPoolType = preload("res://fish/fish_pool.gd")
|
2026-08-29 20:19:47 -04:00
|
|
|
|
const FishingSpotType = preload("res://fishing/fishing_spot.gd")
|
|
|
|
|
|
const WorldTimeServiceType = preload("res://world/world_time_service.gd")
|
|
|
|
|
|
const WorldWeatherServiceType = preload(
|
|
|
|
|
|
"res://world/world_weather_service.gd"
|
|
|
|
|
|
)
|
2026-07-31 00:03:47 -04:00
|
|
|
|
const SILHOUETTE_SHADER: Shader = preload(
|
|
|
|
|
|
"res://ui/logbook_silhouette.gdshader"
|
|
|
|
|
|
)
|
2026-07-31 10:20:18 -04:00
|
|
|
|
const OrganizerTabType = preload("res://ui/components/organizer_tab.gd")
|
2026-08-29 20:19:47 -04:00
|
|
|
|
const ControllerFocusNavigationType = preload(
|
|
|
|
|
|
"res://ui/controller_focus_navigation.gd"
|
|
|
|
|
|
)
|
|
|
|
|
|
const NOTEPAD_INK_ACTION_SCENE: PackedScene = preload(
|
|
|
|
|
|
"res://ui/components/bubble_menu/notepad_ink_action.tscn"
|
|
|
|
|
|
)
|
2026-08-02 09:57:05 -04:00
|
|
|
|
const InventoryNotepadType = preload(
|
|
|
|
|
|
"res://ui/components/inventory_notepad.gd"
|
|
|
|
|
|
)
|
|
|
|
|
|
const LogbookPortraitType = preload(
|
|
|
|
|
|
"res://ui/components/logbook_portrait.gd"
|
|
|
|
|
|
)
|
2026-08-13 14:53:39 -04:00
|
|
|
|
const CurrencyPresentationType = preload(
|
|
|
|
|
|
"res://ui/currency_presentation.gd"
|
|
|
|
|
|
)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
|
|
|
|
|
|
const DETAIL_FADE_DURATION: float = 0.12
|
|
|
|
|
|
const CATEGORY_FADE_DURATION: float = UIMotion.UTILITY_EXIT_DURATION
|
2026-08-02 09:57:05 -04:00
|
|
|
|
const HANDWRITTEN_NUMERIC_SCALE: float = 0.8
|
2026-08-19 00:07:24 -04:00
|
|
|
|
const PORTRAIT_VIEW_MAX_SIZE := Vector2(680.0, 420.0)
|
|
|
|
|
|
const DETAIL_OVERLAY_CARD_SIZE := Vector2(800.0, 600.0)
|
2026-08-15 19:20:42 -04:00
|
|
|
|
const DETAIL_OVERLAY_CONTENT_MARGIN: int = 34
|
2026-08-19 00:07:24 -04:00
|
|
|
|
const DETAIL_OVERLAY_TITLE_FONT_SIZE: int = 36
|
|
|
|
|
|
const DETAIL_OVERLAY_TEXT_FONT_SIZE: int = 28
|
|
|
|
|
|
const DETAIL_OVERLAY_FIELD_FONT_SIZE: int = 22
|
|
|
|
|
|
const DETAIL_OVERLAY_VALUE_FONT_SIZE: int = 26
|
2026-07-30 23:16:28 -04:00
|
|
|
|
const INK := Color("251b10")
|
|
|
|
|
|
const MUTED_INK := Color("6d5b45")
|
2026-08-08 00:34:06 -04:00
|
|
|
|
const LOGBOOK_ARTWORK: Texture2D = preload(
|
|
|
|
|
|
"res://ui/assets/logbook/ui_logbook.png"
|
|
|
|
|
|
)
|
|
|
|
|
|
const SCROLL_UP_TEXTURE: Texture2D = preload(
|
|
|
|
|
|
"res://ui/icons/pictograms/arrow_dark_up_full.png"
|
|
|
|
|
|
)
|
|
|
|
|
|
const SCROLL_DOWN_TEXTURE: Texture2D = preload(
|
|
|
|
|
|
"res://ui/icons/pictograms/arrow_dark_down_full.png"
|
|
|
|
|
|
)
|
|
|
|
|
|
const LOGBOOK_ARTWORK_SOURCE_SIZE := Vector2(512.0, 247.0)
|
|
|
|
|
|
const LEFT_PAGE_CONTENT_RECT := Rect2(56.0, 26.0, 190.0, 198.0)
|
|
|
|
|
|
const RIGHT_PAGE_CONTENT_RECT := Rect2(267.0, 26.0, 210.0, 198.0)
|
2026-08-15 14:28:13 -04:00
|
|
|
|
const LOGBOOK_TAB_LEFT_INSET: float = 92.0
|
|
|
|
|
|
const LOGBOOK_TAB_SIZE := Vector2(108.0, 38.0)
|
|
|
|
|
|
const LOGBOOK_TAB_SEPARATION: float = 6.0
|
|
|
|
|
|
const LOGBOOK_TAB_TEXT_SIDE_INSET: float = 10.0
|
2026-08-29 20:19:47 -04:00
|
|
|
|
const INDEX_TAB_SIZE := Vector2(88.0, 38.0)
|
|
|
|
|
|
const INDEX_TAB_RIGHT_INSET: float = 72.0
|
2026-08-08 00:34:06 -04:00
|
|
|
|
const PAGE_CONTENT_SCALE: float = 0.97
|
2026-08-19 06:53:55 -04:00
|
|
|
|
const WIDE_OUTER_MARGINS := Rect2(52.0, 85.0, 52.0, 18.0)
|
|
|
|
|
|
const COMPACT_OUTER_MARGINS := Rect2(160.0, 85.0, 160.0, 106.0)
|
|
|
|
|
|
const WIDE_BOOK_TOP: float = 50.0
|
|
|
|
|
|
const COMPACT_BOOK_TOP: float = 65.0
|
2026-08-08 00:34:06 -04:00
|
|
|
|
const CATALOG_PORTRAIT_SIZE := Vector2(78.0, 36.0)
|
|
|
|
|
|
const CATALOG_ENTRY_SIZE := Vector2(92.0, 92.0)
|
|
|
|
|
|
const CATALOG_ROW_STEP: float = 98.0
|
|
|
|
|
|
const DETAIL_PORTRAIT_SIZE := Vector2(160.0, 88.0)
|
2026-08-19 00:07:24 -04:00
|
|
|
|
const DETAIL_SECTION_SEPARATION: int = 10
|
2026-08-19 06:53:55 -04:00
|
|
|
|
const DETAIL_QUALITY_SECTION_SIZE := Vector2(300.0, 104.0)
|
|
|
|
|
|
const DETAIL_STATS_SECTION_SIZE := Vector2(400.0, 160.0)
|
2026-08-19 00:07:24 -04:00
|
|
|
|
const DETAIL_STATS_ROW_SEPARATION: int = 8
|
2026-08-29 20:19:47 -04:00
|
|
|
|
const INDEX_OPTION_HEIGHT: float = 34.0
|
|
|
|
|
|
const INDEX_CONTENT_LEFT_INSET: float = 44.0
|
|
|
|
|
|
const INDEX_CONTENT_TOP_INSET: float = 4.0
|
|
|
|
|
|
const INDEX_CONTENT_RIGHT_INSET: float = 22.0
|
|
|
|
|
|
const INDEX_CONTENT_BOTTOM_INSET: float = 10.0
|
|
|
|
|
|
const INDEX_SHOW_LABELS := [
|
|
|
|
|
|
"all entries", "caught", "missing", "needs quality",
|
|
|
|
|
|
]
|
|
|
|
|
|
const INDEX_SORT_LABELS := [
|
|
|
|
|
|
"catalog no.", "name", "rarity", "number caught",
|
|
|
|
|
|
"quality progress", "value", "weight",
|
|
|
|
|
|
]
|
|
|
|
|
|
const INDEX_AVAILABILITY_LABELS := [
|
|
|
|
|
|
"any time", "available now",
|
|
|
|
|
|
]
|
|
|
|
|
|
const INDEX_SEASON_LABELS := [
|
|
|
|
|
|
"any season", "current season", "spring", "summer", "fall", "winter",
|
|
|
|
|
|
]
|
|
|
|
|
|
const INDEX_TIME_LABELS := [
|
|
|
|
|
|
"any time", "current time", "day", "night",
|
|
|
|
|
|
]
|
|
|
|
|
|
const INDEX_RARITY_LABELS := [
|
|
|
|
|
|
"any rarity", "common", "uncommon", "rare", "epic", "legendary",
|
|
|
|
|
|
]
|
|
|
|
|
|
const INDEX_METHOD_LABELS := [
|
|
|
|
|
|
"any method", "fishing", "netting", "digging",
|
|
|
|
|
|
]
|
|
|
|
|
|
const INDEX_HOLDING_LABELS := [
|
|
|
|
|
|
"any ownership", "in cooler", "favorited",
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
signal controller_text_entry_requested(control: Control)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
|
2026-08-15 19:20:42 -04:00
|
|
|
|
enum ControllerZone {
|
|
|
|
|
|
TABS,
|
|
|
|
|
|
ENTRIES,
|
|
|
|
|
|
DETAILS,
|
2026-08-29 20:19:47 -04:00
|
|
|
|
INDEX,
|
2026-08-15 19:20:42 -04:00
|
|
|
|
OVERLAY,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-30 23:16:28 -04:00
|
|
|
|
var _collection_log: CollectionLogType
|
|
|
|
|
|
var _inventory: FishInventoryType
|
|
|
|
|
|
var _catalog: FishPoolType
|
2026-08-29 20:19:47 -04:00
|
|
|
|
var _fishing_spot: FishingSpotType
|
|
|
|
|
|
var _world_time: WorldTimeServiceType
|
|
|
|
|
|
var _world_weather: WorldWeatherServiceType
|
2026-08-14 22:11:49 -04:00
|
|
|
|
var _category: LogbookCatalog.Category = (
|
|
|
|
|
|
LogbookCatalog.Category.FRESH_WATER
|
|
|
|
|
|
)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
var _selected_id: StringName
|
|
|
|
|
|
var _selected_entry_key: StringName
|
|
|
|
|
|
var _active: bool = false
|
|
|
|
|
|
var _interactive: bool = false
|
|
|
|
|
|
var _entry_buttons: Dictionary[StringName, Button] = {}
|
|
|
|
|
|
var _detail_generation: int = 0
|
|
|
|
|
|
var _detail_tween: Tween
|
|
|
|
|
|
var _category_generation: int = 0
|
|
|
|
|
|
var _category_tween: Tween
|
2026-07-31 00:03:47 -04:00
|
|
|
|
var _silhouette_material: ShaderMaterial
|
2026-08-19 06:53:55 -04:00
|
|
|
|
var _outer_margin: MarginContainer
|
|
|
|
|
|
var _book: Control
|
|
|
|
|
|
var _compact_presentation: bool = false
|
2026-07-30 23:16:28 -04:00
|
|
|
|
|
|
|
|
|
|
var _category_tabs: Array[Button] = []
|
2026-08-15 14:28:13 -04:00
|
|
|
|
var _category_tab_categories: Array[LogbookCatalog.Category] = []
|
2026-07-30 23:16:28 -04:00
|
|
|
|
var _catalog_scroll: ScrollContainer
|
|
|
|
|
|
var _catalog_grid: GridContainer
|
2026-08-08 00:34:06 -04:00
|
|
|
|
var _catalog_scroll_up_indicator: TextureRect
|
|
|
|
|
|
var _catalog_scroll_down_indicator: TextureRect
|
2026-07-30 23:16:28 -04:00
|
|
|
|
var _empty_state: Label
|
|
|
|
|
|
var _detail_body: VBoxContainer
|
2026-08-02 09:57:05 -04:00
|
|
|
|
var _detail_portrait_button: Button
|
2026-08-15 19:20:42 -04:00
|
|
|
|
var _detail_buttons: Array[Button] = []
|
2026-08-02 09:57:05 -04:00
|
|
|
|
var _portrait_overlay: Control
|
|
|
|
|
|
var _portrait_overlay_backdrop: Button
|
|
|
|
|
|
var _portrait_overlay_artwork: LogbookPortraitType
|
2026-08-19 00:07:24 -04:00
|
|
|
|
var _portrait_overlay_artwork_view: Control
|
2026-08-15 19:20:42 -04:00
|
|
|
|
var _portrait_overlay_title: Label
|
|
|
|
|
|
var _portrait_overlay_text: Label
|
2026-08-19 00:07:24 -04:00
|
|
|
|
var _portrait_overlay_quality_view: Control
|
|
|
|
|
|
var _portrait_overlay_quality: VBoxContainer
|
|
|
|
|
|
var _portrait_overlay_stats_view: Control
|
|
|
|
|
|
var _portrait_overlay_stats: GridContainer
|
2026-08-15 19:20:42 -04:00
|
|
|
|
var _overlay_return_focus: Control
|
|
|
|
|
|
var _controller_zone: ControllerZone = ControllerZone.TABS
|
2026-08-29 20:19:47 -04:00
|
|
|
|
var _index_tab: OrganizerTabType
|
|
|
|
|
|
var _index_body: VBoxContainer
|
|
|
|
|
|
var _index_search: LineEdit
|
|
|
|
|
|
var _index_summary: Label
|
|
|
|
|
|
var _index_note: Label
|
|
|
|
|
|
var _index_reset: Button
|
|
|
|
|
|
var _index_option_buttons: Dictionary[StringName, Button] = {}
|
|
|
|
|
|
var _index_controls: Array[Control] = []
|
|
|
|
|
|
var _index_open: bool = false
|
|
|
|
|
|
var _index_update_in_progress: bool = false
|
|
|
|
|
|
var _index_visible_count: int = 0
|
|
|
|
|
|
var _index_category_total: int = 0
|
|
|
|
|
|
var _index_options: Dictionary = {
|
|
|
|
|
|
"search": "",
|
|
|
|
|
|
"show": LogbookCatalog.DiscoveryFilter.ALL,
|
|
|
|
|
|
"sort": LogbookCatalog.SortMode.CATALOG_NUMBER,
|
|
|
|
|
|
"descending": false,
|
|
|
|
|
|
"availability": LogbookCatalog.AvailabilityFilter.ANY,
|
|
|
|
|
|
"season": LogbookCatalog.SeasonFilter.ANY,
|
|
|
|
|
|
"time": LogbookCatalog.TimeFilter.ANY,
|
|
|
|
|
|
"rarity": LogbookCatalog.RarityFilter.ANY,
|
|
|
|
|
|
"method": LogbookCatalog.MethodFilter.ANY,
|
|
|
|
|
|
"holding": LogbookCatalog.HoldingFilter.ANY,
|
|
|
|
|
|
"group": "",
|
|
|
|
|
|
}
|
2026-07-30 23:16:28 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _ready() -> void:
|
2026-07-31 00:03:47 -04:00
|
|
|
|
_silhouette_material = ShaderMaterial.new()
|
|
|
|
|
|
_silhouette_material.shader = SILHOUETTE_SHADER
|
2026-07-30 23:16:28 -04:00
|
|
|
|
_build_interface()
|
|
|
|
|
|
set_interactive(false)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func setup(
|
|
|
|
|
|
collection_log: CollectionLogType,
|
|
|
|
|
|
inventory: FishInventoryType,
|
|
|
|
|
|
catalog: FishPoolType,
|
2026-08-29 20:19:47 -04:00
|
|
|
|
fishing_spot: FishingSpotType = null,
|
|
|
|
|
|
world_time: WorldTimeServiceType = null,
|
|
|
|
|
|
world_weather: WorldWeatherServiceType = null,
|
2026-07-30 23:16:28 -04:00
|
|
|
|
) -> void:
|
|
|
|
|
|
_collection_log = collection_log
|
|
|
|
|
|
_inventory = inventory
|
|
|
|
|
|
_catalog = catalog
|
2026-08-29 20:19:47 -04:00
|
|
|
|
_fishing_spot = fishing_spot
|
|
|
|
|
|
_world_time = world_time
|
|
|
|
|
|
_world_weather = world_weather
|
2026-07-30 23:16:28 -04:00
|
|
|
|
if not _collection_log.fish_discovered.is_connected(
|
|
|
|
|
|
_on_fish_discovered
|
|
|
|
|
|
):
|
|
|
|
|
|
_collection_log.fish_discovered.connect(_on_fish_discovered)
|
|
|
|
|
|
if not _collection_log.collection_changed.is_connected(
|
|
|
|
|
|
_on_collection_changed
|
|
|
|
|
|
):
|
|
|
|
|
|
_collection_log.collection_changed.connect(_on_collection_changed)
|
|
|
|
|
|
if not _inventory.catches_changed.is_connected(_on_inventory_changed):
|
|
|
|
|
|
_inventory.catches_changed.connect(_on_inventory_changed)
|
2026-08-29 20:19:47 -04:00
|
|
|
|
if (
|
|
|
|
|
|
_world_time != null
|
|
|
|
|
|
and not _world_time.time_changed.is_connected(
|
|
|
|
|
|
_on_logbook_context_changed
|
|
|
|
|
|
)
|
|
|
|
|
|
):
|
|
|
|
|
|
_world_time.time_changed.connect(_on_logbook_context_changed)
|
|
|
|
|
|
_world_time.calendar_date_changed.connect(_on_logbook_context_changed)
|
|
|
|
|
|
if (
|
|
|
|
|
|
_world_weather != null
|
|
|
|
|
|
and not _world_weather.weather_changed.is_connected(
|
|
|
|
|
|
_on_logbook_context_changed
|
|
|
|
|
|
)
|
|
|
|
|
|
):
|
|
|
|
|
|
_world_weather.weather_changed.connect(_on_logbook_context_changed)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
_refresh_catalog()
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-08-19 06:53:55 -04:00
|
|
|
|
func set_compact_presentation(compact: bool) -> void:
|
|
|
|
|
|
_compact_presentation = compact
|
|
|
|
|
|
_apply_presentation_margins()
|
2026-08-29 20:19:47 -04:00
|
|
|
|
_apply_index_presentation()
|
2026-08-19 06:53:55 -04:00
|
|
|
|
|
|
|
|
|
|
|
2026-07-30 23:16:28 -04:00
|
|
|
|
func activate() -> void:
|
|
|
|
|
|
_active = true
|
|
|
|
|
|
set_interactive(true)
|
|
|
|
|
|
_refresh_catalog()
|
2026-07-31 10:20:18 -04:00
|
|
|
|
_animate_tab_entry()
|
2026-07-30 23:16:28 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func deactivate() -> void:
|
|
|
|
|
|
_active = false
|
2026-08-02 09:57:05 -04:00
|
|
|
|
_hide_portrait_overlay(false)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
set_interactive(false)
|
2026-07-31 10:20:18 -04:00
|
|
|
|
_settle_tabs_for_close()
|
2026-07-30 23:16:28 -04:00
|
|
|
|
_cancel_detail_tween()
|
|
|
|
|
|
_cancel_category_tween()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func set_interactive(value: bool) -> void:
|
|
|
|
|
|
_interactive = value and _active
|
|
|
|
|
|
mouse_filter = (
|
|
|
|
|
|
Control.MOUSE_FILTER_PASS
|
|
|
|
|
|
if _interactive
|
|
|
|
|
|
else Control.MOUSE_FILTER_IGNORE
|
|
|
|
|
|
)
|
|
|
|
|
|
for tab: Button in _category_tabs:
|
|
|
|
|
|
tab.focus_mode = (
|
2026-08-15 19:20:42 -04:00
|
|
|
|
Control.FOCUS_ALL
|
|
|
|
|
|
if _interactive and _controller_zone == ControllerZone.TABS
|
|
|
|
|
|
else Control.FOCUS_NONE
|
2026-07-30 23:16:28 -04:00
|
|
|
|
)
|
|
|
|
|
|
tab.mouse_filter = (
|
|
|
|
|
|
Control.MOUSE_FILTER_STOP
|
2026-07-31 13:14:55 -04:00
|
|
|
|
if _interactive and not tab.button_pressed
|
2026-07-30 23:16:28 -04:00
|
|
|
|
else Control.MOUSE_FILTER_IGNORE
|
|
|
|
|
|
)
|
2026-08-29 20:19:47 -04:00
|
|
|
|
if _index_tab != null:
|
|
|
|
|
|
_index_tab.focus_mode = (
|
|
|
|
|
|
Control.FOCUS_ALL
|
|
|
|
|
|
if _interactive and _controller_zone == ControllerZone.TABS
|
|
|
|
|
|
else Control.FOCUS_NONE
|
|
|
|
|
|
)
|
|
|
|
|
|
_index_tab.mouse_filter = (
|
|
|
|
|
|
Control.MOUSE_FILTER_STOP
|
|
|
|
|
|
if _interactive
|
|
|
|
|
|
else Control.MOUSE_FILTER_IGNORE
|
|
|
|
|
|
)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
for entry: Button in _entry_buttons.values():
|
|
|
|
|
|
entry.focus_mode = (
|
2026-08-15 19:20:42 -04:00
|
|
|
|
Control.FOCUS_ALL
|
|
|
|
|
|
if _interactive and _controller_zone == ControllerZone.ENTRIES
|
|
|
|
|
|
else Control.FOCUS_NONE
|
2026-07-30 23:16:28 -04:00
|
|
|
|
)
|
|
|
|
|
|
entry.mouse_filter = (
|
|
|
|
|
|
Control.MOUSE_FILTER_STOP
|
|
|
|
|
|
if _interactive
|
|
|
|
|
|
else Control.MOUSE_FILTER_IGNORE
|
|
|
|
|
|
)
|
2026-08-15 19:20:42 -04:00
|
|
|
|
for detail_button: Button in _detail_buttons:
|
|
|
|
|
|
if not is_instance_valid(detail_button):
|
|
|
|
|
|
continue
|
|
|
|
|
|
detail_button.focus_mode = (
|
|
|
|
|
|
Control.FOCUS_ALL
|
|
|
|
|
|
if _interactive and _controller_zone == ControllerZone.DETAILS
|
|
|
|
|
|
else Control.FOCUS_NONE
|
|
|
|
|
|
)
|
2026-08-29 20:19:47 -04:00
|
|
|
|
for index_control: Control in _index_controls:
|
|
|
|
|
|
if not is_instance_valid(index_control):
|
|
|
|
|
|
continue
|
|
|
|
|
|
index_control.focus_mode = (
|
|
|
|
|
|
Control.FOCUS_ALL
|
|
|
|
|
|
if _interactive and _controller_zone == ControllerZone.INDEX
|
|
|
|
|
|
else Control.FOCUS_NONE
|
|
|
|
|
|
)
|
|
|
|
|
|
index_control.mouse_filter = (
|
|
|
|
|
|
Control.MOUSE_FILTER_STOP
|
|
|
|
|
|
if _interactive and _index_open
|
|
|
|
|
|
else Control.MOUSE_FILTER_IGNORE
|
|
|
|
|
|
)
|
2026-08-15 19:20:42 -04:00
|
|
|
|
if _portrait_overlay_backdrop != null:
|
|
|
|
|
|
_portrait_overlay_backdrop.focus_mode = (
|
|
|
|
|
|
Control.FOCUS_ALL
|
|
|
|
|
|
if _interactive and _controller_zone == ControllerZone.OVERLAY
|
|
|
|
|
|
else Control.FOCUS_NONE
|
|
|
|
|
|
)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func focus_initial() -> void:
|
|
|
|
|
|
if not _active or not _interactive:
|
|
|
|
|
|
return
|
2026-08-15 19:20:42 -04:00
|
|
|
|
if _controller_zone == ControllerZone.OVERLAY:
|
2026-08-02 09:57:05 -04:00
|
|
|
|
_portrait_overlay_backdrop.grab_focus()
|
|
|
|
|
|
return
|
2026-08-15 19:20:42 -04:00
|
|
|
|
if _controller_zone == ControllerZone.TABS:
|
2026-08-29 20:19:47 -04:00
|
|
|
|
if _index_open and _index_tab != null:
|
|
|
|
|
|
_index_tab.grab_focus()
|
|
|
|
|
|
return
|
2026-08-15 19:20:42 -04:00
|
|
|
|
var category_tab_index: int = _category_tab_categories.find(_category)
|
|
|
|
|
|
if category_tab_index >= 0:
|
|
|
|
|
|
_category_tabs[category_tab_index].grab_focus()
|
|
|
|
|
|
return
|
|
|
|
|
|
if _controller_zone == ControllerZone.DETAILS:
|
|
|
|
|
|
if not _detail_buttons.is_empty():
|
|
|
|
|
|
_detail_buttons.front().grab_focus()
|
|
|
|
|
|
return
|
2026-08-29 20:19:47 -04:00
|
|
|
|
if _controller_zone == ControllerZone.INDEX:
|
|
|
|
|
|
if _index_search != null:
|
|
|
|
|
|
_index_search.grab_focus()
|
|
|
|
|
|
return
|
2026-08-15 19:20:42 -04:00
|
|
|
|
var selected := _entry_buttons.get(_selected_entry_key) as Button
|
2026-07-30 23:16:28 -04:00
|
|
|
|
if selected != null:
|
|
|
|
|
|
selected.grab_focus()
|
|
|
|
|
|
elif not _entry_buttons.is_empty():
|
|
|
|
|
|
var first := _entry_buttons.values().front() as Button
|
|
|
|
|
|
first.grab_focus()
|
2026-08-15 19:20:42 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func reset_controller_zone() -> void:
|
|
|
|
|
|
_controller_zone = ControllerZone.TABS
|
|
|
|
|
|
set_interactive(_interactive)
|
|
|
|
|
|
call_deferred("focus_initial")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func handle_controller_input(event: InputEvent) -> bool:
|
|
|
|
|
|
if not _active or not _interactive:
|
|
|
|
|
|
return false
|
|
|
|
|
|
var accept_pressed: bool = event.is_action_pressed("ui_accept")
|
|
|
|
|
|
var cancel_pressed: bool = event.is_action_pressed("ui_cancel")
|
|
|
|
|
|
if cancel_pressed:
|
|
|
|
|
|
match _controller_zone:
|
|
|
|
|
|
ControllerZone.OVERLAY:
|
|
|
|
|
|
_hide_portrait_overlay()
|
|
|
|
|
|
ControllerZone.DETAILS:
|
|
|
|
|
|
_set_controller_zone(ControllerZone.ENTRIES)
|
2026-08-29 20:19:47 -04:00
|
|
|
|
ControllerZone.INDEX:
|
|
|
|
|
|
_set_index_open(false)
|
2026-08-15 19:20:42 -04:00
|
|
|
|
ControllerZone.ENTRIES:
|
|
|
|
|
|
_set_controller_zone(ControllerZone.TABS)
|
|
|
|
|
|
ControllerZone.TABS:
|
|
|
|
|
|
return false
|
|
|
|
|
|
return true
|
|
|
|
|
|
if _controller_zone == ControllerZone.TABS:
|
|
|
|
|
|
if event.is_action_pressed("ui_left"):
|
2026-08-29 20:19:47 -04:00
|
|
|
|
if _index_open:
|
|
|
|
|
|
_set_index_open(false, false)
|
|
|
|
|
|
_category_tabs.back().grab_focus()
|
|
|
|
|
|
return true
|
2026-08-15 19:20:42 -04:00
|
|
|
|
_select_adjacent_controller_category(-1)
|
|
|
|
|
|
return true
|
|
|
|
|
|
if event.is_action_pressed("ui_right"):
|
2026-08-29 20:19:47 -04:00
|
|
|
|
if (
|
|
|
|
|
|
_category_tab_categories.find(_category)
|
|
|
|
|
|
== _category_tab_categories.size() - 1
|
|
|
|
|
|
):
|
|
|
|
|
|
_set_index_open(true, false)
|
|
|
|
|
|
_index_tab.grab_focus()
|
|
|
|
|
|
return true
|
2026-08-15 19:20:42 -04:00
|
|
|
|
_select_adjacent_controller_category(1)
|
|
|
|
|
|
return true
|
|
|
|
|
|
if accept_pressed:
|
2026-08-29 20:19:47 -04:00
|
|
|
|
if _index_open:
|
|
|
|
|
|
_set_controller_zone(ControllerZone.INDEX)
|
|
|
|
|
|
elif not _entry_buttons.is_empty():
|
2026-08-15 19:20:42 -04:00
|
|
|
|
_set_controller_zone(ControllerZone.ENTRIES)
|
|
|
|
|
|
return true
|
2026-08-29 20:19:47 -04:00
|
|
|
|
if _controller_zone == ControllerZone.INDEX:
|
|
|
|
|
|
var focus_owner := get_viewport().gui_get_focus_owner()
|
|
|
|
|
|
if accept_pressed and focus_owner == _index_search:
|
|
|
|
|
|
controller_text_entry_requested.emit(_index_search)
|
|
|
|
|
|
return true
|
|
|
|
|
|
if event.is_action_pressed("ui_left"):
|
|
|
|
|
|
var previous_key := _index_key_for_control(focus_owner)
|
|
|
|
|
|
if not previous_key.is_empty():
|
|
|
|
|
|
_cycle_index_option(previous_key, -1)
|
|
|
|
|
|
return true
|
|
|
|
|
|
if event.is_action_pressed("ui_right") or accept_pressed:
|
|
|
|
|
|
var next_key := _index_key_for_control(focus_owner)
|
|
|
|
|
|
if not next_key.is_empty():
|
|
|
|
|
|
_cycle_index_option(next_key, 1)
|
|
|
|
|
|
return true
|
2026-08-15 19:20:42 -04:00
|
|
|
|
if _controller_zone == ControllerZone.ENTRIES and accept_pressed:
|
|
|
|
|
|
if not _selected_entry_key.is_empty() and not _detail_buttons.is_empty():
|
|
|
|
|
|
_set_controller_zone(ControllerZone.DETAILS)
|
|
|
|
|
|
return true
|
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _set_controller_zone(zone: ControllerZone) -> void:
|
|
|
|
|
|
_controller_zone = zone
|
|
|
|
|
|
set_interactive(_interactive)
|
|
|
|
|
|
call_deferred("focus_initial")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _select_adjacent_controller_category(direction: int) -> void:
|
|
|
|
|
|
var current_index: int = _category_tab_categories.find(_category)
|
|
|
|
|
|
var target_index: int = clampi(
|
|
|
|
|
|
current_index + direction,
|
|
|
|
|
|
0,
|
|
|
|
|
|
_category_tab_categories.size() - 1,
|
|
|
|
|
|
)
|
|
|
|
|
|
if target_index != current_index:
|
|
|
|
|
|
_select_category(_category_tab_categories[target_index])
|
2026-07-30 23:16:28 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _build_interface() -> void:
|
|
|
|
|
|
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
2026-08-19 06:53:55 -04:00
|
|
|
|
_outer_margin = MarginContainer.new()
|
|
|
|
|
|
_outer_margin.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
|
|
|
|
|
add_child(_outer_margin)
|
|
|
|
|
|
_apply_presentation_margins()
|
2026-07-30 23:16:28 -04:00
|
|
|
|
|
2026-07-31 10:20:18 -04:00
|
|
|
|
var stack := Control.new()
|
|
|
|
|
|
stack.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
2026-08-19 06:53:55 -04:00
|
|
|
|
_outer_margin.add_child(stack)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
|
|
|
|
|
|
var tabs := HBoxContainer.new()
|
2026-08-08 00:34:06 -04:00
|
|
|
|
# Align the first flange with the authored left paper edge.
|
|
|
|
|
|
tabs.position = Vector2(LOGBOOK_TAB_LEFT_INSET, 37.0)
|
2026-08-15 14:28:13 -04:00
|
|
|
|
var category_order: Array[LogbookCatalog.Category] = [
|
2026-08-14 22:11:49 -04:00
|
|
|
|
LogbookCatalog.Category.FRESH_WATER,
|
|
|
|
|
|
LogbookCatalog.Category.SALT_WATER,
|
|
|
|
|
|
LogbookCatalog.Category.SHELLFISH,
|
2026-08-15 14:28:13 -04:00
|
|
|
|
LogbookCatalog.Category.OTHER,
|
|
|
|
|
|
]
|
|
|
|
|
|
tabs.size = Vector2(
|
|
|
|
|
|
LOGBOOK_TAB_SIZE.x * float(category_order.size())
|
|
|
|
|
|
+ LOGBOOK_TAB_SEPARATION * float(category_order.size() - 1),
|
|
|
|
|
|
LOGBOOK_TAB_SIZE.y,
|
|
|
|
|
|
)
|
|
|
|
|
|
tabs.z_index = 10
|
|
|
|
|
|
tabs.mouse_filter = Control.MOUSE_FILTER_PASS
|
|
|
|
|
|
tabs.add_theme_constant_override(
|
|
|
|
|
|
"separation", roundi(LOGBOOK_TAB_SEPARATION)
|
|
|
|
|
|
)
|
|
|
|
|
|
stack.add_child(tabs)
|
|
|
|
|
|
for category: LogbookCatalog.Category in category_order:
|
2026-07-31 10:20:18 -04:00
|
|
|
|
var tab := OrganizerTabType.new()
|
2026-08-15 14:28:13 -04:00
|
|
|
|
tab.custom_minimum_size = LOGBOOK_TAB_SIZE
|
2026-07-30 23:16:28 -04:00
|
|
|
|
tab.toggle_mode = true
|
|
|
|
|
|
tab.text = LogbookCatalog.category_label(category)
|
2026-07-31 13:14:55 -04:00
|
|
|
|
tab.palette_index = int(category)
|
|
|
|
|
|
tab.set_selected(category == _category, false)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
tab.pressed.connect(_select_category.bind(category))
|
|
|
|
|
|
tabs.add_child(tab)
|
|
|
|
|
|
_category_tabs.append(tab)
|
2026-08-15 14:28:13 -04:00
|
|
|
|
_category_tab_categories.append(category)
|
2026-08-29 20:19:47 -04:00
|
|
|
|
_index_tab = OrganizerTabType.new()
|
|
|
|
|
|
_index_tab.name = "CatalogIndexTab"
|
|
|
|
|
|
_index_tab.anchor_left = 1.0
|
|
|
|
|
|
_index_tab.anchor_right = 1.0
|
|
|
|
|
|
_index_tab.offset_left = -INDEX_TAB_RIGHT_INSET - INDEX_TAB_SIZE.x
|
|
|
|
|
|
_index_tab.offset_top = 37.0
|
|
|
|
|
|
_index_tab.offset_right = -INDEX_TAB_RIGHT_INSET
|
|
|
|
|
|
_index_tab.offset_bottom = 37.0 + INDEX_TAB_SIZE.y
|
|
|
|
|
|
_index_tab.text = "index"
|
|
|
|
|
|
_index_tab.palette_index = 0
|
|
|
|
|
|
_index_tab.tooltip_text = "Open catalog index"
|
|
|
|
|
|
_index_tab.accessibility_name = "Catalog index"
|
|
|
|
|
|
_index_tab.pressed.connect(_toggle_index_page)
|
|
|
|
|
|
stack.add_child(_index_tab)
|
2026-07-31 10:20:18 -04:00
|
|
|
|
_configure_category_focus()
|
2026-07-30 23:16:28 -04:00
|
|
|
|
|
2026-08-08 00:34:06 -04:00
|
|
|
|
var book := Control.new()
|
2026-08-19 06:53:55 -04:00
|
|
|
|
_book = book
|
2026-07-31 10:20:18 -04:00
|
|
|
|
book.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
2026-08-19 06:53:55 -04:00
|
|
|
|
book.offset_top = (
|
|
|
|
|
|
COMPACT_BOOK_TOP if _compact_presentation else WIDE_BOOK_TOP
|
|
|
|
|
|
)
|
2026-07-31 10:20:18 -04:00
|
|
|
|
book.z_index = 20
|
2026-08-08 00:34:06 -04:00
|
|
|
|
book.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
2026-07-31 10:20:18 -04:00
|
|
|
|
stack.add_child(book)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
|
2026-08-08 00:34:06 -04:00
|
|
|
|
var artwork := TextureRect.new()
|
|
|
|
|
|
artwork.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
|
|
|
|
|
artwork.texture = LOGBOOK_ARTWORK
|
|
|
|
|
|
artwork.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
|
|
|
|
|
artwork.stretch_mode = TextureRect.STRETCH_SCALE
|
|
|
|
|
|
artwork.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
|
|
|
|
|
artwork.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
|
|
|
|
book.add_child(artwork)
|
|
|
|
|
|
|
|
|
|
|
|
var left_page := Control.new()
|
|
|
|
|
|
left_page.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
|
|
|
|
_apply_artwork_rect(left_page, LEFT_PAGE_CONTENT_RECT)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
book.add_child(left_page)
|
|
|
|
|
|
var left_layout := VBoxContainer.new()
|
2026-08-08 00:34:06 -04:00
|
|
|
|
left_layout.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
left_layout.add_theme_constant_override("separation", 8)
|
2026-08-08 00:34:06 -04:00
|
|
|
|
left_page.add_child(left_layout)
|
|
|
|
|
|
_apply_page_content_scale(left_layout)
|
|
|
|
|
|
var scroll_indicator_top_lane := Control.new()
|
|
|
|
|
|
scroll_indicator_top_lane.custom_minimum_size.y = 20.0
|
|
|
|
|
|
scroll_indicator_top_lane.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
|
|
|
|
left_layout.add_child(scroll_indicator_top_lane)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
_empty_state = _label("", 18)
|
|
|
|
|
|
_empty_state.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
|
|
|
|
_empty_state.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
|
|
|
|
|
_empty_state.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
|
|
|
|
|
left_layout.add_child(_empty_state)
|
2026-08-08 00:34:06 -04:00
|
|
|
|
var catalog_scroll_margin := MarginContainer.new()
|
|
|
|
|
|
catalog_scroll_margin.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
|
|
|
|
|
catalog_scroll_margin.add_theme_constant_override("margin_left", 20)
|
|
|
|
|
|
left_layout.add_child(catalog_scroll_margin)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
_catalog_scroll = ScrollContainer.new()
|
|
|
|
|
|
_catalog_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
|
|
|
|
|
_catalog_scroll.horizontal_scroll_mode = (
|
|
|
|
|
|
ScrollContainer.SCROLL_MODE_DISABLED
|
|
|
|
|
|
)
|
2026-08-16 16:40:10 -04:00
|
|
|
|
_catalog_scroll.follow_focus = true
|
2026-08-08 00:34:06 -04:00
|
|
|
|
_catalog_scroll.scroll_vertical_custom_step = CATALOG_ROW_STEP
|
|
|
|
|
|
catalog_scroll_margin.add_child(_catalog_scroll)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
_catalog_grid = GridContainer.new()
|
2026-08-08 00:34:06 -04:00
|
|
|
|
_catalog_grid.columns = 4
|
2026-07-30 23:16:28 -04:00
|
|
|
|
_catalog_grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
2026-08-08 00:34:06 -04:00
|
|
|
|
_catalog_grid.add_theme_constant_override("h_separation", 6)
|
|
|
|
|
|
_catalog_grid.add_theme_constant_override("v_separation", 6)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
_catalog_scroll.add_child(_catalog_grid)
|
2026-08-08 00:34:06 -04:00
|
|
|
|
var scroll_indicator_bottom_lane := Control.new()
|
|
|
|
|
|
scroll_indicator_bottom_lane.custom_minimum_size.y = 32.0
|
|
|
|
|
|
scroll_indicator_bottom_lane.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
|
|
|
|
left_layout.add_child(scroll_indicator_bottom_lane)
|
|
|
|
|
|
_configure_catalog_scroll_bar()
|
|
|
|
|
|
_catalog_scroll_up_indicator = _make_scroll_indicator(
|
|
|
|
|
|
"CatalogScrollUpIndicator",
|
|
|
|
|
|
SCROLL_UP_TEXTURE,
|
|
|
|
|
|
true,
|
2026-07-30 23:16:28 -04:00
|
|
|
|
)
|
2026-08-08 00:34:06 -04:00
|
|
|
|
left_page.add_child(_catalog_scroll_up_indicator)
|
|
|
|
|
|
_catalog_scroll_down_indicator = _make_scroll_indicator(
|
|
|
|
|
|
"CatalogScrollDownIndicator",
|
|
|
|
|
|
SCROLL_DOWN_TEXTURE,
|
|
|
|
|
|
false,
|
|
|
|
|
|
)
|
|
|
|
|
|
left_page.add_child(_catalog_scroll_down_indicator)
|
|
|
|
|
|
|
|
|
|
|
|
var right_page := Control.new()
|
|
|
|
|
|
right_page.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
|
|
|
|
_apply_artwork_rect(right_page, RIGHT_PAGE_CONTENT_RECT)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
book.add_child(right_page)
|
|
|
|
|
|
_detail_body = VBoxContainer.new()
|
2026-08-08 00:34:06 -04:00
|
|
|
|
_detail_body.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
_detail_body.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
2026-08-02 09:57:05 -04:00
|
|
|
|
_detail_body.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
2026-08-19 00:07:24 -04:00
|
|
|
|
_detail_body.add_theme_constant_override(
|
|
|
|
|
|
"separation", DETAIL_SECTION_SEPARATION
|
|
|
|
|
|
)
|
2026-08-08 00:34:06 -04:00
|
|
|
|
right_page.add_child(_detail_body)
|
|
|
|
|
|
_apply_page_content_scale(_detail_body)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
_show_no_selection()
|
2026-08-29 20:19:47 -04:00
|
|
|
|
_build_index_page(right_page)
|
2026-08-02 09:57:05 -04:00
|
|
|
|
_build_portrait_overlay()
|
2026-07-30 23:16:28 -04:00
|
|
|
|
|
|
|
|
|
|
|
2026-08-29 20:19:47 -04:00
|
|
|
|
func _build_index_page(right_page: Control) -> void:
|
|
|
|
|
|
_index_body = VBoxContainer.new()
|
|
|
|
|
|
_index_body.name = "CatalogIndex"
|
|
|
|
|
|
_index_body.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
|
|
|
|
|
_index_body.offset_left = INDEX_CONTENT_LEFT_INSET
|
|
|
|
|
|
_index_body.offset_top = INDEX_CONTENT_TOP_INSET
|
|
|
|
|
|
_index_body.offset_right = -INDEX_CONTENT_RIGHT_INSET
|
|
|
|
|
|
_index_body.offset_bottom = -INDEX_CONTENT_BOTTOM_INSET
|
|
|
|
|
|
_index_body.add_theme_constant_override("separation", 4)
|
|
|
|
|
|
_index_body.visible = false
|
|
|
|
|
|
right_page.add_child(_index_body)
|
|
|
|
|
|
_apply_page_content_scale(_index_body)
|
|
|
|
|
|
_apply_index_presentation()
|
|
|
|
|
|
|
|
|
|
|
|
var heading := _label("catalog index", 28)
|
|
|
|
|
|
heading.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
|
|
|
|
_index_body.add_child(heading)
|
|
|
|
|
|
_index_summary = _field_label("", 14)
|
|
|
|
|
|
_index_summary.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
|
|
|
|
_index_summary.add_theme_color_override("font_color", MUTED_INK)
|
|
|
|
|
|
_index_body.add_child(_index_summary)
|
|
|
|
|
|
|
|
|
|
|
|
var search_stack := VBoxContainer.new()
|
|
|
|
|
|
search_stack.add_theme_constant_override("separation", 0)
|
|
|
|
|
|
_index_body.add_child(search_stack)
|
|
|
|
|
|
var search_label := _field_label("find by name, group, or catalog no.", 13)
|
|
|
|
|
|
search_label.add_theme_color_override("font_color", MUTED_INK)
|
|
|
|
|
|
search_stack.add_child(search_label)
|
|
|
|
|
|
_index_search = LineEdit.new()
|
|
|
|
|
|
_index_search.name = "CatalogIndexSearch"
|
|
|
|
|
|
_index_search.custom_minimum_size.y = 34.0
|
|
|
|
|
|
_index_search.placeholder_text = "write here..."
|
|
|
|
|
|
_index_search.max_length = 48
|
|
|
|
|
|
_index_search.add_theme_font_override(
|
|
|
|
|
|
"font", InventoryNotepadType.HANDWRITTEN_FONT
|
|
|
|
|
|
)
|
|
|
|
|
|
_index_search.add_theme_font_size_override("font_size", 18)
|
|
|
|
|
|
_index_search.add_theme_color_override("font_color", INK)
|
|
|
|
|
|
_index_search.add_theme_color_override("caret_color", INK)
|
|
|
|
|
|
_index_search.add_theme_color_override(
|
|
|
|
|
|
"font_placeholder_color", Color(MUTED_INK, 0.72)
|
|
|
|
|
|
)
|
|
|
|
|
|
for style_name: StringName in [
|
|
|
|
|
|
&"normal", &"focus", &"read_only",
|
|
|
|
|
|
]:
|
|
|
|
|
|
_index_search.add_theme_stylebox_override(
|
|
|
|
|
|
style_name,
|
|
|
|
|
|
_index_search_style(style_name == &"focus"),
|
|
|
|
|
|
)
|
|
|
|
|
|
_index_search.text_changed.connect(_on_index_search_changed)
|
|
|
|
|
|
search_stack.add_child(_index_search)
|
|
|
|
|
|
_index_controls.append(_index_search)
|
|
|
|
|
|
|
|
|
|
|
|
var options_grid := GridContainer.new()
|
|
|
|
|
|
options_grid.columns = 2
|
|
|
|
|
|
options_grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
|
|
|
|
options_grid.add_theme_constant_override("h_separation", 18)
|
|
|
|
|
|
options_grid.add_theme_constant_override("v_separation", 2)
|
|
|
|
|
|
_index_body.add_child(options_grid)
|
|
|
|
|
|
for descriptor: Dictionary in [
|
|
|
|
|
|
{"key": &"show", "label": "show"},
|
|
|
|
|
|
{"key": &"rarity", "label": "rarity"},
|
|
|
|
|
|
{"key": &"sort", "label": "sort by"},
|
|
|
|
|
|
{"key": &"availability", "label": "availability"},
|
|
|
|
|
|
{"key": &"descending", "label": "order"},
|
|
|
|
|
|
{"key": &"season", "label": "season"},
|
|
|
|
|
|
{"key": &"method", "label": "method"},
|
|
|
|
|
|
{"key": &"time", "label": "time of day"},
|
|
|
|
|
|
{"key": &"holding", "label": "ownership"},
|
|
|
|
|
|
{"key": &"group", "label": "collection group"},
|
|
|
|
|
|
]:
|
|
|
|
|
|
options_grid.add_child(_make_index_option(
|
|
|
|
|
|
descriptor["key"],
|
|
|
|
|
|
str(descriptor["label"]),
|
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
|
|
var footer := HBoxContainer.new()
|
|
|
|
|
|
footer.alignment = BoxContainer.ALIGNMENT_CENTER
|
|
|
|
|
|
_index_body.add_child(footer)
|
|
|
|
|
|
_index_reset = NOTEPAD_INK_ACTION_SCENE.instantiate() as Button
|
|
|
|
|
|
_index_reset.name = "CatalogIndexReset"
|
|
|
|
|
|
_index_reset.custom_minimum_size = Vector2(126.0, 34.0)
|
|
|
|
|
|
_index_reset.text = "clear index"
|
|
|
|
|
|
_apply_index_value_font(_index_reset)
|
|
|
|
|
|
_index_reset.tooltip_text = "Clear every catalog filter"
|
|
|
|
|
|
_index_reset.accessibility_name = "Clear catalog index filters"
|
|
|
|
|
|
_index_reset.pressed.connect(_reset_index)
|
|
|
|
|
|
footer.add_child(_index_reset)
|
|
|
|
|
|
_index_controls.append(_index_reset)
|
|
|
|
|
|
|
|
|
|
|
|
_index_note = _field_label(
|
|
|
|
|
|
"silhouettes keep names hidden in filtered results",
|
|
|
|
|
|
12,
|
|
|
|
|
|
)
|
|
|
|
|
|
_index_note.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
|
|
|
|
_index_note.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
|
|
|
|
_index_note.add_theme_color_override("font_color", Color(MUTED_INK, 0.82))
|
|
|
|
|
|
_index_body.add_child(_index_note)
|
|
|
|
|
|
_update_index_controls()
|
|
|
|
|
|
_configure_index_focus.call_deferred()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _make_index_option(key: StringName, label_text: String) -> Control:
|
|
|
|
|
|
var stack := VBoxContainer.new()
|
|
|
|
|
|
stack.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
|
|
|
|
stack.add_theme_constant_override("separation", -3)
|
|
|
|
|
|
var label := _field_label(label_text, 12)
|
|
|
|
|
|
label.add_theme_color_override("font_color", MUTED_INK)
|
|
|
|
|
|
stack.add_child(label)
|
|
|
|
|
|
var button := NOTEPAD_INK_ACTION_SCENE.instantiate() as Button
|
|
|
|
|
|
button.name = "CatalogIndex%s" % String(key).capitalize().replace(" ", "")
|
|
|
|
|
|
button.custom_minimum_size = Vector2(0.0, INDEX_OPTION_HEIGHT)
|
|
|
|
|
|
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
|
|
|
|
button.clip_text = true
|
|
|
|
|
|
_apply_index_value_font(button)
|
|
|
|
|
|
button.tooltip_text = "Change %s; use left or right on a controller" % label_text
|
|
|
|
|
|
button.pressed.connect(_cycle_index_option.bind(key, 1))
|
|
|
|
|
|
button.gui_input.connect(_on_index_option_gui_input.bind(key))
|
|
|
|
|
|
stack.add_child(button)
|
|
|
|
|
|
_index_option_buttons[key] = button
|
|
|
|
|
|
_index_controls.append(button)
|
|
|
|
|
|
return stack
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _apply_index_value_font(button: Button) -> void:
|
|
|
|
|
|
button.add_theme_font_override(
|
|
|
|
|
|
"font", InventoryNotepadType.HANDWRITTEN_FONT
|
|
|
|
|
|
)
|
|
|
|
|
|
button.add_theme_font_size_override("font_size", 18)
|
|
|
|
|
|
for color_name: StringName in [
|
|
|
|
|
|
&"font_color",
|
|
|
|
|
|
&"font_hover_color",
|
|
|
|
|
|
&"font_focus_color",
|
|
|
|
|
|
&"font_pressed_color",
|
|
|
|
|
|
]:
|
|
|
|
|
|
button.add_theme_color_override(color_name, INK)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _index_search_style(focused: bool) -> StyleBoxFlat:
|
|
|
|
|
|
var style := StyleBoxFlat.new()
|
|
|
|
|
|
style.bg_color = Color(1.0, 0.97, 0.82, 0.12 if focused else 0.0)
|
|
|
|
|
|
style.border_color = Color(INK, 0.72 if focused else 0.42)
|
|
|
|
|
|
style.border_width_bottom = 2
|
|
|
|
|
|
style.content_margin_left = 6
|
|
|
|
|
|
style.content_margin_right = 22
|
|
|
|
|
|
style.content_margin_top = 2
|
|
|
|
|
|
style.content_margin_bottom = 3
|
|
|
|
|
|
return style
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _configure_index_focus() -> void:
|
|
|
|
|
|
var controls: Array[Control] = []
|
|
|
|
|
|
for control: Control in _index_controls:
|
|
|
|
|
|
if control != null and is_instance_valid(control):
|
|
|
|
|
|
controls.append(control)
|
|
|
|
|
|
if not controls.is_empty():
|
|
|
|
|
|
ControllerFocusNavigationType.configure_spatial_neighbors(controls)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _apply_index_presentation() -> void:
|
|
|
|
|
|
if _index_body == null:
|
|
|
|
|
|
return
|
|
|
|
|
|
var scale_value := 0.82 if _compact_presentation else PAGE_CONTENT_SCALE
|
|
|
|
|
|
_index_body.scale = Vector2.ONE * scale_value
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-08-19 06:53:55 -04:00
|
|
|
|
func _apply_presentation_margins() -> void:
|
|
|
|
|
|
if _outer_margin == null:
|
|
|
|
|
|
return
|
|
|
|
|
|
var margins: Rect2 = (
|
|
|
|
|
|
COMPACT_OUTER_MARGINS
|
|
|
|
|
|
if _compact_presentation
|
|
|
|
|
|
else WIDE_OUTER_MARGINS
|
|
|
|
|
|
)
|
|
|
|
|
|
_outer_margin.add_theme_constant_override(
|
|
|
|
|
|
"margin_left", roundi(margins.position.x)
|
|
|
|
|
|
)
|
|
|
|
|
|
_outer_margin.add_theme_constant_override(
|
|
|
|
|
|
"margin_top", roundi(margins.position.y)
|
|
|
|
|
|
)
|
|
|
|
|
|
_outer_margin.add_theme_constant_override(
|
|
|
|
|
|
"margin_right", roundi(margins.size.x)
|
|
|
|
|
|
)
|
|
|
|
|
|
_outer_margin.add_theme_constant_override(
|
|
|
|
|
|
"margin_bottom", roundi(margins.size.y)
|
|
|
|
|
|
)
|
|
|
|
|
|
if _book != null:
|
|
|
|
|
|
_book.offset_top = (
|
|
|
|
|
|
COMPACT_BOOK_TOP
|
|
|
|
|
|
if _compact_presentation
|
|
|
|
|
|
else WIDE_BOOK_TOP
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-08-08 00:34:06 -04:00
|
|
|
|
func _apply_artwork_rect(control: Control, source_rect: Rect2) -> void:
|
|
|
|
|
|
control.anchor_left = source_rect.position.x / LOGBOOK_ARTWORK_SOURCE_SIZE.x
|
|
|
|
|
|
control.anchor_top = source_rect.position.y / LOGBOOK_ARTWORK_SOURCE_SIZE.y
|
|
|
|
|
|
control.anchor_right = source_rect.end.x / LOGBOOK_ARTWORK_SOURCE_SIZE.x
|
|
|
|
|
|
control.anchor_bottom = source_rect.end.y / LOGBOOK_ARTWORK_SOURCE_SIZE.y
|
|
|
|
|
|
control.offset_left = 0.0
|
|
|
|
|
|
control.offset_top = 0.0
|
|
|
|
|
|
control.offset_right = 0.0
|
|
|
|
|
|
control.offset_bottom = 0.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _apply_page_content_scale(control: Control) -> void:
|
|
|
|
|
|
control.scale = Vector2.ONE * PAGE_CONTENT_SCALE
|
|
|
|
|
|
control.resized.connect(_center_scaled_content.bind(control))
|
|
|
|
|
|
_center_scaled_content.call_deferred(control)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _center_scaled_content(control: Control) -> void:
|
|
|
|
|
|
if is_instance_valid(control):
|
|
|
|
|
|
control.pivot_offset = control.size * 0.5
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _configure_catalog_scroll_bar() -> void:
|
|
|
|
|
|
var scroll_bar: VScrollBar = _catalog_scroll.get_v_scroll_bar()
|
|
|
|
|
|
scroll_bar.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
|
|
|
|
scroll_bar.focus_mode = Control.FOCUS_NONE
|
|
|
|
|
|
scroll_bar.self_modulate = Color.TRANSPARENT
|
|
|
|
|
|
scroll_bar.custom_minimum_size.x = 0.0
|
|
|
|
|
|
scroll_bar.add_theme_constant_override("scroll_size", 0)
|
|
|
|
|
|
var empty_style := StyleBoxEmpty.new()
|
|
|
|
|
|
for style_name: StringName in [
|
|
|
|
|
|
&"scroll",
|
|
|
|
|
|
&"scroll_focus",
|
|
|
|
|
|
&"grabber",
|
|
|
|
|
|
&"grabber_highlight",
|
|
|
|
|
|
&"grabber_pressed",
|
|
|
|
|
|
]:
|
|
|
|
|
|
scroll_bar.add_theme_stylebox_override(style_name, empty_style)
|
|
|
|
|
|
scroll_bar.changed.connect(_refresh_catalog_scroll_indicators)
|
|
|
|
|
|
scroll_bar.value_changed.connect(_on_catalog_scroll_value_changed)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _make_scroll_indicator(
|
|
|
|
|
|
indicator_name: String,
|
|
|
|
|
|
indicator_texture: Texture2D,
|
|
|
|
|
|
at_top: bool,
|
|
|
|
|
|
) -> TextureRect:
|
|
|
|
|
|
var indicator := TextureRect.new()
|
|
|
|
|
|
indicator.name = indicator_name
|
|
|
|
|
|
indicator.anchor_left = 0.5
|
|
|
|
|
|
indicator.anchor_right = 0.5
|
|
|
|
|
|
indicator.anchor_top = 0.0 if at_top else 1.0
|
|
|
|
|
|
indicator.anchor_bottom = indicator.anchor_top
|
|
|
|
|
|
indicator.offset_left = -14.0
|
|
|
|
|
|
indicator.offset_right = 14.0
|
|
|
|
|
|
indicator.offset_top = -8.0 if at_top else -35.0
|
|
|
|
|
|
indicator.offset_bottom = 20.0 if at_top else -7.0
|
|
|
|
|
|
indicator.z_index = 30
|
|
|
|
|
|
indicator.texture = indicator_texture
|
|
|
|
|
|
indicator.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
|
|
|
|
|
indicator.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
|
|
|
|
|
indicator.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
|
|
|
|
|
indicator.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
|
|
|
|
indicator.visible = false
|
|
|
|
|
|
return indicator
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-08-09 13:31:30 -04:00
|
|
|
|
func _on_catalog_scroll_value_changed(_value: float) -> void:
|
2026-08-08 00:34:06 -04:00
|
|
|
|
_refresh_catalog_scroll_indicators()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _refresh_catalog_scroll_indicators() -> void:
|
|
|
|
|
|
if (
|
|
|
|
|
|
_catalog_scroll == null
|
|
|
|
|
|
or _catalog_scroll_up_indicator == null
|
|
|
|
|
|
or _catalog_scroll_down_indicator == null
|
|
|
|
|
|
):
|
|
|
|
|
|
return
|
|
|
|
|
|
var scroll_bar: VScrollBar = _catalog_scroll.get_v_scroll_bar()
|
|
|
|
|
|
var maximum_scroll: float = scroll_bar.max_value - scroll_bar.page
|
|
|
|
|
|
var has_overflow: bool = (
|
|
|
|
|
|
_catalog_scroll.visible
|
|
|
|
|
|
and maximum_scroll > scroll_bar.min_value + 0.5
|
|
|
|
|
|
)
|
|
|
|
|
|
_catalog_scroll_up_indicator.visible = (
|
|
|
|
|
|
has_overflow and scroll_bar.value > scroll_bar.min_value + 0.5
|
|
|
|
|
|
)
|
|
|
|
|
|
_catalog_scroll_down_indicator.visible = (
|
|
|
|
|
|
has_overflow and scroll_bar.value < maximum_scroll - 0.5
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-30 23:16:28 -04:00
|
|
|
|
func _refresh_catalog() -> void:
|
|
|
|
|
|
if not is_node_ready():
|
|
|
|
|
|
return
|
|
|
|
|
|
var preserved_selection: StringName = _selected_id
|
|
|
|
|
|
var preserved_entry_key: StringName = _selected_entry_key
|
|
|
|
|
|
_clear_entries()
|
|
|
|
|
|
var species: Array[FishDataType] = []
|
2026-08-29 20:19:47 -04:00
|
|
|
|
var category_total: int = 0
|
2026-07-30 23:16:28 -04:00
|
|
|
|
if _catalog != null:
|
2026-08-29 20:19:47 -04:00
|
|
|
|
for fish: FishDataType in LogbookCatalog.ordered_species(
|
|
|
|
|
|
_catalog.candidates
|
|
|
|
|
|
):
|
|
|
|
|
|
if LogbookCatalog.category_for(fish) == _category:
|
|
|
|
|
|
category_total += 1
|
|
|
|
|
|
var available_now_resolver := Callable()
|
|
|
|
|
|
if _fishing_spot != null and is_instance_valid(_fishing_spot):
|
|
|
|
|
|
available_now_resolver = Callable(
|
|
|
|
|
|
_fishing_spot,
|
|
|
|
|
|
"is_species_available_now",
|
|
|
|
|
|
)
|
|
|
|
|
|
species = LogbookCatalog.filtered_species(
|
|
|
|
|
|
_catalog.candidates,
|
|
|
|
|
|
_category,
|
|
|
|
|
|
_collection_log,
|
|
|
|
|
|
_inventory,
|
|
|
|
|
|
_index_options,
|
|
|
|
|
|
available_now_resolver,
|
|
|
|
|
|
_current_season(),
|
|
|
|
|
|
_current_is_night(),
|
|
|
|
|
|
)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
for fish: FishDataType in species:
|
|
|
|
|
|
var discovered: bool = _collection_log.has_discovered(fish.id)
|
2026-07-31 00:03:47 -04:00
|
|
|
|
var entry: Button = _make_entry(fish, discovered)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
var selection_key: StringName = (
|
|
|
|
|
|
fish.id if discovered else _unknown_selection_key(fish)
|
|
|
|
|
|
)
|
|
|
|
|
|
_entry_buttons[selection_key] = entry
|
|
|
|
|
|
entry.pressed.connect(
|
|
|
|
|
|
_select_entry.bind(
|
|
|
|
|
|
selection_key,
|
|
|
|
|
|
fish.id if discovered else StringName(),
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
entry.focus_entered.connect(
|
|
|
|
|
|
_select_entry.bind(
|
|
|
|
|
|
selection_key,
|
|
|
|
|
|
fish.id if discovered else StringName(),
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
_catalog_grid.add_child(entry)
|
2026-08-29 20:19:47 -04:00
|
|
|
|
_empty_state.text = (
|
|
|
|
|
|
"No entries match this index."
|
|
|
|
|
|
if _index_has_active_criteria()
|
|
|
|
|
|
else LogbookCatalog.empty_state(_category)
|
|
|
|
|
|
)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
_empty_state.visible = _entry_buttons.is_empty()
|
|
|
|
|
|
_catalog_scroll.visible = not _entry_buttons.is_empty()
|
|
|
|
|
|
if (
|
|
|
|
|
|
not preserved_selection.is_empty()
|
|
|
|
|
|
and _entry_buttons.has(preserved_selection)
|
|
|
|
|
|
):
|
|
|
|
|
|
_selected_id = preserved_selection
|
|
|
|
|
|
_selected_entry_key = preserved_selection
|
|
|
|
|
|
elif _entry_buttons.has(preserved_entry_key):
|
|
|
|
|
|
_selected_id = StringName()
|
|
|
|
|
|
_selected_entry_key = preserved_entry_key
|
|
|
|
|
|
else:
|
|
|
|
|
|
_selected_id = StringName()
|
|
|
|
|
|
_selected_entry_key = StringName()
|
|
|
|
|
|
_refresh_selection_styles()
|
|
|
|
|
|
_refresh_details(false)
|
2026-08-29 20:19:47 -04:00
|
|
|
|
_update_index_controls(species.size(), category_total)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
set_interactive(_interactive)
|
2026-08-08 00:34:06 -04:00
|
|
|
|
_refresh_catalog_scroll_indicators.call_deferred()
|
2026-07-30 23:16:28 -04:00
|
|
|
|
|
|
|
|
|
|
|
2026-08-29 20:19:47 -04:00
|
|
|
|
func _toggle_index_page() -> void:
|
|
|
|
|
|
_set_index_open(not _index_open)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _set_index_open(open: bool, restore_focus: bool = true) -> void:
|
|
|
|
|
|
_index_open = open
|
|
|
|
|
|
if _index_tab != null:
|
|
|
|
|
|
_index_tab.set_selected(open)
|
|
|
|
|
|
_index_tab.tooltip_text = (
|
|
|
|
|
|
"Return to catch record" if open else "Open catalog index"
|
|
|
|
|
|
)
|
|
|
|
|
|
if _index_body != null:
|
|
|
|
|
|
_index_body.visible = open
|
|
|
|
|
|
if _detail_body != null:
|
|
|
|
|
|
_detail_body.visible = not open
|
|
|
|
|
|
if not open:
|
|
|
|
|
|
_refresh_details(false)
|
|
|
|
|
|
_update_index_controls()
|
|
|
|
|
|
if restore_focus and _active and _interactive:
|
|
|
|
|
|
if open:
|
|
|
|
|
|
_controller_zone = ControllerZone.INDEX
|
|
|
|
|
|
set_interactive(true)
|
|
|
|
|
|
_index_search.grab_focus()
|
|
|
|
|
|
elif _controller_zone == ControllerZone.INDEX:
|
|
|
|
|
|
_controller_zone = ControllerZone.TABS
|
|
|
|
|
|
set_interactive(true)
|
|
|
|
|
|
_index_tab.grab_focus()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _on_index_search_changed(value: String) -> void:
|
|
|
|
|
|
if _index_update_in_progress:
|
|
|
|
|
|
return
|
|
|
|
|
|
_index_options["search"] = value
|
|
|
|
|
|
_refresh_catalog()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _cycle_index_option(key: StringName, direction: int = 1) -> void:
|
|
|
|
|
|
if not _index_options.has(key):
|
|
|
|
|
|
return
|
|
|
|
|
|
if key == &"descending":
|
|
|
|
|
|
_index_options[key] = not bool(_index_options[key])
|
|
|
|
|
|
elif key == &"group":
|
|
|
|
|
|
var groups: Array[String] = [""]
|
|
|
|
|
|
if _catalog != null:
|
|
|
|
|
|
groups.append_array(LogbookCatalog.available_groups(
|
|
|
|
|
|
_catalog.candidates,
|
|
|
|
|
|
_category,
|
|
|
|
|
|
_collection_log,
|
|
|
|
|
|
))
|
|
|
|
|
|
var current_index := groups.find(str(_index_options[key]))
|
|
|
|
|
|
if current_index < 0:
|
|
|
|
|
|
current_index = 0
|
|
|
|
|
|
_index_options[key] = groups[wrapi(
|
|
|
|
|
|
current_index + direction,
|
|
|
|
|
|
0,
|
|
|
|
|
|
groups.size(),
|
|
|
|
|
|
)]
|
|
|
|
|
|
else:
|
|
|
|
|
|
var option_count := _index_option_count(key)
|
|
|
|
|
|
if option_count <= 0:
|
|
|
|
|
|
return
|
|
|
|
|
|
_index_options[key] = wrapi(
|
|
|
|
|
|
int(_index_options[key]) + direction,
|
|
|
|
|
|
0,
|
|
|
|
|
|
option_count,
|
|
|
|
|
|
)
|
|
|
|
|
|
_refresh_catalog()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _on_index_option_gui_input(
|
|
|
|
|
|
event: InputEvent,
|
|
|
|
|
|
key: StringName,
|
|
|
|
|
|
) -> void:
|
|
|
|
|
|
var mouse_event := event as InputEventMouseButton
|
|
|
|
|
|
if mouse_event == null or not mouse_event.pressed:
|
|
|
|
|
|
return
|
|
|
|
|
|
if mouse_event.button_index in [MOUSE_BUTTON_RIGHT, MOUSE_BUTTON_WHEEL_UP]:
|
|
|
|
|
|
_cycle_index_option(key, -1)
|
|
|
|
|
|
get_viewport().set_input_as_handled()
|
|
|
|
|
|
elif mouse_event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
|
|
|
|
|
|
_cycle_index_option(key, 1)
|
|
|
|
|
|
get_viewport().set_input_as_handled()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _index_key_for_control(control: Control) -> StringName:
|
|
|
|
|
|
for key: StringName in _index_option_buttons:
|
|
|
|
|
|
if _index_option_buttons[key] == control:
|
|
|
|
|
|
return key
|
|
|
|
|
|
return StringName()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _reset_index() -> void:
|
|
|
|
|
|
_index_options = {
|
|
|
|
|
|
"search": "",
|
|
|
|
|
|
"show": LogbookCatalog.DiscoveryFilter.ALL,
|
|
|
|
|
|
"sort": LogbookCatalog.SortMode.CATALOG_NUMBER,
|
|
|
|
|
|
"descending": false,
|
|
|
|
|
|
"availability": LogbookCatalog.AvailabilityFilter.ANY,
|
|
|
|
|
|
"season": LogbookCatalog.SeasonFilter.ANY,
|
|
|
|
|
|
"time": LogbookCatalog.TimeFilter.ANY,
|
|
|
|
|
|
"rarity": LogbookCatalog.RarityFilter.ANY,
|
|
|
|
|
|
"method": LogbookCatalog.MethodFilter.ANY,
|
|
|
|
|
|
"holding": LogbookCatalog.HoldingFilter.ANY,
|
|
|
|
|
|
"group": "",
|
|
|
|
|
|
}
|
|
|
|
|
|
_index_update_in_progress = true
|
|
|
|
|
|
_index_search.text = ""
|
|
|
|
|
|
_index_update_in_progress = false
|
|
|
|
|
|
_refresh_catalog()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _update_index_controls(
|
|
|
|
|
|
visible_count: int = -1,
|
|
|
|
|
|
category_total: int = -1,
|
|
|
|
|
|
) -> void:
|
|
|
|
|
|
if visible_count >= 0:
|
|
|
|
|
|
_index_visible_count = visible_count
|
|
|
|
|
|
if category_total >= 0:
|
|
|
|
|
|
_index_category_total = category_total
|
|
|
|
|
|
if _index_summary != null:
|
|
|
|
|
|
_index_summary.text = "%d shown of %d" % [
|
|
|
|
|
|
_index_visible_count,
|
|
|
|
|
|
_index_category_total,
|
|
|
|
|
|
]
|
|
|
|
|
|
for key: StringName in _index_option_buttons:
|
|
|
|
|
|
var button := _index_option_buttons[key]
|
|
|
|
|
|
if button == null:
|
|
|
|
|
|
continue
|
|
|
|
|
|
button.text = _index_option_label(key)
|
|
|
|
|
|
button.accessibility_name = "%s: %s" % [
|
|
|
|
|
|
String(key).replace("_", " "),
|
|
|
|
|
|
button.text,
|
|
|
|
|
|
]
|
|
|
|
|
|
if _index_tab != null:
|
|
|
|
|
|
var marked := _index_has_active_criteria()
|
|
|
|
|
|
_index_tab.text = "index •" if marked else "index"
|
|
|
|
|
|
_index_tab.accessibility_name = (
|
|
|
|
|
|
"Catalog index, filters active" if marked else "Catalog index"
|
|
|
|
|
|
)
|
|
|
|
|
|
if _index_reset != null:
|
|
|
|
|
|
_index_reset.disabled = not _index_has_active_criteria()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _index_option_label(key: StringName) -> String:
|
|
|
|
|
|
match key:
|
|
|
|
|
|
&"show":
|
|
|
|
|
|
return INDEX_SHOW_LABELS[int(_index_options[key])]
|
|
|
|
|
|
&"sort":
|
|
|
|
|
|
return INDEX_SORT_LABELS[int(_index_options[key])]
|
|
|
|
|
|
&"availability":
|
|
|
|
|
|
return INDEX_AVAILABILITY_LABELS[int(_index_options[key])]
|
|
|
|
|
|
&"season":
|
|
|
|
|
|
return INDEX_SEASON_LABELS[int(_index_options[key])]
|
|
|
|
|
|
&"time":
|
|
|
|
|
|
return INDEX_TIME_LABELS[int(_index_options[key])]
|
|
|
|
|
|
&"rarity":
|
|
|
|
|
|
return INDEX_RARITY_LABELS[int(_index_options[key])]
|
|
|
|
|
|
&"method":
|
|
|
|
|
|
return INDEX_METHOD_LABELS[int(_index_options[key])]
|
|
|
|
|
|
&"holding":
|
|
|
|
|
|
return INDEX_HOLDING_LABELS[int(_index_options[key])]
|
|
|
|
|
|
&"group":
|
|
|
|
|
|
var group := str(_index_options[key])
|
|
|
|
|
|
return group if not group.is_empty() else "any group"
|
|
|
|
|
|
&"descending":
|
|
|
|
|
|
var descending := bool(_index_options[key])
|
|
|
|
|
|
match int(_index_options["sort"]):
|
|
|
|
|
|
LogbookCatalog.SortMode.NAME:
|
|
|
|
|
|
return "z–a" if descending else "a–z"
|
|
|
|
|
|
LogbookCatalog.SortMode.CATALOG_NUMBER:
|
|
|
|
|
|
return "high–low" if descending else "low–high"
|
|
|
|
|
|
LogbookCatalog.SortMode.RARITY:
|
|
|
|
|
|
return (
|
|
|
|
|
|
"rare–common" if descending else "common–rare"
|
|
|
|
|
|
)
|
|
|
|
|
|
LogbookCatalog.SortMode.VALUE, LogbookCatalog.SortMode.WEIGHT:
|
|
|
|
|
|
return "high–low" if descending else "low–high"
|
|
|
|
|
|
_:
|
|
|
|
|
|
return "most first" if descending else "least first"
|
|
|
|
|
|
return "any"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _index_option_count(key: StringName) -> int:
|
|
|
|
|
|
match key:
|
|
|
|
|
|
&"show":
|
|
|
|
|
|
return INDEX_SHOW_LABELS.size()
|
|
|
|
|
|
&"sort":
|
|
|
|
|
|
return INDEX_SORT_LABELS.size()
|
|
|
|
|
|
&"availability":
|
|
|
|
|
|
return INDEX_AVAILABILITY_LABELS.size()
|
|
|
|
|
|
&"season":
|
|
|
|
|
|
return INDEX_SEASON_LABELS.size()
|
|
|
|
|
|
&"time":
|
|
|
|
|
|
return INDEX_TIME_LABELS.size()
|
|
|
|
|
|
&"rarity":
|
|
|
|
|
|
return INDEX_RARITY_LABELS.size()
|
|
|
|
|
|
&"method":
|
|
|
|
|
|
return INDEX_METHOD_LABELS.size()
|
|
|
|
|
|
&"holding":
|
|
|
|
|
|
return INDEX_HOLDING_LABELS.size()
|
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _index_has_active_criteria() -> bool:
|
|
|
|
|
|
return (
|
|
|
|
|
|
not str(_index_options["search"]).is_empty()
|
|
|
|
|
|
or int(_index_options["show"])
|
|
|
|
|
|
!= LogbookCatalog.DiscoveryFilter.ALL
|
|
|
|
|
|
or int(_index_options["sort"])
|
|
|
|
|
|
!= LogbookCatalog.SortMode.CATALOG_NUMBER
|
|
|
|
|
|
or bool(_index_options["descending"])
|
|
|
|
|
|
or int(_index_options["availability"])
|
|
|
|
|
|
!= LogbookCatalog.AvailabilityFilter.ANY
|
|
|
|
|
|
or int(_index_options["season"])
|
|
|
|
|
|
!= LogbookCatalog.SeasonFilter.ANY
|
|
|
|
|
|
or int(_index_options["time"])
|
|
|
|
|
|
!= LogbookCatalog.TimeFilter.ANY
|
|
|
|
|
|
or int(_index_options["rarity"])
|
|
|
|
|
|
!= LogbookCatalog.RarityFilter.ANY
|
|
|
|
|
|
or int(_index_options["method"])
|
|
|
|
|
|
!= LogbookCatalog.MethodFilter.ANY
|
|
|
|
|
|
or int(_index_options["holding"])
|
|
|
|
|
|
!= LogbookCatalog.HoldingFilter.ANY
|
|
|
|
|
|
or not str(_index_options["group"]).is_empty()
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _current_season() -> int:
|
|
|
|
|
|
return (
|
|
|
|
|
|
_world_time.get_season()
|
|
|
|
|
|
if _world_time != null
|
|
|
|
|
|
else CalendarSeason.UNKNOWN
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _current_is_night() -> bool:
|
|
|
|
|
|
return _world_time != null and _world_time.is_night_period()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func notify_availability_context_changed() -> void:
|
|
|
|
|
|
_on_logbook_context_changed()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _on_logbook_context_changed(
|
|
|
|
|
|
_first: Variant = null,
|
|
|
|
|
|
_second: Variant = null,
|
|
|
|
|
|
) -> void:
|
|
|
|
|
|
if (
|
|
|
|
|
|
int(_index_options["availability"])
|
|
|
|
|
|
== LogbookCatalog.AvailabilityFilter.AVAILABLE_NOW
|
|
|
|
|
|
or int(_index_options["season"])
|
|
|
|
|
|
== LogbookCatalog.SeasonFilter.CURRENT
|
|
|
|
|
|
or int(_index_options["time"])
|
|
|
|
|
|
== LogbookCatalog.TimeFilter.CURRENT
|
|
|
|
|
|
):
|
|
|
|
|
|
_refresh_catalog()
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-31 00:03:47 -04:00
|
|
|
|
func _make_entry(fish: FishDataType, discovered: bool) -> Button:
|
2026-07-30 23:16:28 -04:00
|
|
|
|
var entry := Button.new()
|
2026-08-08 00:34:06 -04:00
|
|
|
|
entry.custom_minimum_size = CATALOG_ENTRY_SIZE
|
2026-08-02 09:57:05 -04:00
|
|
|
|
entry.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
|
|
|
|
|
entry.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
2026-07-30 23:16:28 -04:00
|
|
|
|
entry.toggle_mode = true
|
|
|
|
|
|
entry.add_theme_stylebox_override("normal", _entry_style(false))
|
|
|
|
|
|
entry.add_theme_stylebox_override("hover", _entry_style(true))
|
2026-08-02 09:57:05 -04:00
|
|
|
|
entry.add_theme_stylebox_override("hover_pressed", _entry_style(true))
|
2026-07-30 23:16:28 -04:00
|
|
|
|
entry.add_theme_stylebox_override("focus", _entry_style(true))
|
|
|
|
|
|
entry.add_theme_stylebox_override("pressed", _entry_style(true))
|
2026-08-02 09:57:05 -04:00
|
|
|
|
entry.text = ""
|
2026-07-31 00:03:47 -04:00
|
|
|
|
if not discovered:
|
2026-07-30 23:16:28 -04:00
|
|
|
|
entry.tooltip_text = ""
|
|
|
|
|
|
entry.accessibility_name = "Unknown catalog entry"
|
2026-08-02 09:57:05 -04:00
|
|
|
|
_add_entry_content(entry, fish.display_texture, "???", true)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
else:
|
|
|
|
|
|
entry.tooltip_text = fish.display_name
|
|
|
|
|
|
entry.accessibility_name = fish.display_name
|
2026-08-02 09:57:05 -04:00
|
|
|
|
_add_entry_content(
|
|
|
|
|
|
entry, fish.display_texture, fish.display_name, false
|
|
|
|
|
|
)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
return entry
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-08-02 09:57:05 -04:00
|
|
|
|
func _add_entry_content(
|
2026-07-31 00:03:47 -04:00
|
|
|
|
entry: Button,
|
|
|
|
|
|
portrait: Texture2D,
|
2026-08-02 09:57:05 -04:00
|
|
|
|
entry_name: String,
|
|
|
|
|
|
unknown: bool,
|
2026-07-31 00:03:47 -04:00
|
|
|
|
) -> void:
|
|
|
|
|
|
var content_margin := MarginContainer.new()
|
|
|
|
|
|
content_margin.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
|
|
|
|
content_margin.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
2026-08-02 09:57:05 -04:00
|
|
|
|
content_margin.add_theme_constant_override("margin_left", 5)
|
|
|
|
|
|
content_margin.add_theme_constant_override("margin_top", 8)
|
|
|
|
|
|
content_margin.add_theme_constant_override("margin_right", 5)
|
|
|
|
|
|
content_margin.add_theme_constant_override("margin_bottom", 2)
|
2026-07-31 00:03:47 -04:00
|
|
|
|
entry.add_child(content_margin)
|
|
|
|
|
|
|
|
|
|
|
|
var content := VBoxContainer.new()
|
|
|
|
|
|
content.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
2026-08-02 09:57:05 -04:00
|
|
|
|
content.alignment = BoxContainer.ALIGNMENT_CENTER
|
|
|
|
|
|
content.add_theme_constant_override("separation", 4)
|
2026-07-31 00:03:47 -04:00
|
|
|
|
content_margin.add_child(content)
|
|
|
|
|
|
|
2026-08-24 12:01:33 -04:00
|
|
|
|
var portrait_frame := CenterContainer.new()
|
|
|
|
|
|
portrait_frame.custom_minimum_size = CATALOG_PORTRAIT_SIZE
|
|
|
|
|
|
portrait_frame.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
|
|
|
|
content.add_child(portrait_frame)
|
|
|
|
|
|
|
2026-08-02 09:57:05 -04:00
|
|
|
|
var portrait_view := LogbookPortraitType.new()
|
2026-08-24 12:01:33 -04:00
|
|
|
|
portrait_view.configure_uniform_footprint(
|
2026-08-02 09:57:05 -04:00
|
|
|
|
portrait,
|
2026-08-08 00:34:06 -04:00
|
|
|
|
CATALOG_PORTRAIT_SIZE,
|
2026-08-02 09:57:05 -04:00
|
|
|
|
_silhouette_material if unknown else null,
|
|
|
|
|
|
)
|
2026-08-24 12:01:33 -04:00
|
|
|
|
portrait_frame.add_child(portrait_view)
|
2026-07-31 00:03:47 -04:00
|
|
|
|
|
2026-08-02 09:57:05 -04:00
|
|
|
|
var name_label := _label(_entry_label_text(entry_name), 16)
|
|
|
|
|
|
name_label.custom_minimum_size.y = 38.0
|
|
|
|
|
|
name_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
|
|
|
|
name_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
|
|
|
|
name_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
|
|
|
|
|
name_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
|
|
|
|
content.add_child(name_label)
|
2026-07-31 00:03:47 -04:00
|
|
|
|
|
|
|
|
|
|
|
2026-08-14 22:11:49 -04:00
|
|
|
|
func _select_category(category: LogbookCatalog.Category) -> void:
|
2026-07-30 23:16:28 -04:00
|
|
|
|
if category == _category:
|
2026-08-29 20:19:47 -04:00
|
|
|
|
if _index_open:
|
|
|
|
|
|
_set_index_open(false)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
return
|
|
|
|
|
|
_category = category
|
2026-08-29 20:19:47 -04:00
|
|
|
|
_set_index_open(false, false)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
_selected_id = StringName()
|
|
|
|
|
|
_selected_entry_key = StringName()
|
|
|
|
|
|
for index: int in _category_tabs.size():
|
2026-07-31 13:14:55 -04:00
|
|
|
|
(_category_tabs[index] as OrganizerTabType).set_selected(
|
2026-08-15 14:28:13 -04:00
|
|
|
|
_category_tab_categories[index] == _category
|
2026-07-31 13:14:55 -04:00
|
|
|
|
)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
_begin_category_transition()
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-31 10:20:18 -04:00
|
|
|
|
func _configure_category_focus() -> void:
|
|
|
|
|
|
for index: int in _category_tabs.size():
|
|
|
|
|
|
var tab: Button = _category_tabs[index]
|
|
|
|
|
|
tab.focus_neighbor_left = tab.get_path_to(
|
|
|
|
|
|
_category_tabs[maxi(index - 1, 0)]
|
|
|
|
|
|
)
|
|
|
|
|
|
tab.focus_neighbor_right = tab.get_path_to(
|
|
|
|
|
|
_category_tabs[mini(index + 1, _category_tabs.size() - 1)]
|
|
|
|
|
|
)
|
2026-08-29 20:19:47 -04:00
|
|
|
|
if not _category_tabs.is_empty() and _index_tab != null:
|
|
|
|
|
|
var final_tab: Button = _category_tabs.back()
|
|
|
|
|
|
final_tab.focus_neighbor_right = final_tab.get_path_to(_index_tab)
|
|
|
|
|
|
_index_tab.focus_neighbor_left = _index_tab.get_path_to(final_tab)
|
|
|
|
|
|
_index_tab.focus_neighbor_right = _index_tab.get_path_to(_index_tab)
|
2026-07-31 10:20:18 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _animate_tab_entry() -> void:
|
|
|
|
|
|
for index: int in _category_tabs.size():
|
|
|
|
|
|
(_category_tabs[index] as OrganizerTabType).animate_entrance(
|
|
|
|
|
|
float(index) * 0.025
|
|
|
|
|
|
)
|
2026-08-29 20:19:47 -04:00
|
|
|
|
if _index_tab != null:
|
|
|
|
|
|
_index_tab.animate_entrance(float(_category_tabs.size()) * 0.025)
|
2026-07-31 10:20:18 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _settle_tabs_for_close() -> void:
|
|
|
|
|
|
for tab: OrganizerTabType in _category_tabs:
|
|
|
|
|
|
tab.settle_for_close()
|
2026-08-29 20:19:47 -04:00
|
|
|
|
if _index_tab != null:
|
|
|
|
|
|
_index_tab.settle_for_close()
|
2026-07-31 10:20:18 -04:00
|
|
|
|
|
|
|
|
|
|
|
2026-07-30 23:16:28 -04:00
|
|
|
|
func _begin_category_transition() -> void:
|
|
|
|
|
|
_cancel_category_tween()
|
|
|
|
|
|
_category_generation += 1
|
|
|
|
|
|
var generation: int = _category_generation
|
|
|
|
|
|
var restore_interactive: bool = _interactive
|
|
|
|
|
|
set_interactive(false)
|
|
|
|
|
|
_category_tween = create_tween()
|
|
|
|
|
|
_category_tween.tween_method(
|
|
|
|
|
|
_set_category_content_alpha,
|
|
|
|
|
|
1.0,
|
|
|
|
|
|
0.0,
|
|
|
|
|
|
CATEGORY_FADE_DURATION * 0.5,
|
|
|
|
|
|
).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
|
|
|
|
|
|
_category_tween.tween_callback(
|
|
|
|
|
|
func() -> void:
|
|
|
|
|
|
if generation != _category_generation:
|
|
|
|
|
|
return
|
|
|
|
|
|
_refresh_catalog()
|
|
|
|
|
|
_set_category_content_alpha(0.0)
|
|
|
|
|
|
)
|
|
|
|
|
|
_category_tween.tween_method(
|
|
|
|
|
|
_set_category_content_alpha,
|
|
|
|
|
|
0.0,
|
|
|
|
|
|
1.0,
|
|
|
|
|
|
CATEGORY_FADE_DURATION * 0.5,
|
|
|
|
|
|
).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
|
|
|
|
|
|
_category_tween.finished.connect(
|
|
|
|
|
|
func() -> void:
|
|
|
|
|
|
if generation != _category_generation:
|
|
|
|
|
|
return
|
|
|
|
|
|
_category_tween = null
|
|
|
|
|
|
_set_category_content_alpha(1.0)
|
|
|
|
|
|
set_interactive(restore_interactive)
|
|
|
|
|
|
focus_initial(),
|
|
|
|
|
|
CONNECT_ONE_SHOT,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _cancel_category_tween() -> void:
|
|
|
|
|
|
_category_generation += 1
|
|
|
|
|
|
if _category_tween != null:
|
|
|
|
|
|
_category_tween.kill()
|
|
|
|
|
|
_category_tween = null
|
|
|
|
|
|
_set_category_content_alpha(1.0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _set_category_content_alpha(alpha: float) -> void:
|
|
|
|
|
|
if _catalog_scroll != null:
|
|
|
|
|
|
_catalog_scroll.modulate.a = alpha
|
|
|
|
|
|
if _empty_state != null:
|
|
|
|
|
|
_empty_state.modulate.a = alpha
|
|
|
|
|
|
if _detail_body != null:
|
|
|
|
|
|
_detail_body.modulate.a = alpha
|
2026-08-29 20:19:47 -04:00
|
|
|
|
if _index_body != null:
|
|
|
|
|
|
_index_body.modulate.a = alpha
|
2026-07-30 23:16:28 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _select_entry(entry_key: StringName, fish_id: StringName) -> void:
|
2026-08-29 20:19:47 -04:00
|
|
|
|
_set_index_open(false, false)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
if fish_id.is_empty():
|
|
|
|
|
|
_selected_id = StringName()
|
|
|
|
|
|
_selected_entry_key = entry_key
|
|
|
|
|
|
_refresh_selection_styles()
|
|
|
|
|
|
_show_unknown()
|
|
|
|
|
|
return
|
|
|
|
|
|
if (
|
|
|
|
|
|
_collection_log == null
|
|
|
|
|
|
or not _collection_log.has_discovered(fish_id)
|
|
|
|
|
|
):
|
|
|
|
|
|
return
|
|
|
|
|
|
_selected_id = fish_id
|
|
|
|
|
|
_selected_entry_key = entry_key
|
|
|
|
|
|
_refresh_selection_styles()
|
|
|
|
|
|
_refresh_details(true)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _refresh_details(animate: bool) -> void:
|
|
|
|
|
|
if _selected_id.is_empty():
|
|
|
|
|
|
_show_no_selection()
|
|
|
|
|
|
return
|
|
|
|
|
|
if (
|
|
|
|
|
|
_collection_log == null
|
|
|
|
|
|
or not _collection_log.has_discovered(_selected_id)
|
|
|
|
|
|
):
|
|
|
|
|
|
_show_unknown()
|
|
|
|
|
|
return
|
|
|
|
|
|
var fish: FishDataType = (
|
|
|
|
|
|
_catalog.get_fish_by_id(_selected_id)
|
|
|
|
|
|
if _catalog != null
|
|
|
|
|
|
else null
|
|
|
|
|
|
)
|
|
|
|
|
|
if fish == null:
|
|
|
|
|
|
_show_no_selection()
|
|
|
|
|
|
return
|
|
|
|
|
|
var rebuild: Callable = _build_known_details.bind(fish)
|
|
|
|
|
|
if animate:
|
|
|
|
|
|
_crossfade_details(rebuild)
|
|
|
|
|
|
else:
|
|
|
|
|
|
rebuild.call()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _build_known_details(fish: FishDataType) -> void:
|
|
|
|
|
|
_clear_details()
|
2026-08-14 16:31:28 -04:00
|
|
|
|
var catalog_number: int = LogbookCatalog.catalog_number(fish)
|
2026-08-08 00:34:06 -04:00
|
|
|
|
var summary_margin := MarginContainer.new()
|
|
|
|
|
|
summary_margin.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
|
|
|
|
_set_margins(summary_margin, 40, 0, 40, 0)
|
|
|
|
|
|
_detail_body.add_child(summary_margin)
|
2026-08-02 09:57:05 -04:00
|
|
|
|
var summary_columns := HBoxContainer.new()
|
|
|
|
|
|
summary_columns.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
|
|
|
|
summary_columns.add_theme_constant_override("separation", 24)
|
2026-08-08 00:34:06 -04:00
|
|
|
|
summary_margin.add_child(summary_columns)
|
2026-08-02 09:57:05 -04:00
|
|
|
|
|
|
|
|
|
|
var portrait_column := VBoxContainer.new()
|
|
|
|
|
|
portrait_column.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
|
|
|
|
portrait_column.size_flags_stretch_ratio = 1.0
|
|
|
|
|
|
portrait_column.add_theme_constant_override("separation", 4)
|
|
|
|
|
|
summary_columns.add_child(portrait_column)
|
|
|
|
|
|
var title := _label(fish.display_name, 26)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
2026-08-02 09:57:05 -04:00
|
|
|
|
portrait_column.add_child(title)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
if fish.display_texture != null:
|
2026-08-02 09:57:05 -04:00
|
|
|
|
_detail_portrait_button = Button.new()
|
2026-08-08 00:34:06 -04:00
|
|
|
|
_detail_portrait_button.custom_minimum_size = DETAIL_PORTRAIT_SIZE
|
2026-08-02 09:57:05 -04:00
|
|
|
|
_detail_portrait_button.size_flags_horizontal = (
|
|
|
|
|
|
Control.SIZE_SHRINK_CENTER
|
|
|
|
|
|
)
|
2026-08-08 00:34:06 -04:00
|
|
|
|
_detail_portrait_button.clip_contents = true
|
2026-08-02 09:57:05 -04:00
|
|
|
|
_detail_portrait_button.tooltip_text = "View larger artwork"
|
|
|
|
|
|
_detail_portrait_button.accessibility_name = (
|
|
|
|
|
|
"View larger artwork for %s" % fish.display_name
|
|
|
|
|
|
)
|
|
|
|
|
|
_detail_portrait_button.add_theme_stylebox_override(
|
|
|
|
|
|
"normal", _portrait_button_style(false)
|
|
|
|
|
|
)
|
|
|
|
|
|
_detail_portrait_button.add_theme_stylebox_override(
|
|
|
|
|
|
"hover", _portrait_button_style(true)
|
|
|
|
|
|
)
|
|
|
|
|
|
_detail_portrait_button.add_theme_stylebox_override(
|
|
|
|
|
|
"focus", _portrait_button_style(true)
|
|
|
|
|
|
)
|
|
|
|
|
|
_detail_portrait_button.add_theme_stylebox_override(
|
|
|
|
|
|
"pressed", _portrait_button_style(true)
|
|
|
|
|
|
)
|
|
|
|
|
|
_detail_portrait_button.pressed.connect(
|
|
|
|
|
|
_show_portrait_overlay.bind(fish.display_texture)
|
|
|
|
|
|
)
|
2026-08-15 19:20:42 -04:00
|
|
|
|
_detail_buttons.append(_detail_portrait_button)
|
2026-08-02 09:57:05 -04:00
|
|
|
|
portrait_column.add_child(_detail_portrait_button)
|
|
|
|
|
|
var artwork_center := CenterContainer.new()
|
|
|
|
|
|
artwork_center.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
|
|
|
|
artwork_center.set_anchors_and_offsets_preset(
|
|
|
|
|
|
Control.PRESET_FULL_RECT
|
|
|
|
|
|
)
|
|
|
|
|
|
_detail_portrait_button.add_child(artwork_center)
|
|
|
|
|
|
var artwork := LogbookPortraitType.new()
|
|
|
|
|
|
artwork.configure(
|
|
|
|
|
|
fish.display_texture,
|
2026-08-08 00:34:06 -04:00
|
|
|
|
DETAIL_PORTRAIT_SIZE,
|
2026-08-02 09:57:05 -04:00
|
|
|
|
)
|
|
|
|
|
|
artwork_center.add_child(artwork)
|
|
|
|
|
|
|
2026-08-15 19:20:42 -04:00
|
|
|
|
var facts_text: String = LogbookCatalog.facts_for(fish)
|
2026-08-19 00:07:24 -04:00
|
|
|
|
var facts_section_title: String = (
|
|
|
|
|
|
"shellfish facts"
|
|
|
|
|
|
if fish.logbook_section == FishDataType.LogbookSection.SHELLFISH
|
|
|
|
|
|
else "fish facts"
|
|
|
|
|
|
)
|
2026-08-15 19:20:42 -04:00
|
|
|
|
var facts_button := _make_detail_section_button(
|
2026-08-19 00:07:24 -04:00
|
|
|
|
facts_section_title,
|
2026-08-15 19:20:42 -04:00
|
|
|
|
facts_text,
|
|
|
|
|
|
)
|
|
|
|
|
|
facts_button.custom_minimum_size = Vector2(190.0, 132.0)
|
|
|
|
|
|
facts_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
|
|
|
|
facts_button.size_flags_stretch_ratio = 1.0
|
|
|
|
|
|
summary_columns.add_child(facts_button)
|
2026-08-02 09:57:05 -04:00
|
|
|
|
var facts_column := VBoxContainer.new()
|
2026-08-15 19:20:42 -04:00
|
|
|
|
facts_column.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
|
|
|
|
|
facts_column.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
2026-08-02 09:57:05 -04:00
|
|
|
|
facts_column.add_theme_constant_override("separation", 6)
|
2026-08-15 19:20:42 -04:00
|
|
|
|
facts_button.add_child(facts_column)
|
2026-08-14 22:11:49 -04:00
|
|
|
|
var facts_heading := _field_label(
|
2026-08-19 00:07:24 -04:00
|
|
|
|
facts_section_title,
|
2026-08-14 22:11:49 -04:00
|
|
|
|
16,
|
|
|
|
|
|
)
|
2026-08-02 09:57:05 -04:00
|
|
|
|
facts_column.add_child(facts_heading)
|
2026-08-15 19:20:42 -04:00
|
|
|
|
var facts := _label(facts_text, 16)
|
2026-08-02 09:57:05 -04:00
|
|
|
|
facts.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
|
|
|
|
facts.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
|
|
|
|
facts_column.add_child(facts)
|
2026-08-15 19:20:42 -04:00
|
|
|
|
var quality_button := _make_detail_section_button(
|
|
|
|
|
|
"quality collection",
|
|
|
|
|
|
_quality_overlay_text(fish.id),
|
|
|
|
|
|
)
|
2026-08-19 06:53:55 -04:00
|
|
|
|
quality_button.custom_minimum_size = DETAIL_QUALITY_SECTION_SIZE
|
|
|
|
|
|
quality_button.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
2026-08-15 19:20:42 -04:00
|
|
|
|
_detail_body.add_child(quality_button)
|
|
|
|
|
|
var quality_progress := _build_quality_progress(fish.id)
|
|
|
|
|
|
quality_progress.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
|
|
|
|
|
quality_progress.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
|
|
|
|
quality_button.add_child(quality_progress)
|
|
|
|
|
|
|
|
|
|
|
|
var stats_button := _make_detail_section_button(
|
|
|
|
|
|
"fish stats",
|
|
|
|
|
|
_stats_overlay_text(fish, catalog_number),
|
|
|
|
|
|
)
|
2026-08-19 06:53:55 -04:00
|
|
|
|
stats_button.custom_minimum_size = DETAIL_STATS_SECTION_SIZE
|
|
|
|
|
|
stats_button.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
2026-08-15 19:20:42 -04:00
|
|
|
|
_detail_body.add_child(stats_button)
|
2026-08-08 00:34:06 -04:00
|
|
|
|
var stats_anchor := VBoxContainer.new()
|
2026-08-15 19:20:42 -04:00
|
|
|
|
stats_anchor.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
|
|
|
|
|
stats_anchor.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
2026-08-08 00:34:06 -04:00
|
|
|
|
stats_anchor.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
|
|
|
|
stats_anchor.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
2026-08-19 00:07:24 -04:00
|
|
|
|
stats_anchor.alignment = BoxContainer.ALIGNMENT_BEGIN
|
2026-08-15 19:20:42 -04:00
|
|
|
|
stats_button.add_child(stats_anchor)
|
2026-08-02 09:57:05 -04:00
|
|
|
|
var stats_columns := HBoxContainer.new()
|
|
|
|
|
|
stats_columns.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
|
|
|
|
stats_columns.add_theme_constant_override("separation", 28)
|
2026-08-08 00:34:06 -04:00
|
|
|
|
stats_anchor.add_child(stats_columns)
|
2026-08-02 09:57:05 -04:00
|
|
|
|
var left_stats := _make_stats_column()
|
|
|
|
|
|
var right_stats := _make_stats_column()
|
|
|
|
|
|
stats_columns.add_child(left_stats)
|
|
|
|
|
|
stats_columns.add_child(right_stats)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
_add_detail_row(
|
2026-08-02 09:57:05 -04:00
|
|
|
|
left_stats,
|
2026-07-30 23:16:28 -04:00
|
|
|
|
"catalog number",
|
2026-08-02 09:57:05 -04:00
|
|
|
|
"#%03d" % catalog_number if catalog_number > 0 else "unknown",
|
|
|
|
|
|
)
|
|
|
|
|
|
_add_detail_row(
|
|
|
|
|
|
left_stats,
|
2026-08-14 22:11:49 -04:00
|
|
|
|
(
|
|
|
|
|
|
"habitat"
|
|
|
|
|
|
if fish.logbook_section == FishDataType.LogbookSection.SHELLFISH
|
|
|
|
|
|
else "body of water"
|
|
|
|
|
|
),
|
|
|
|
|
|
fish.get_habitat_label(),
|
2026-07-30 23:16:28 -04:00
|
|
|
|
)
|
|
|
|
|
|
_add_detail_row(
|
2026-08-02 09:57:05 -04:00
|
|
|
|
left_stats,
|
2026-07-30 23:16:28 -04:00
|
|
|
|
"weight range",
|
|
|
|
|
|
"%.2f–%.2f lb" % [
|
|
|
|
|
|
fish.get_minimum_weight(),
|
|
|
|
|
|
fish.get_maximum_weight(),
|
|
|
|
|
|
],
|
|
|
|
|
|
)
|
feat: expand progression and multiplayer systems
Add named save slots, progression import/export, and a unified play flow. Add live friend requests, presence, invitations, and relationship controls without durable discovery-server social storage. Advance the network protocol with isolated channels, movement reconciliation, late-join recovery, fishing replication, and animation synchronization. Preserve per-species catch totals, refine generated-world startup and water recovery, and complete the related input and interface improvements.
2026-08-23 20:48:38 -04:00
|
|
|
|
_add_detail_row(
|
|
|
|
|
|
left_stats,
|
|
|
|
|
|
"number caught",
|
|
|
|
|
|
str(_collection_log.get_catch_count(fish.id)),
|
|
|
|
|
|
)
|
2026-08-02 09:57:05 -04:00
|
|
|
|
_add_detail_row(right_stats, "rarity", fish.get_rarity_name().to_lower())
|
|
|
|
|
|
_add_detail_row(right_stats, "time of day", _availability_text(fish))
|
2026-08-18 21:20:02 -04:00
|
|
|
|
_add_detail_row(right_stats, "seasons", fish.get_season_text())
|
2026-08-13 14:53:39 -04:00
|
|
|
|
_add_currency_detail_row(
|
2026-08-02 09:57:05 -04:00
|
|
|
|
right_stats,
|
2026-07-30 23:16:28 -04:00
|
|
|
|
"value range",
|
2026-08-13 14:53:39 -04:00
|
|
|
|
FishQualityType.apply_sale_value(
|
|
|
|
|
|
fish.sell_value_min,
|
|
|
|
|
|
FishQualityType.Tier.BORING,
|
|
|
|
|
|
),
|
|
|
|
|
|
FishQualityType.apply_sale_value(
|
|
|
|
|
|
fish.sell_value_max,
|
|
|
|
|
|
FishQualityType.Tier.SHINY,
|
|
|
|
|
|
),
|
2026-07-30 23:16:28 -04:00
|
|
|
|
)
|
2026-08-15 19:20:42 -04:00
|
|
|
|
_configure_detail_focus(
|
|
|
|
|
|
_detail_portrait_button,
|
|
|
|
|
|
facts_button,
|
|
|
|
|
|
quality_button,
|
|
|
|
|
|
stats_button,
|
|
|
|
|
|
)
|
|
|
|
|
|
set_interactive(_interactive)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _configure_detail_focus(
|
|
|
|
|
|
portrait: Button,
|
|
|
|
|
|
facts: Button,
|
|
|
|
|
|
quality: Button,
|
|
|
|
|
|
stats: Button,
|
|
|
|
|
|
) -> void:
|
|
|
|
|
|
var top_left: Button = portrait if portrait != null else facts
|
|
|
|
|
|
if portrait != null:
|
|
|
|
|
|
portrait.focus_neighbor_left = portrait.get_path_to(portrait)
|
|
|
|
|
|
portrait.focus_neighbor_right = portrait.get_path_to(facts)
|
|
|
|
|
|
portrait.focus_neighbor_top = portrait.get_path_to(portrait)
|
|
|
|
|
|
portrait.focus_neighbor_bottom = portrait.get_path_to(quality)
|
|
|
|
|
|
portrait.focus_previous = portrait.get_path_to(stats)
|
|
|
|
|
|
portrait.focus_next = portrait.get_path_to(facts)
|
|
|
|
|
|
facts.focus_neighbor_left = facts.get_path_to(top_left)
|
|
|
|
|
|
facts.focus_neighbor_right = facts.get_path_to(facts)
|
|
|
|
|
|
facts.focus_neighbor_top = facts.get_path_to(facts)
|
|
|
|
|
|
facts.focus_neighbor_bottom = facts.get_path_to(quality)
|
|
|
|
|
|
facts.focus_previous = facts.get_path_to(top_left)
|
|
|
|
|
|
facts.focus_next = facts.get_path_to(quality)
|
|
|
|
|
|
quality.focus_neighbor_left = quality.get_path_to(quality)
|
|
|
|
|
|
quality.focus_neighbor_right = quality.get_path_to(quality)
|
|
|
|
|
|
quality.focus_neighbor_top = quality.get_path_to(facts)
|
|
|
|
|
|
quality.focus_neighbor_bottom = quality.get_path_to(stats)
|
|
|
|
|
|
quality.focus_previous = quality.get_path_to(facts)
|
|
|
|
|
|
quality.focus_next = quality.get_path_to(stats)
|
|
|
|
|
|
stats.focus_neighbor_left = stats.get_path_to(stats)
|
|
|
|
|
|
stats.focus_neighbor_right = stats.get_path_to(stats)
|
|
|
|
|
|
stats.focus_neighbor_top = stats.get_path_to(quality)
|
|
|
|
|
|
stats.focus_neighbor_bottom = stats.get_path_to(stats)
|
|
|
|
|
|
stats.focus_previous = stats.get_path_to(quality)
|
|
|
|
|
|
stats.focus_next = stats.get_path_to(top_left)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
|
|
|
|
|
|
|
2026-08-02 17:44:11 -04:00
|
|
|
|
func _build_quality_progress(fish_id: StringName) -> VBoxContainer:
|
|
|
|
|
|
var quality_section := VBoxContainer.new()
|
2026-08-08 00:34:06 -04:00
|
|
|
|
quality_section.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
|
|
|
|
quality_section.add_theme_constant_override("separation", 6)
|
2026-08-02 17:44:11 -04:00
|
|
|
|
var discovered_count: int = 0
|
|
|
|
|
|
for quality: int in FishQualityType.TIER_COUNT:
|
|
|
|
|
|
if _collection_log.has_discovered_quality(fish_id, quality):
|
|
|
|
|
|
discovered_count += 1
|
|
|
|
|
|
var heading := _field_label(
|
|
|
|
|
|
"quality collection • %d / %d" % [
|
|
|
|
|
|
discovered_count,
|
|
|
|
|
|
FishQualityType.TIER_COUNT,
|
|
|
|
|
|
],
|
2026-08-08 00:34:06 -04:00
|
|
|
|
17,
|
2026-08-02 17:44:11 -04:00
|
|
|
|
)
|
|
|
|
|
|
heading.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
|
|
|
|
heading.add_theme_color_override("font_color", MUTED_INK)
|
|
|
|
|
|
quality_section.add_child(heading)
|
2026-08-08 00:34:06 -04:00
|
|
|
|
var tiers := VBoxContainer.new()
|
|
|
|
|
|
tiers.add_theme_constant_override("separation", 6)
|
2026-08-02 17:44:11 -04:00
|
|
|
|
quality_section.add_child(tiers)
|
|
|
|
|
|
for quality: int in FishQualityType.TIER_COUNT:
|
2026-08-08 00:34:06 -04:00
|
|
|
|
var row: HBoxContainer
|
|
|
|
|
|
if quality % 2 == 0:
|
|
|
|
|
|
row = HBoxContainer.new()
|
|
|
|
|
|
row.alignment = BoxContainer.ALIGNMENT_CENTER
|
|
|
|
|
|
row.add_theme_constant_override("separation", 28)
|
|
|
|
|
|
tiers.add_child(row)
|
|
|
|
|
|
else:
|
|
|
|
|
|
row = tiers.get_child(tiers.get_child_count() - 1) as HBoxContainer
|
2026-08-02 17:44:11 -04:00
|
|
|
|
var discovered: bool = _collection_log.has_discovered_quality(
|
|
|
|
|
|
fish_id,
|
|
|
|
|
|
quality,
|
|
|
|
|
|
)
|
|
|
|
|
|
var tier := HBoxContainer.new()
|
|
|
|
|
|
tier.alignment = BoxContainer.ALIGNMENT_CENTER
|
2026-08-08 00:34:06 -04:00
|
|
|
|
tier.add_theme_constant_override("separation", 5)
|
2026-08-02 17:44:11 -04:00
|
|
|
|
var quality_color: Color = UIPalette.get_quality_color(quality)
|
2026-08-08 00:34:06 -04:00
|
|
|
|
var dot := _label("●" if discovered else "○", 17)
|
2026-08-02 17:44:11 -04:00
|
|
|
|
dot.add_theme_color_override("font_color", quality_color)
|
|
|
|
|
|
dot.modulate.a = 1.0 if discovered else 0.48
|
|
|
|
|
|
tier.add_child(dot)
|
2026-08-08 00:34:06 -04:00
|
|
|
|
var tier_label := _label(FishQualityType.display_name(quality), 16)
|
2026-08-02 17:44:11 -04:00
|
|
|
|
tier_label.add_theme_color_override(
|
|
|
|
|
|
"font_color",
|
|
|
|
|
|
INK if discovered else MUTED_INK,
|
|
|
|
|
|
)
|
|
|
|
|
|
tier_label.modulate.a = 1.0 if discovered else 0.58
|
|
|
|
|
|
tier.tooltip_text = (
|
|
|
|
|
|
"%s quality collected"
|
|
|
|
|
|
if discovered
|
|
|
|
|
|
else "%s quality not yet collected"
|
|
|
|
|
|
) % FishQualityType.display_name(quality)
|
|
|
|
|
|
tier.add_child(tier_label)
|
2026-08-08 00:34:06 -04:00
|
|
|
|
row.add_child(tier)
|
2026-08-02 17:44:11 -04:00
|
|
|
|
return quality_section
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-08-15 19:20:42 -04:00
|
|
|
|
func _make_detail_section_button(
|
|
|
|
|
|
section_title: String,
|
|
|
|
|
|
section_text: String,
|
|
|
|
|
|
) -> Button:
|
|
|
|
|
|
var button := Button.new()
|
|
|
|
|
|
button.text = ""
|
|
|
|
|
|
button.tooltip_text = "View %s larger" % section_title
|
|
|
|
|
|
button.accessibility_name = "View %s larger" % section_title
|
|
|
|
|
|
button.add_theme_stylebox_override("normal", _portrait_button_style(false))
|
|
|
|
|
|
button.add_theme_stylebox_override("hover", _portrait_button_style(true))
|
|
|
|
|
|
button.add_theme_stylebox_override("focus", _portrait_button_style(true))
|
|
|
|
|
|
button.add_theme_stylebox_override("pressed", _portrait_button_style(true))
|
|
|
|
|
|
button.pressed.connect(
|
|
|
|
|
|
_show_detail_text_overlay.bind(section_title, section_text, button)
|
|
|
|
|
|
)
|
|
|
|
|
|
_detail_buttons.append(button)
|
|
|
|
|
|
return button
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _quality_overlay_text(fish_id: StringName) -> String:
|
|
|
|
|
|
var lines: Array[String] = []
|
|
|
|
|
|
for quality: int in FishQualityType.TIER_COUNT:
|
|
|
|
|
|
var collected: bool = _collection_log.has_discovered_quality(
|
|
|
|
|
|
fish_id,
|
|
|
|
|
|
quality,
|
|
|
|
|
|
)
|
|
|
|
|
|
lines.append("%s %s" % [
|
|
|
|
|
|
"collected" if collected else "not collected",
|
|
|
|
|
|
FishQualityType.display_name(quality),
|
|
|
|
|
|
])
|
|
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _stats_overlay_text(fish: FishDataType, catalog_number: int) -> String:
|
|
|
|
|
|
var habitat_label: String = (
|
|
|
|
|
|
"habitat"
|
|
|
|
|
|
if fish.logbook_section == FishDataType.LogbookSection.SHELLFISH
|
|
|
|
|
|
else "body of water"
|
|
|
|
|
|
)
|
|
|
|
|
|
return "\n".join([
|
|
|
|
|
|
"catalog number: %s" % (
|
|
|
|
|
|
"#%03d" % catalog_number if catalog_number > 0 else "unknown"
|
|
|
|
|
|
),
|
|
|
|
|
|
"%s: %s" % [habitat_label, fish.get_habitat_label()],
|
|
|
|
|
|
"weight range: %.2f–%.2f lb" % [
|
|
|
|
|
|
fish.get_minimum_weight(),
|
|
|
|
|
|
fish.get_maximum_weight(),
|
|
|
|
|
|
],
|
feat: expand progression and multiplayer systems
Add named save slots, progression import/export, and a unified play flow. Add live friend requests, presence, invitations, and relationship controls without durable discovery-server social storage. Advance the network protocol with isolated channels, movement reconciliation, late-join recovery, fishing replication, and animation synchronization. Preserve per-species catch totals, refine generated-world startup and water recovery, and complete the related input and interface improvements.
2026-08-23 20:48:38 -04:00
|
|
|
|
"number caught: %d" % _collection_log.get_catch_count(fish.id),
|
2026-08-15 19:20:42 -04:00
|
|
|
|
"rarity: %s" % fish.get_rarity_name().to_lower(),
|
|
|
|
|
|
"time of day: %s" % _availability_text(fish),
|
2026-08-18 21:20:02 -04:00
|
|
|
|
"seasons: %s" % fish.get_season_text(),
|
2026-08-15 19:20:42 -04:00
|
|
|
|
"value range: %d–%d" % [
|
|
|
|
|
|
FishQualityType.apply_sale_value(
|
|
|
|
|
|
fish.sell_value_min,
|
|
|
|
|
|
FishQualityType.Tier.BORING,
|
|
|
|
|
|
),
|
|
|
|
|
|
FishQualityType.apply_sale_value(
|
|
|
|
|
|
fish.sell_value_max,
|
|
|
|
|
|
FishQualityType.Tier.SHINY,
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-30 23:16:28 -04:00
|
|
|
|
func _show_no_selection() -> void:
|
|
|
|
|
|
_clear_details()
|
|
|
|
|
|
var instruction := _label("Select an entry to read its catch record.", 21)
|
|
|
|
|
|
instruction.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
|
|
|
|
instruction.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
|
|
|
|
instruction.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
|
|
|
|
|
instruction.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
|
|
|
|
|
_detail_body.add_child(instruction)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _show_unknown() -> void:
|
|
|
|
|
|
_clear_details()
|
|
|
|
|
|
var unknown := _label("???", 34)
|
|
|
|
|
|
unknown.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
|
|
|
|
_detail_body.add_child(unknown)
|
|
|
|
|
|
var instruction := _label("Catch this creature to learn more.", 20)
|
|
|
|
|
|
instruction.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
|
|
|
|
instruction.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
|
|
|
|
_detail_body.add_child(instruction)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-08-02 09:57:05 -04:00
|
|
|
|
func _add_detail_row(
|
|
|
|
|
|
parent: VBoxContainer,
|
|
|
|
|
|
row_name: String,
|
|
|
|
|
|
value: String,
|
|
|
|
|
|
) -> void:
|
2026-07-30 23:16:28 -04:00
|
|
|
|
var row := VBoxContainer.new()
|
2026-08-02 09:57:05 -04:00
|
|
|
|
row.add_theme_constant_override("separation", 2)
|
|
|
|
|
|
var heading := _field_label(row_name, 14)
|
|
|
|
|
|
heading.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
2026-07-30 23:16:28 -04:00
|
|
|
|
heading.add_theme_color_override("font_color", MUTED_INK)
|
2026-08-02 09:57:05 -04:00
|
|
|
|
var content := _handwritten_value_label(value.to_lower(), 16)
|
|
|
|
|
|
content.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
2026-07-30 23:16:28 -04:00
|
|
|
|
content.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
|
|
|
|
row.add_child(heading)
|
|
|
|
|
|
row.add_child(content)
|
2026-08-02 09:57:05 -04:00
|
|
|
|
parent.add_child(row)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-08-13 14:53:39 -04:00
|
|
|
|
func _add_currency_detail_row(
|
|
|
|
|
|
parent: VBoxContainer,
|
|
|
|
|
|
row_name: String,
|
|
|
|
|
|
minimum_value: int,
|
|
|
|
|
|
maximum_value: int,
|
|
|
|
|
|
) -> void:
|
|
|
|
|
|
var row := VBoxContainer.new()
|
|
|
|
|
|
row.add_theme_constant_override("separation", 2)
|
|
|
|
|
|
var heading := _field_label(row_name, 14)
|
|
|
|
|
|
heading.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
|
|
|
|
heading.add_theme_color_override("font_color", MUTED_INK)
|
|
|
|
|
|
row.add_child(heading)
|
|
|
|
|
|
var value: CurrencyAmount = (
|
|
|
|
|
|
CurrencyPresentationType.instantiate_amount(minimum_value, 16.0)
|
|
|
|
|
|
)
|
|
|
|
|
|
value.set_amount_text("%d–%d" % [minimum_value, maximum_value])
|
|
|
|
|
|
value.get_amount_label().add_theme_font_override(
|
|
|
|
|
|
"font", InventoryNotepadType.HANDWRITTEN_FONT
|
|
|
|
|
|
)
|
|
|
|
|
|
value.get_amount_label().add_theme_font_size_override("font_size", 16)
|
|
|
|
|
|
value.get_amount_label().add_theme_color_override("font_color", INK)
|
|
|
|
|
|
row.add_child(value)
|
|
|
|
|
|
parent.add_child(row)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-08-02 09:57:05 -04:00
|
|
|
|
func _make_stats_column() -> VBoxContainer:
|
|
|
|
|
|
var column := VBoxContainer.new()
|
|
|
|
|
|
column.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
|
|
|
|
column.size_flags_stretch_ratio = 1.0
|
2026-08-19 00:07:24 -04:00
|
|
|
|
column.add_theme_constant_override(
|
|
|
|
|
|
"separation", DETAIL_STATS_ROW_SEPARATION
|
|
|
|
|
|
)
|
2026-08-02 09:57:05 -04:00
|
|
|
|
return column
|
2026-07-30 23:16:28 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _availability_text(fish: FishDataType) -> String:
|
|
|
|
|
|
if fish.availability == null:
|
2026-08-02 09:57:05 -04:00
|
|
|
|
return "unknown"
|
2026-07-30 23:16:28 -04:00
|
|
|
|
if fish.availability.allow_day and fish.availability.allow_night:
|
|
|
|
|
|
return "day and night"
|
|
|
|
|
|
if fish.availability.allow_day:
|
|
|
|
|
|
return "day"
|
|
|
|
|
|
if fish.availability.allow_night:
|
|
|
|
|
|
return "night"
|
2026-08-02 09:57:05 -04:00
|
|
|
|
return "unknown"
|
2026-07-30 23:16:28 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _crossfade_details(rebuild: Callable) -> void:
|
|
|
|
|
|
_cancel_detail_tween()
|
|
|
|
|
|
_detail_generation += 1
|
|
|
|
|
|
var generation: int = _detail_generation
|
|
|
|
|
|
_detail_tween = create_tween()
|
|
|
|
|
|
_detail_tween.tween_property(
|
|
|
|
|
|
_detail_body, "modulate:a", 0.0, DETAIL_FADE_DURATION * 0.5
|
|
|
|
|
|
)
|
|
|
|
|
|
_detail_tween.tween_callback(
|
|
|
|
|
|
func() -> void:
|
|
|
|
|
|
if generation == _detail_generation:
|
|
|
|
|
|
rebuild.call()
|
|
|
|
|
|
)
|
|
|
|
|
|
_detail_tween.tween_property(
|
|
|
|
|
|
_detail_body, "modulate:a", 1.0, DETAIL_FADE_DURATION * 0.5
|
|
|
|
|
|
)
|
|
|
|
|
|
_detail_tween.finished.connect(
|
|
|
|
|
|
func() -> void:
|
|
|
|
|
|
if generation == _detail_generation:
|
|
|
|
|
|
_detail_tween = null,
|
|
|
|
|
|
CONNECT_ONE_SHOT,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _cancel_detail_tween() -> void:
|
|
|
|
|
|
_detail_generation += 1
|
|
|
|
|
|
if _detail_tween != null:
|
|
|
|
|
|
_detail_tween.kill()
|
|
|
|
|
|
_detail_tween = null
|
|
|
|
|
|
if _detail_body != null:
|
|
|
|
|
|
_detail_body.modulate.a = 1.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _clear_entries() -> void:
|
|
|
|
|
|
for child: Node in _catalog_grid.get_children():
|
|
|
|
|
|
_catalog_grid.remove_child(child)
|
|
|
|
|
|
child.queue_free()
|
|
|
|
|
|
_entry_buttons.clear()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _clear_details() -> void:
|
2026-08-02 09:57:05 -04:00
|
|
|
|
_detail_portrait_button = null
|
2026-08-15 19:20:42 -04:00
|
|
|
|
_detail_buttons.clear()
|
2026-07-30 23:16:28 -04:00
|
|
|
|
for child: Node in _detail_body.get_children():
|
|
|
|
|
|
_detail_body.remove_child(child)
|
|
|
|
|
|
child.queue_free()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _refresh_selection_styles() -> void:
|
|
|
|
|
|
for fish_id: StringName in _entry_buttons:
|
|
|
|
|
|
_entry_buttons[fish_id].button_pressed = (
|
|
|
|
|
|
fish_id == _selected_entry_key
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _on_fish_discovered(fish_id: StringName) -> void:
|
2026-07-31 00:03:47 -04:00
|
|
|
|
if _catalog == null:
|
2026-07-30 23:16:28 -04:00
|
|
|
|
return
|
2026-07-31 00:03:47 -04:00
|
|
|
|
var fish: FishDataType = _catalog.get_fish_by_id(fish_id)
|
|
|
|
|
|
if fish == null:
|
|
|
|
|
|
return
|
|
|
|
|
|
if _selected_entry_key == _unknown_selection_key(fish):
|
|
|
|
|
|
_selected_id = fish_id
|
|
|
|
|
|
_selected_entry_key = fish_id
|
2026-07-30 23:16:28 -04:00
|
|
|
|
_refresh_catalog()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _on_collection_changed() -> void:
|
|
|
|
|
|
_refresh_catalog()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _on_inventory_changed() -> void:
|
2026-08-29 20:19:47 -04:00
|
|
|
|
# Ownership filters and favorite-backed views depend on the live cooler,
|
|
|
|
|
|
# while rebuilding the catalog also keeps the selected detail current.
|
|
|
|
|
|
_refresh_catalog()
|
2026-07-30 23:16:28 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _unknown_selection_key(fish: FishDataType) -> StringName:
|
2026-08-14 16:31:28 -04:00
|
|
|
|
var number: int = LogbookCatalog.catalog_number(fish)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
return StringName("unknown_%d" % number)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _label(text: String, font_size: int) -> Label:
|
2026-08-02 09:57:05 -04:00
|
|
|
|
var label := Label.new()
|
|
|
|
|
|
label.text = text
|
|
|
|
|
|
label.add_theme_font_override(
|
|
|
|
|
|
"font", InventoryNotepadType.HANDWRITTEN_FONT
|
|
|
|
|
|
)
|
|
|
|
|
|
label.add_theme_font_size_override("font_size", font_size)
|
|
|
|
|
|
label.add_theme_color_override("font_color", INK)
|
|
|
|
|
|
return label
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _field_label(text: String, font_size: int) -> Label:
|
2026-07-30 23:16:28 -04:00
|
|
|
|
var label := Label.new()
|
|
|
|
|
|
label.text = text
|
|
|
|
|
|
label.add_theme_font_override("font", UtilityPageStyle.TuffyFont)
|
|
|
|
|
|
label.add_theme_font_size_override("font_size", font_size)
|
|
|
|
|
|
label.add_theme_color_override("font_color", INK)
|
|
|
|
|
|
return label
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-08-02 09:57:05 -04:00
|
|
|
|
func _handwritten_value_label(text: String, font_size: int) -> Label:
|
|
|
|
|
|
var adjusted_size: int = font_size
|
|
|
|
|
|
if _contains_ascii_digit(text):
|
|
|
|
|
|
adjusted_size = roundi(
|
|
|
|
|
|
float(font_size) * HANDWRITTEN_NUMERIC_SCALE
|
|
|
|
|
|
)
|
|
|
|
|
|
return _label(text, adjusted_size)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _contains_ascii_digit(text: String) -> bool:
|
|
|
|
|
|
for codepoint: int in text.to_utf32_buffer():
|
|
|
|
|
|
if codepoint >= 48 and codepoint <= 57:
|
|
|
|
|
|
return true
|
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _entry_label_text(entry_name: String) -> String:
|
|
|
|
|
|
var split_index: int = entry_name.find(" ")
|
|
|
|
|
|
if split_index < 0:
|
|
|
|
|
|
return entry_name
|
|
|
|
|
|
return (
|
|
|
|
|
|
entry_name.substr(0, split_index)
|
|
|
|
|
|
+ "\n"
|
|
|
|
|
|
+ entry_name.substr(split_index + 1)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _build_portrait_overlay() -> void:
|
|
|
|
|
|
_portrait_overlay = Control.new()
|
|
|
|
|
|
_portrait_overlay.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
|
|
|
|
|
_portrait_overlay.z_index = 500
|
|
|
|
|
|
_portrait_overlay.visible = false
|
|
|
|
|
|
_portrait_overlay.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
|
|
|
|
add_child(_portrait_overlay)
|
|
|
|
|
|
|
|
|
|
|
|
_portrait_overlay_backdrop = Button.new()
|
|
|
|
|
|
_portrait_overlay_backdrop.set_anchors_and_offsets_preset(
|
|
|
|
|
|
Control.PRESET_FULL_RECT
|
|
|
|
|
|
)
|
|
|
|
|
|
_portrait_overlay_backdrop.text = ""
|
|
|
|
|
|
_portrait_overlay_backdrop.tooltip_text = "Return to logbook"
|
|
|
|
|
|
_portrait_overlay_backdrop.accessibility_name = "Return to logbook"
|
|
|
|
|
|
_portrait_overlay_backdrop.focus_neighbor_top = NodePath(".")
|
|
|
|
|
|
_portrait_overlay_backdrop.focus_neighbor_bottom = NodePath(".")
|
|
|
|
|
|
_portrait_overlay_backdrop.focus_neighbor_left = NodePath(".")
|
|
|
|
|
|
_portrait_overlay_backdrop.focus_neighbor_right = NodePath(".")
|
|
|
|
|
|
_portrait_overlay_backdrop.focus_next = NodePath(".")
|
|
|
|
|
|
_portrait_overlay_backdrop.focus_previous = NodePath(".")
|
2026-08-19 00:07:24 -04:00
|
|
|
|
var backdrop_style := _portrait_overlay_backdrop_style()
|
2026-08-02 09:57:05 -04:00
|
|
|
|
for state: StringName in [
|
|
|
|
|
|
&"normal", &"hover", &"focus", &"pressed", &"disabled",
|
|
|
|
|
|
]:
|
|
|
|
|
|
_portrait_overlay_backdrop.add_theme_stylebox_override(
|
2026-08-19 00:07:24 -04:00
|
|
|
|
state, backdrop_style
|
2026-08-02 09:57:05 -04:00
|
|
|
|
)
|
|
|
|
|
|
_portrait_overlay_backdrop.pressed.connect(_hide_portrait_overlay)
|
|
|
|
|
|
_portrait_overlay_backdrop.gui_input.connect(
|
|
|
|
|
|
_on_portrait_overlay_backdrop_input
|
|
|
|
|
|
)
|
|
|
|
|
|
_portrait_overlay.add_child(_portrait_overlay_backdrop)
|
|
|
|
|
|
|
2026-08-19 00:07:24 -04:00
|
|
|
|
var overlay_center := CenterContainer.new()
|
|
|
|
|
|
overlay_center.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
|
|
|
|
overlay_center.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
|
|
|
|
|
_portrait_overlay.add_child(overlay_center)
|
2026-08-02 09:57:05 -04:00
|
|
|
|
var card := PanelContainer.new()
|
|
|
|
|
|
card.mouse_filter = Control.MOUSE_FILTER_STOP
|
2026-08-19 00:07:24 -04:00
|
|
|
|
card.custom_minimum_size = DETAIL_OVERLAY_CARD_SIZE
|
|
|
|
|
|
card.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
|
|
|
|
|
card.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
2026-08-02 09:57:05 -04:00
|
|
|
|
card.add_theme_stylebox_override("panel", _portrait_view_style())
|
2026-08-19 00:07:24 -04:00
|
|
|
|
overlay_center.add_child(card)
|
2026-08-02 09:57:05 -04:00
|
|
|
|
var margin := MarginContainer.new()
|
2026-08-15 19:20:42 -04:00
|
|
|
|
_set_margins(
|
|
|
|
|
|
margin,
|
|
|
|
|
|
DETAIL_OVERLAY_CONTENT_MARGIN,
|
|
|
|
|
|
DETAIL_OVERLAY_CONTENT_MARGIN,
|
|
|
|
|
|
DETAIL_OVERLAY_CONTENT_MARGIN,
|
|
|
|
|
|
DETAIL_OVERLAY_CONTENT_MARGIN,
|
|
|
|
|
|
)
|
2026-08-02 09:57:05 -04:00
|
|
|
|
card.add_child(margin)
|
2026-08-15 19:20:42 -04:00
|
|
|
|
var overlay_stack := VBoxContainer.new()
|
2026-08-19 00:07:24 -04:00
|
|
|
|
overlay_stack.add_theme_constant_override("separation", 20)
|
2026-08-15 19:20:42 -04:00
|
|
|
|
margin.add_child(overlay_stack)
|
|
|
|
|
|
_portrait_overlay_title = _field_label(
|
|
|
|
|
|
"", DETAIL_OVERLAY_TITLE_FONT_SIZE
|
|
|
|
|
|
)
|
|
|
|
|
|
_portrait_overlay_title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
2026-08-19 00:07:24 -04:00
|
|
|
|
_portrait_overlay_title.add_theme_color_override(
|
|
|
|
|
|
"font_color", UIPalette.TEXT
|
|
|
|
|
|
)
|
2026-08-15 19:20:42 -04:00
|
|
|
|
overlay_stack.add_child(_portrait_overlay_title)
|
2026-08-19 00:07:24 -04:00
|
|
|
|
var content_panel := PanelContainer.new()
|
|
|
|
|
|
content_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
|
|
|
|
content_panel.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
|
|
|
|
|
content_panel.add_theme_stylebox_override(
|
|
|
|
|
|
"panel", _portrait_overlay_content_style()
|
|
|
|
|
|
)
|
|
|
|
|
|
overlay_stack.add_child(content_panel)
|
|
|
|
|
|
var content_margin := MarginContainer.new()
|
|
|
|
|
|
_set_margins(content_margin, 28, 24, 28, 24)
|
|
|
|
|
|
content_panel.add_child(content_margin)
|
|
|
|
|
|
var content_holder := Control.new()
|
|
|
|
|
|
content_holder.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
|
|
|
|
content_holder.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
|
|
|
|
|
content_margin.add_child(content_holder)
|
|
|
|
|
|
|
|
|
|
|
|
_portrait_overlay_artwork_view = CenterContainer.new()
|
|
|
|
|
|
_portrait_overlay_artwork_view.set_anchors_and_offsets_preset(
|
|
|
|
|
|
Control.PRESET_FULL_RECT
|
|
|
|
|
|
)
|
|
|
|
|
|
content_holder.add_child(_portrait_overlay_artwork_view)
|
2026-08-02 09:57:05 -04:00
|
|
|
|
_portrait_overlay_artwork = LogbookPortraitType.new()
|
2026-08-15 19:20:42 -04:00
|
|
|
|
_portrait_overlay_artwork.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
2026-08-19 00:07:24 -04:00
|
|
|
|
_portrait_overlay_artwork_view.add_child(_portrait_overlay_artwork)
|
|
|
|
|
|
|
|
|
|
|
|
_portrait_overlay_text = _field_label("", DETAIL_OVERLAY_TEXT_FONT_SIZE)
|
|
|
|
|
|
_portrait_overlay_text.set_anchors_and_offsets_preset(
|
|
|
|
|
|
Control.PRESET_FULL_RECT
|
|
|
|
|
|
)
|
2026-08-15 19:20:42 -04:00
|
|
|
|
_portrait_overlay_text.horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT
|
|
|
|
|
|
_portrait_overlay_text.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
|
|
|
|
|
_portrait_overlay_text.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
2026-08-19 00:07:24 -04:00
|
|
|
|
_portrait_overlay_text.add_theme_color_override(
|
|
|
|
|
|
"font_color", UIPalette.TEXT
|
|
|
|
|
|
)
|
|
|
|
|
|
content_holder.add_child(_portrait_overlay_text)
|
|
|
|
|
|
|
|
|
|
|
|
_portrait_overlay_quality_view = CenterContainer.new()
|
|
|
|
|
|
_portrait_overlay_quality_view.set_anchors_and_offsets_preset(
|
|
|
|
|
|
Control.PRESET_FULL_RECT
|
|
|
|
|
|
)
|
|
|
|
|
|
content_holder.add_child(_portrait_overlay_quality_view)
|
|
|
|
|
|
_portrait_overlay_quality = VBoxContainer.new()
|
|
|
|
|
|
_portrait_overlay_quality.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
|
|
|
|
|
_portrait_overlay_quality.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
|
|
|
|
|
_portrait_overlay_quality.add_theme_constant_override("separation", 22)
|
|
|
|
|
|
_portrait_overlay_quality_view.add_child(_portrait_overlay_quality)
|
|
|
|
|
|
|
|
|
|
|
|
_portrait_overlay_stats_view = CenterContainer.new()
|
|
|
|
|
|
_portrait_overlay_stats_view.set_anchors_and_offsets_preset(
|
|
|
|
|
|
Control.PRESET_FULL_RECT
|
|
|
|
|
|
)
|
|
|
|
|
|
content_holder.add_child(_portrait_overlay_stats_view)
|
|
|
|
|
|
_portrait_overlay_stats = GridContainer.new()
|
|
|
|
|
|
_portrait_overlay_stats.columns = 2
|
|
|
|
|
|
_portrait_overlay_stats.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
|
|
|
|
|
_portrait_overlay_stats.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
|
|
|
|
|
_portrait_overlay_stats.add_theme_constant_override("h_separation", 48)
|
|
|
|
|
|
_portrait_overlay_stats.add_theme_constant_override("v_separation", 12)
|
|
|
|
|
|
_portrait_overlay_stats_view.add_child(_portrait_overlay_stats)
|
|
|
|
|
|
_set_detail_overlay_view(&"text")
|
2026-08-02 09:57:05 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _show_portrait_overlay(portrait_texture: Texture2D) -> void:
|
|
|
|
|
|
if portrait_texture == null or _portrait_overlay == null:
|
|
|
|
|
|
return
|
|
|
|
|
|
_portrait_overlay_artwork.configure_fitted(
|
|
|
|
|
|
portrait_texture,
|
|
|
|
|
|
PORTRAIT_VIEW_MAX_SIZE,
|
|
|
|
|
|
)
|
2026-08-15 19:20:42 -04:00
|
|
|
|
_portrait_overlay_title.text = "artwork"
|
2026-08-19 00:07:24 -04:00
|
|
|
|
_set_detail_overlay_view(&"artwork")
|
2026-08-15 19:20:42 -04:00
|
|
|
|
_overlay_return_focus = _detail_portrait_button
|
|
|
|
|
|
_controller_zone = ControllerZone.OVERLAY
|
|
|
|
|
|
set_interactive(_interactive)
|
|
|
|
|
|
_portrait_overlay.visible = true
|
|
|
|
|
|
_portrait_overlay.mouse_filter = Control.MOUSE_FILTER_STOP
|
|
|
|
|
|
_portrait_overlay_backdrop.grab_focus()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _show_detail_text_overlay(
|
|
|
|
|
|
section_title: String,
|
|
|
|
|
|
section_text: String,
|
|
|
|
|
|
return_focus: Control,
|
|
|
|
|
|
) -> void:
|
|
|
|
|
|
if _portrait_overlay == null:
|
|
|
|
|
|
return
|
|
|
|
|
|
_portrait_overlay_title.text = section_title
|
2026-08-19 00:07:24 -04:00
|
|
|
|
match section_title:
|
|
|
|
|
|
"quality collection":
|
|
|
|
|
|
_populate_quality_overlay(_selected_id)
|
|
|
|
|
|
_set_detail_overlay_view(&"quality")
|
|
|
|
|
|
"fish stats":
|
|
|
|
|
|
_populate_stats_overlay(section_text)
|
|
|
|
|
|
_set_detail_overlay_view(&"stats")
|
|
|
|
|
|
_:
|
|
|
|
|
|
_portrait_overlay_text.text = section_text
|
|
|
|
|
|
_set_detail_overlay_view(&"text")
|
2026-08-15 19:20:42 -04:00
|
|
|
|
_overlay_return_focus = return_focus
|
|
|
|
|
|
_controller_zone = ControllerZone.OVERLAY
|
|
|
|
|
|
set_interactive(_interactive)
|
2026-08-02 09:57:05 -04:00
|
|
|
|
_portrait_overlay.visible = true
|
|
|
|
|
|
_portrait_overlay.mouse_filter = Control.MOUSE_FILTER_STOP
|
|
|
|
|
|
_portrait_overlay_backdrop.grab_focus()
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-08-19 00:07:24 -04:00
|
|
|
|
func _set_detail_overlay_view(view: StringName) -> void:
|
|
|
|
|
|
_portrait_overlay_artwork_view.visible = view == &"artwork"
|
|
|
|
|
|
_portrait_overlay_text.visible = view == &"text"
|
|
|
|
|
|
_portrait_overlay_quality_view.visible = view == &"quality"
|
|
|
|
|
|
_portrait_overlay_stats_view.visible = view == &"stats"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _populate_quality_overlay(fish_id: StringName) -> void:
|
|
|
|
|
|
_clear_overlay_container(_portrait_overlay_quality)
|
|
|
|
|
|
var discovered_count: int = 0
|
|
|
|
|
|
for quality: int in FishQualityType.TIER_COUNT:
|
|
|
|
|
|
if _collection_log.has_discovered_quality(fish_id, quality):
|
|
|
|
|
|
discovered_count += 1
|
|
|
|
|
|
var summary := _field_label(
|
|
|
|
|
|
"%d / %d collected" % [
|
|
|
|
|
|
discovered_count,
|
|
|
|
|
|
FishQualityType.TIER_COUNT,
|
|
|
|
|
|
],
|
|
|
|
|
|
DETAIL_OVERLAY_VALUE_FONT_SIZE,
|
|
|
|
|
|
)
|
|
|
|
|
|
summary.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
|
|
|
|
summary.add_theme_color_override("font_color", UIPalette.MUTED_TEXT)
|
|
|
|
|
|
_portrait_overlay_quality.add_child(summary)
|
|
|
|
|
|
var quality_grid := GridContainer.new()
|
|
|
|
|
|
quality_grid.columns = 2
|
|
|
|
|
|
quality_grid.add_theme_constant_override("h_separation", 44)
|
|
|
|
|
|
quality_grid.add_theme_constant_override("v_separation", 20)
|
|
|
|
|
|
_portrait_overlay_quality.add_child(quality_grid)
|
|
|
|
|
|
for quality: int in FishQualityType.TIER_COUNT:
|
|
|
|
|
|
var collected: bool = _collection_log.has_discovered_quality(
|
|
|
|
|
|
fish_id,
|
|
|
|
|
|
quality,
|
|
|
|
|
|
)
|
|
|
|
|
|
var tier := HBoxContainer.new()
|
|
|
|
|
|
tier.custom_minimum_size.x = 250.0
|
|
|
|
|
|
tier.add_theme_constant_override("separation", 12)
|
|
|
|
|
|
quality_grid.add_child(tier)
|
|
|
|
|
|
var quality_color: Color = UIPalette.get_quality_color(quality)
|
|
|
|
|
|
var dot := _field_label("●" if collected else "○", 34)
|
|
|
|
|
|
dot.add_theme_color_override("font_color", quality_color)
|
|
|
|
|
|
dot.modulate.a = 1.0 if collected else 0.55
|
|
|
|
|
|
tier.add_child(dot)
|
|
|
|
|
|
var tier_label := _field_label(
|
|
|
|
|
|
FishQualityType.display_name(quality),
|
|
|
|
|
|
DETAIL_OVERLAY_VALUE_FONT_SIZE,
|
|
|
|
|
|
)
|
|
|
|
|
|
tier_label.add_theme_color_override(
|
|
|
|
|
|
"font_color",
|
|
|
|
|
|
UIPalette.TEXT if collected else UIPalette.MUTED_TEXT,
|
|
|
|
|
|
)
|
|
|
|
|
|
tier.add_child(tier_label)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _populate_stats_overlay(section_text: String) -> void:
|
|
|
|
|
|
_clear_overlay_container(_portrait_overlay_stats)
|
|
|
|
|
|
for line: String in section_text.split("\n", false):
|
|
|
|
|
|
var separator_index: int = line.find(":")
|
|
|
|
|
|
if separator_index < 0:
|
|
|
|
|
|
continue
|
|
|
|
|
|
var field_name: String = line.left(separator_index).strip_edges()
|
|
|
|
|
|
var field_value: String = line.substr(separator_index + 1).strip_edges()
|
|
|
|
|
|
var field_label := _field_label(
|
|
|
|
|
|
field_name,
|
|
|
|
|
|
DETAIL_OVERLAY_FIELD_FONT_SIZE,
|
|
|
|
|
|
)
|
|
|
|
|
|
field_label.custom_minimum_size.x = 210.0
|
|
|
|
|
|
field_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
|
|
|
|
|
field_label.add_theme_color_override(
|
|
|
|
|
|
"font_color", UIPalette.MUTED_TEXT
|
|
|
|
|
|
)
|
|
|
|
|
|
_portrait_overlay_stats.add_child(field_label)
|
|
|
|
|
|
var value_label := _field_label(
|
|
|
|
|
|
field_value,
|
|
|
|
|
|
DETAIL_OVERLAY_VALUE_FONT_SIZE,
|
|
|
|
|
|
)
|
|
|
|
|
|
value_label.custom_minimum_size.x = 280.0
|
|
|
|
|
|
value_label.add_theme_color_override("font_color", UIPalette.TEXT)
|
|
|
|
|
|
_portrait_overlay_stats.add_child(value_label)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _clear_overlay_container(container: Container) -> void:
|
|
|
|
|
|
for child: Node in container.get_children():
|
|
|
|
|
|
container.remove_child(child)
|
|
|
|
|
|
child.queue_free()
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-08-02 09:57:05 -04:00
|
|
|
|
func _hide_portrait_overlay(restore_focus: bool = true) -> void:
|
|
|
|
|
|
if _portrait_overlay == null or not _portrait_overlay.visible:
|
|
|
|
|
|
return
|
|
|
|
|
|
_portrait_overlay.visible = false
|
|
|
|
|
|
_portrait_overlay.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
2026-08-15 19:20:42 -04:00
|
|
|
|
_controller_zone = ControllerZone.DETAILS
|
|
|
|
|
|
set_interactive(_interactive)
|
2026-08-02 09:57:05 -04:00
|
|
|
|
if (
|
|
|
|
|
|
restore_focus
|
2026-08-15 19:20:42 -04:00
|
|
|
|
and _overlay_return_focus != null
|
|
|
|
|
|
and is_instance_valid(_overlay_return_focus)
|
2026-08-02 09:57:05 -04:00
|
|
|
|
and _active
|
|
|
|
|
|
and _interactive
|
|
|
|
|
|
):
|
2026-08-15 19:20:42 -04:00
|
|
|
|
_overlay_return_focus.grab_focus()
|
2026-08-02 09:57:05 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _on_portrait_overlay_backdrop_input(event: InputEvent) -> void:
|
|
|
|
|
|
if event.is_action_pressed("ui_cancel"):
|
|
|
|
|
|
_hide_portrait_overlay()
|
|
|
|
|
|
get_viewport().set_input_as_handled()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _portrait_button_style(highlighted: bool) -> StyleBoxFlat:
|
|
|
|
|
|
var style := StyleBoxFlat.new()
|
|
|
|
|
|
style.bg_color = (
|
|
|
|
|
|
Color(1.0, 0.96, 0.86, 0.48)
|
|
|
|
|
|
if highlighted
|
|
|
|
|
|
else Color.TRANSPARENT
|
|
|
|
|
|
)
|
|
|
|
|
|
style.set_border_width_all(0)
|
|
|
|
|
|
style.set_corner_radius_all(12)
|
|
|
|
|
|
return style
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _portrait_view_style() -> StyleBoxFlat:
|
|
|
|
|
|
var style := StyleBoxFlat.new()
|
2026-08-19 00:07:24 -04:00
|
|
|
|
style.bg_color = UIPalette.PANEL
|
|
|
|
|
|
style.border_color = UIPalette.PRIMARY
|
|
|
|
|
|
style.set_border_width_all(4)
|
|
|
|
|
|
style.set_corner_radius_all(24)
|
|
|
|
|
|
style.shadow_color = Color(0.0, 0.0, 0.0, 0.42)
|
|
|
|
|
|
style.shadow_size = 12
|
|
|
|
|
|
return style
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _portrait_overlay_backdrop_style() -> StyleBoxFlat:
|
|
|
|
|
|
var style := StyleBoxFlat.new()
|
|
|
|
|
|
style.bg_color = Color(0.01, 0.045, 0.065, 0.82)
|
|
|
|
|
|
style.set_border_width_all(0)
|
|
|
|
|
|
return style
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _portrait_overlay_content_style() -> StyleBoxFlat:
|
|
|
|
|
|
var style := StyleBoxFlat.new()
|
|
|
|
|
|
style.bg_color = UIPalette.ELEVATED_PANEL
|
2026-08-02 09:57:05 -04:00
|
|
|
|
style.set_border_width_all(0)
|
2026-08-19 00:07:24 -04:00
|
|
|
|
style.set_corner_radius_all(16)
|
2026-08-02 09:57:05 -04:00
|
|
|
|
return style
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _entry_style(highlighted: bool) -> StyleBoxFlat:
|
2026-07-30 23:16:28 -04:00
|
|
|
|
var style := StyleBoxFlat.new()
|
2026-08-02 09:57:05 -04:00
|
|
|
|
style.bg_color = Color("fff5dc") if highlighted else Color.TRANSPARENT
|
|
|
|
|
|
style.set_border_width_all(0)
|
|
|
|
|
|
style.set_corner_radius_all(51)
|
|
|
|
|
|
style.content_margin_left = 5
|
|
|
|
|
|
style.content_margin_top = 5
|
|
|
|
|
|
style.content_margin_right = 5
|
|
|
|
|
|
style.content_margin_bottom = 5
|
2026-07-30 23:16:28 -04:00
|
|
|
|
return style
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _set_margins(
|
|
|
|
|
|
container: MarginContainer,
|
|
|
|
|
|
left: int,
|
|
|
|
|
|
top: int,
|
|
|
|
|
|
right: int,
|
|
|
|
|
|
bottom: int,
|
|
|
|
|
|
) -> void:
|
|
|
|
|
|
container.add_theme_constant_override("margin_left", left)
|
|
|
|
|
|
container.add_theme_constant_override("margin_top", top)
|
|
|
|
|
|
container.add_theme_constant_override("margin_right", right)
|
|
|
|
|
|
container.add_theme_constant_override("margin_bottom", bottom)
|