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")
|
|
|
|
|
|
const FishInventoryType = preload("res://inventory/fish_inventory.gd")
|
|
|
|
|
|
const FishPoolType = preload("res://fish/fish_pool.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-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-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
|
|
|
|
|
|
const PORTRAIT_VIEW_MAX_SIZE := Vector2(860.0, 480.0)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
const INK := Color("251b10")
|
|
|
|
|
|
const MUTED_INK := Color("6d5b45")
|
|
|
|
|
|
const PAPER := Color("f2e6c9")
|
|
|
|
|
|
const PAPER_DARK := Color("e8d7b4")
|
2026-07-31 13:14:55 -04:00
|
|
|
|
const LOGBOOK_TAB_LEFT_INSET: float = 34.0
|
2026-07-30 23:16:28 -04:00
|
|
|
|
|
|
|
|
|
|
var _collection_log: CollectionLogType
|
|
|
|
|
|
var _inventory: FishInventoryType
|
|
|
|
|
|
var _catalog: FishPoolType
|
2026-08-02 09:57:05 -04:00
|
|
|
|
var _category: WaterType.Type = WaterType.Type.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-07-30 23:16:28 -04:00
|
|
|
|
|
|
|
|
|
|
var _category_tabs: Array[Button] = []
|
|
|
|
|
|
var _catalog_scroll: ScrollContainer
|
|
|
|
|
|
var _catalog_grid: GridContainer
|
|
|
|
|
|
var _empty_state: Label
|
|
|
|
|
|
var _detail_body: VBoxContainer
|
2026-08-02 09:57:05 -04:00
|
|
|
|
var _detail_portrait_button: Button
|
|
|
|
|
|
var _portrait_overlay: Control
|
|
|
|
|
|
var _portrait_overlay_backdrop: Button
|
|
|
|
|
|
var _portrait_overlay_artwork: LogbookPortraitType
|
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,
|
|
|
|
|
|
) -> void:
|
|
|
|
|
|
_collection_log = collection_log
|
|
|
|
|
|
_inventory = inventory
|
|
|
|
|
|
_catalog = catalog
|
|
|
|
|
|
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)
|
|
|
|
|
|
_refresh_catalog()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 = (
|
|
|
|
|
|
Control.FOCUS_ALL if _interactive else Control.FOCUS_NONE
|
|
|
|
|
|
)
|
|
|
|
|
|
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
|
|
|
|
|
|
)
|
|
|
|
|
|
for entry: Button in _entry_buttons.values():
|
|
|
|
|
|
entry.focus_mode = (
|
|
|
|
|
|
Control.FOCUS_ALL if _interactive else Control.FOCUS_NONE
|
|
|
|
|
|
)
|
|
|
|
|
|
entry.mouse_filter = (
|
|
|
|
|
|
Control.MOUSE_FILTER_STOP
|
|
|
|
|
|
if _interactive
|
|
|
|
|
|
else Control.MOUSE_FILTER_IGNORE
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func focus_initial() -> void:
|
|
|
|
|
|
if not _active or not _interactive:
|
|
|
|
|
|
return
|
2026-08-02 09:57:05 -04:00
|
|
|
|
if _portrait_overlay != null and _portrait_overlay.visible:
|
|
|
|
|
|
_portrait_overlay_backdrop.grab_focus()
|
|
|
|
|
|
return
|
2026-07-30 23:16:28 -04:00
|
|
|
|
var selected := _entry_buttons.get(_selected_id) as Button
|
|
|
|
|
|
if selected != null:
|
|
|
|
|
|
selected.grab_focus()
|
|
|
|
|
|
elif not _entry_buttons.is_empty():
|
|
|
|
|
|
var first := _entry_buttons.values().front() as Button
|
|
|
|
|
|
first.grab_focus()
|
|
|
|
|
|
else:
|
|
|
|
|
|
_category_tabs[int(_category)].grab_focus()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _build_interface() -> void:
|
|
|
|
|
|
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
|
|
|
|
|
var outer := MarginContainer.new()
|
|
|
|
|
|
outer.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
|
|
|
|
|
outer.add_theme_constant_override("margin_left", 52)
|
|
|
|
|
|
outer.add_theme_constant_override("margin_top", 116)
|
|
|
|
|
|
outer.add_theme_constant_override("margin_right", 52)
|
|
|
|
|
|
outer.add_theme_constant_override("margin_bottom", 18)
|
|
|
|
|
|
add_child(outer)
|
|
|
|
|
|
|
2026-07-31 10:20:18 -04:00
|
|
|
|
var stack := Control.new()
|
|
|
|
|
|
stack.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
|
|
|
|
outer.add_child(stack)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
|
|
|
|
|
|
var tabs := HBoxContainer.new()
|
2026-07-31 13:14:55 -04:00
|
|
|
|
# The 26 px page corner plus 8 px clearance keeps the flange behind
|
|
|
|
|
|
# the straight portion of the spread.
|
|
|
|
|
|
tabs.position = Vector2(LOGBOOK_TAB_LEFT_INSET, 20.0)
|
|
|
|
|
|
tabs.size = Vector2(384.0, 38.0)
|
2026-07-31 10:20:18 -04:00
|
|
|
|
tabs.z_index = 10
|
|
|
|
|
|
tabs.mouse_filter = Control.MOUSE_FILTER_PASS
|
2026-07-31 13:14:55 -04:00
|
|
|
|
tabs.add_theme_constant_override("separation", 6)
|
2026-07-31 10:20:18 -04:00
|
|
|
|
stack.add_child(tabs)
|
2026-07-31 16:05:44 -04:00
|
|
|
|
for category: WaterType.Type in [
|
|
|
|
|
|
WaterType.Type.FRESH_WATER,
|
|
|
|
|
|
WaterType.Type.SALT_WATER,
|
|
|
|
|
|
WaterType.Type.OTHER,
|
2026-07-30 23:16:28 -04:00
|
|
|
|
]:
|
2026-07-31 10:20:18 -04:00
|
|
|
|
var tab := OrganizerTabType.new()
|
2026-07-31 13:14:55 -04:00
|
|
|
|
tab.custom_minimum_size = Vector2(124, 38)
|
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-07-31 10:20:18 -04:00
|
|
|
|
_configure_category_focus()
|
2026-07-30 23:16:28 -04:00
|
|
|
|
|
|
|
|
|
|
var book := HBoxContainer.new()
|
2026-07-31 10:20:18 -04:00
|
|
|
|
book.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
|
|
|
|
|
book.offset_top = 50.0
|
|
|
|
|
|
book.z_index = 20
|
2026-07-30 23:16:28 -04:00
|
|
|
|
book.add_theme_constant_override("separation", 12)
|
2026-07-31 10:20:18 -04:00
|
|
|
|
stack.add_child(book)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
|
|
|
|
|
|
var left_page := PanelContainer.new()
|
|
|
|
|
|
left_page.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
|
|
|
|
left_page.size_flags_stretch_ratio = 1.12
|
|
|
|
|
|
left_page.add_theme_stylebox_override(
|
|
|
|
|
|
"panel", _paper_style(PAPER, true)
|
|
|
|
|
|
)
|
|
|
|
|
|
book.add_child(left_page)
|
|
|
|
|
|
var left_margin := MarginContainer.new()
|
|
|
|
|
|
_set_margins(left_margin, 18, 16, 18, 16)
|
|
|
|
|
|
left_page.add_child(left_margin)
|
|
|
|
|
|
var left_layout := VBoxContainer.new()
|
|
|
|
|
|
left_layout.add_theme_constant_override("separation", 8)
|
|
|
|
|
|
left_margin.add_child(left_layout)
|
|
|
|
|
|
var heading := _label("catch catalog", 25)
|
|
|
|
|
|
heading.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
|
|
|
|
left_layout.add_child(heading)
|
|
|
|
|
|
_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)
|
|
|
|
|
|
_catalog_scroll = ScrollContainer.new()
|
|
|
|
|
|
_catalog_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
|
|
|
|
|
_catalog_scroll.horizontal_scroll_mode = (
|
|
|
|
|
|
ScrollContainer.SCROLL_MODE_DISABLED
|
|
|
|
|
|
)
|
|
|
|
|
|
left_layout.add_child(_catalog_scroll)
|
|
|
|
|
|
_catalog_grid = GridContainer.new()
|
2026-08-02 09:57:05 -04:00
|
|
|
|
_catalog_grid.columns = 5
|
2026-07-30 23:16:28 -04:00
|
|
|
|
_catalog_grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
|
|
|
|
_catalog_grid.add_theme_constant_override("h_separation", 8)
|
|
|
|
|
|
_catalog_grid.add_theme_constant_override("v_separation", 8)
|
|
|
|
|
|
_catalog_scroll.add_child(_catalog_grid)
|
|
|
|
|
|
|
|
|
|
|
|
var gutter := ColorRect.new()
|
|
|
|
|
|
gutter.custom_minimum_size.x = 10
|
|
|
|
|
|
gutter.color = Color("795f3f")
|
|
|
|
|
|
gutter.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
|
|
|
|
book.add_child(gutter)
|
|
|
|
|
|
|
|
|
|
|
|
var right_page := PanelContainer.new()
|
|
|
|
|
|
right_page.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
|
|
|
|
right_page.add_theme_stylebox_override(
|
|
|
|
|
|
"panel", _paper_style(PAPER_DARK, false)
|
|
|
|
|
|
)
|
|
|
|
|
|
book.add_child(right_page)
|
|
|
|
|
|
var right_margin := MarginContainer.new()
|
|
|
|
|
|
_set_margins(right_margin, 22, 16, 22, 16)
|
|
|
|
|
|
right_page.add_child(right_margin)
|
|
|
|
|
|
_detail_body = VBoxContainer.new()
|
|
|
|
|
|
_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
|
|
|
|
|
|
_detail_body.add_theme_constant_override("separation", 16)
|
|
|
|
|
|
right_margin.add_child(_detail_body)
|
2026-07-30 23:16:28 -04:00
|
|
|
|
_show_no_selection()
|
2026-08-02 09:57:05 -04:00
|
|
|
|
_build_portrait_overlay()
|
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] = []
|
|
|
|
|
|
if _catalog != null:
|
|
|
|
|
|
species = LogbookCatalog.ordered_species(_catalog.candidates)
|
|
|
|
|
|
for fish: FishDataType in species:
|
|
|
|
|
|
if LogbookCatalog.category_for(fish) != _category:
|
|
|
|
|
|
continue
|
|
|
|
|
|
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)
|
|
|
|
|
|
_empty_state.text = LogbookCatalog.empty_state(_category)
|
|
|
|
|
|
_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)
|
|
|
|
|
|
set_interactive(_interactive)
|
|
|
|
|
|
|
|
|
|
|
|
|
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-02 09:57:05 -04:00
|
|
|
|
entry.custom_minimum_size = Vector2(102, 102)
|
|
|
|
|
|
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-02 09:57:05 -04:00
|
|
|
|
var portrait_view := LogbookPortraitType.new()
|
|
|
|
|
|
portrait_view.configure(
|
|
|
|
|
|
portrait,
|
|
|
|
|
|
LogbookPortraitType.ENTRY_FRAME_SIZE,
|
|
|
|
|
|
_silhouette_material if unknown else null,
|
|
|
|
|
|
)
|
2026-07-31 00:03:47 -04:00
|
|
|
|
content.add_child(portrait_view)
|
|
|
|
|
|
|
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-07-31 16:05:44 -04:00
|
|
|
|
func _select_category(category: WaterType.Type) -> void:
|
2026-07-30 23:16:28 -04:00
|
|
|
|
if category == _category:
|
|
|
|
|
|
return
|
|
|
|
|
|
_category = category
|
|
|
|
|
|
_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(
|
|
|
|
|
|
index == int(_category)
|
|
|
|
|
|
)
|
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)]
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _animate_tab_entry() -> void:
|
|
|
|
|
|
for index: int in _category_tabs.size():
|
|
|
|
|
|
(_category_tabs[index] as OrganizerTabType).animate_entrance(
|
|
|
|
|
|
float(index) * 0.025
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _settle_tabs_for_close() -> void:
|
|
|
|
|
|
for tab: OrganizerTabType in _category_tabs:
|
|
|
|
|
|
tab.settle_for_close()
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _select_entry(entry_key: StringName, fish_id: StringName) -> void:
|
|
|
|
|
|
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()
|
|
|
|
|
|
var catalog_number: int = LogbookCatalog.catalog_number(fish.id)
|
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)
|
|
|
|
|
|
_detail_body.add_child(summary_columns)
|
|
|
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
|
_detail_portrait_button.custom_minimum_size = (
|
|
|
|
|
|
LogbookPortraitType.DETAIL_FRAME_SIZE
|
|
|
|
|
|
)
|
|
|
|
|
|
_detail_portrait_button.size_flags_horizontal = (
|
|
|
|
|
|
Control.SIZE_SHRINK_CENTER
|
|
|
|
|
|
)
|
|
|
|
|
|
_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)
|
|
|
|
|
|
)
|
|
|
|
|
|
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,
|
|
|
|
|
|
LogbookPortraitType.DETAIL_FRAME_SIZE,
|
|
|
|
|
|
)
|
|
|
|
|
|
artwork_center.add_child(artwork)
|
|
|
|
|
|
|
|
|
|
|
|
var facts_column := VBoxContainer.new()
|
|
|
|
|
|
facts_column.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
|
|
|
|
facts_column.size_flags_stretch_ratio = 1.0
|
|
|
|
|
|
facts_column.add_theme_constant_override("separation", 6)
|
|
|
|
|
|
summary_columns.add_child(facts_column)
|
|
|
|
|
|
var facts_heading := _field_label("fish facts", 16)
|
|
|
|
|
|
facts_column.add_child(facts_heading)
|
|
|
|
|
|
var facts := _label(LogbookCatalog.facts_for(fish.id), 16)
|
|
|
|
|
|
facts.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
|
|
|
|
facts.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
|
|
|
|
facts_column.add_child(facts)
|
|
|
|
|
|
|
|
|
|
|
|
var stats_columns := HBoxContainer.new()
|
|
|
|
|
|
stats_columns.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
|
|
|
|
stats_columns.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
|
|
|
|
|
stats_columns.add_theme_constant_override("separation", 28)
|
|
|
|
|
|
_detail_body.add_child(stats_columns)
|
|
|
|
|
|
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,
|
|
|
|
|
|
"body of water",
|
|
|
|
|
|
WaterType.label(fish.get_primary_water_type()).to_lower(),
|
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(),
|
|
|
|
|
|
],
|
|
|
|
|
|
)
|
2026-08-02 09:57:05 -04:00
|
|
|
|
_add_detail_row(left_stats, "number caught", "unknown")
|
|
|
|
|
|
_add_detail_row(right_stats, "rarity", fish.get_rarity_name().to_lower())
|
|
|
|
|
|
_add_detail_row(right_stats, "time of day", _availability_text(fish))
|
2026-07-30 23:16:28 -04:00
|
|
|
|
_add_detail_row(
|
2026-08-02 09:57:05 -04:00
|
|
|
|
right_stats,
|
2026-07-30 23:16:28 -04:00
|
|
|
|
"value range",
|
|
|
|
|
|
"%d–%d fish coin" % [
|
|
|
|
|
|
fish.sell_value_min,
|
|
|
|
|
|
fish.sell_value_max,
|
|
|
|
|
|
],
|
|
|
|
|
|
)
|
|
|
|
|
|
_add_detail_row(
|
2026-08-02 09:57:05 -04:00
|
|
|
|
right_stats,
|
2026-07-30 23:16:28 -04:00
|
|
|
|
"number owned",
|
|
|
|
|
|
str(_inventory.get_count(fish.id) if _inventory != null else 0),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _make_stats_column() -> VBoxContainer:
|
|
|
|
|
|
var column := VBoxContainer.new()
|
|
|
|
|
|
column.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
|
|
|
|
column.size_flags_stretch_ratio = 1.0
|
|
|
|
|
|
column.add_theme_constant_override("separation", 12)
|
|
|
|
|
|
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-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:
|
|
|
|
|
|
_refresh_details(false)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _unknown_selection_key(fish: FishDataType) -> StringName:
|
|
|
|
|
|
var number: int = LogbookCatalog.catalog_number(fish.id)
|
|
|
|
|
|
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)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-30 23:16:28 -04:00
|
|
|
|
func _paper_style(color: Color, left: bool) -> StyleBoxFlat:
|
|
|
|
|
|
var style := StyleBoxFlat.new()
|
|
|
|
|
|
style.bg_color = color
|
|
|
|
|
|
style.border_color = Color("9b7a4f")
|
|
|
|
|
|
style.set_border_width_all(3)
|
|
|
|
|
|
style.corner_radius_top_left = 26 if left else 6
|
|
|
|
|
|
style.corner_radius_bottom_left = 32 if left else 6
|
|
|
|
|
|
style.corner_radius_top_right = 6 if left else 28
|
|
|
|
|
|
style.corner_radius_bottom_right = 6 if left else 34
|
|
|
|
|
|
return style
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-08-02 09:57:05 -04:00
|
|
|
|
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(".")
|
|
|
|
|
|
var transparent_style := StyleBoxEmpty.new()
|
|
|
|
|
|
for state: StringName in [
|
|
|
|
|
|
&"normal", &"hover", &"focus", &"pressed", &"disabled",
|
|
|
|
|
|
]:
|
|
|
|
|
|
_portrait_overlay_backdrop.add_theme_stylebox_override(
|
|
|
|
|
|
state, transparent_style
|
|
|
|
|
|
)
|
|
|
|
|
|
_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)
|
|
|
|
|
|
|
|
|
|
|
|
var center := CenterContainer.new()
|
|
|
|
|
|
center.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
|
|
|
|
center.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
|
|
|
|
|
_portrait_overlay.add_child(center)
|
|
|
|
|
|
var card := PanelContainer.new()
|
|
|
|
|
|
card.mouse_filter = Control.MOUSE_FILTER_STOP
|
|
|
|
|
|
card.add_theme_stylebox_override("panel", _portrait_view_style())
|
|
|
|
|
|
center.add_child(card)
|
|
|
|
|
|
var margin := MarginContainer.new()
|
|
|
|
|
|
_set_margins(margin, 22, 22, 22, 22)
|
|
|
|
|
|
card.add_child(margin)
|
|
|
|
|
|
_portrait_overlay_artwork = LogbookPortraitType.new()
|
|
|
|
|
|
margin.add_child(_portrait_overlay_artwork)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
|
)
|
|
|
|
|
|
_portrait_overlay.visible = true
|
|
|
|
|
|
_portrait_overlay.mouse_filter = Control.MOUSE_FILTER_STOP
|
|
|
|
|
|
_portrait_overlay_backdrop.grab_focus()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
if (
|
|
|
|
|
|
restore_focus
|
|
|
|
|
|
and _detail_portrait_button != null
|
|
|
|
|
|
and is_instance_valid(_detail_portrait_button)
|
|
|
|
|
|
and _active
|
|
|
|
|
|
and _interactive
|
|
|
|
|
|
):
|
|
|
|
|
|
_detail_portrait_button.grab_focus()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
|
style.bg_color = Color("fff5dc")
|
|
|
|
|
|
style.set_border_width_all(0)
|
|
|
|
|
|
style.set_corner_radius_all(18)
|
|
|
|
|
|
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)
|