straywild/ui/logbook_page.gd

2318 lines
75 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

class_name LogbookPage
extends Control
const CollectionLogType = preload("res://collection/collection_log.gd")
const FishDataType = preload("res://fish/fish_data.gd")
const FishQualityType = preload("res://fish/fish_quality.gd")
const FishInventoryType = preload("res://inventory/fish_inventory.gd")
const FishPoolType = preload("res://fish/fish_pool.gd")
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"
)
const SILHOUETTE_SHADER: Shader = preload(
"res://ui/logbook_silhouette.gdshader"
)
const OrganizerTabType = preload("res://ui/components/organizer_tab.gd")
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"
)
const InventoryNotepadType = preload(
"res://ui/components/inventory_notepad.gd"
)
const LogbookPortraitType = preload(
"res://ui/components/logbook_portrait.gd"
)
const CurrencyPresentationType = preload(
"res://ui/currency_presentation.gd"
)
const DETAIL_FADE_DURATION: float = 0.12
const CATEGORY_FADE_DURATION: float = UIMotion.UTILITY_EXIT_DURATION
const HANDWRITTEN_NUMERIC_SCALE: float = 0.8
const PORTRAIT_VIEW_MAX_SIZE := Vector2(680.0, 420.0)
const DETAIL_OVERLAY_CARD_SIZE := Vector2(800.0, 600.0)
const DETAIL_OVERLAY_CONTENT_MARGIN: int = 34
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
const INK := Color("251b10")
const MUTED_INK := Color("6d5b45")
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)
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
const INDEX_TAB_SIZE := Vector2(88.0, 38.0)
const INDEX_TAB_RIGHT_INSET: float = 72.0
const PAGE_CONTENT_SCALE: float = 0.97
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
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)
const DETAIL_SECTION_SEPARATION: int = 10
const DETAIL_QUALITY_SECTION_SIZE := Vector2(300.0, 104.0)
const DETAIL_STATS_SECTION_SIZE := Vector2(400.0, 160.0)
const DETAIL_STATS_ROW_SEPARATION: int = 8
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)
enum ControllerZone {
TABS,
ENTRIES,
DETAILS,
INDEX,
OVERLAY,
}
var _collection_log: CollectionLogType
var _inventory: FishInventoryType
var _catalog: FishPoolType
var _fishing_spot: FishingSpotType
var _world_time: WorldTimeServiceType
var _world_weather: WorldWeatherServiceType
var _category: LogbookCatalog.Category = (
LogbookCatalog.Category.FRESH_WATER
)
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
var _silhouette_material: ShaderMaterial
var _outer_margin: MarginContainer
var _book: Control
var _compact_presentation: bool = false
var _category_tabs: Array[Button] = []
var _category_tab_categories: Array[LogbookCatalog.Category] = []
var _catalog_scroll: ScrollContainer
var _catalog_grid: GridContainer
var _catalog_scroll_up_indicator: TextureRect
var _catalog_scroll_down_indicator: TextureRect
var _empty_state: Label
var _detail_body: VBoxContainer
var _detail_portrait_button: Button
var _detail_buttons: Array[Button] = []
var _portrait_overlay: Control
var _portrait_overlay_backdrop: Button
var _portrait_overlay_artwork: LogbookPortraitType
var _portrait_overlay_artwork_view: Control
var _portrait_overlay_title: Label
var _portrait_overlay_text: Label
var _portrait_overlay_quality_view: Control
var _portrait_overlay_quality: VBoxContainer
var _portrait_overlay_stats_view: Control
var _portrait_overlay_stats: GridContainer
var _overlay_return_focus: Control
var _controller_zone: ControllerZone = ControllerZone.TABS
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": "",
}
func _ready() -> void:
_silhouette_material = ShaderMaterial.new()
_silhouette_material.shader = SILHOUETTE_SHADER
_build_interface()
set_interactive(false)
func setup(
collection_log: CollectionLogType,
inventory: FishInventoryType,
catalog: FishPoolType,
fishing_spot: FishingSpotType = null,
world_time: WorldTimeServiceType = null,
world_weather: WorldWeatherServiceType = null,
) -> void:
_collection_log = collection_log
_inventory = inventory
_catalog = catalog
_fishing_spot = fishing_spot
_world_time = world_time
_world_weather = world_weather
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)
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)
_refresh_catalog()
func set_compact_presentation(compact: bool) -> void:
_compact_presentation = compact
_apply_presentation_margins()
_apply_index_presentation()
func activate() -> void:
_active = true
set_interactive(true)
_refresh_catalog()
_animate_tab_entry()
func deactivate() -> void:
_active = false
_hide_portrait_overlay(false)
set_interactive(false)
_settle_tabs_for_close()
_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 and _controller_zone == ControllerZone.TABS
else Control.FOCUS_NONE
)
tab.mouse_filter = (
Control.MOUSE_FILTER_STOP
if _interactive and not tab.button_pressed
else Control.MOUSE_FILTER_IGNORE
)
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
)
for entry: Button in _entry_buttons.values():
entry.focus_mode = (
Control.FOCUS_ALL
if _interactive and _controller_zone == ControllerZone.ENTRIES
else Control.FOCUS_NONE
)
entry.mouse_filter = (
Control.MOUSE_FILTER_STOP
if _interactive
else Control.MOUSE_FILTER_IGNORE
)
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
)
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
)
if _portrait_overlay_backdrop != null:
_portrait_overlay_backdrop.focus_mode = (
Control.FOCUS_ALL
if _interactive and _controller_zone == ControllerZone.OVERLAY
else Control.FOCUS_NONE
)
func focus_initial() -> void:
if not _active or not _interactive:
return
if _controller_zone == ControllerZone.OVERLAY:
_portrait_overlay_backdrop.grab_focus()
return
if _controller_zone == ControllerZone.TABS:
if _index_open and _index_tab != null:
_index_tab.grab_focus()
return
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
if _controller_zone == ControllerZone.INDEX:
if _index_search != null:
_index_search.grab_focus()
return
var selected := _entry_buttons.get(_selected_entry_key) 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()
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)
ControllerZone.INDEX:
_set_index_open(false)
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"):
if _index_open:
_set_index_open(false, false)
_category_tabs.back().grab_focus()
return true
_select_adjacent_controller_category(-1)
return true
if event.is_action_pressed("ui_right"):
if (
_category_tab_categories.find(_category)
== _category_tab_categories.size() - 1
):
_set_index_open(true, false)
_index_tab.grab_focus()
return true
_select_adjacent_controller_category(1)
return true
if accept_pressed:
if _index_open:
_set_controller_zone(ControllerZone.INDEX)
elif not _entry_buttons.is_empty():
_set_controller_zone(ControllerZone.ENTRIES)
return true
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
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])
func _build_interface() -> void:
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
_outer_margin = MarginContainer.new()
_outer_margin.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
add_child(_outer_margin)
_apply_presentation_margins()
var stack := Control.new()
stack.mouse_filter = Control.MOUSE_FILTER_IGNORE
_outer_margin.add_child(stack)
var tabs := HBoxContainer.new()
# Align the first flange with the authored left paper edge.
tabs.position = Vector2(LOGBOOK_TAB_LEFT_INSET, 37.0)
var category_order: Array[LogbookCatalog.Category] = [
LogbookCatalog.Category.FRESH_WATER,
LogbookCatalog.Category.SALT_WATER,
LogbookCatalog.Category.SHELLFISH,
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:
var tab := OrganizerTabType.new()
tab.custom_minimum_size = LOGBOOK_TAB_SIZE
tab.toggle_mode = true
tab.text = LogbookCatalog.category_label(category)
tab.palette_index = int(category)
tab.set_selected(category == _category, false)
tab.pressed.connect(_select_category.bind(category))
tabs.add_child(tab)
_category_tabs.append(tab)
_category_tab_categories.append(category)
_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)
_configure_category_focus()
var book := Control.new()
_book = book
book.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
book.offset_top = (
COMPACT_BOOK_TOP if _compact_presentation else WIDE_BOOK_TOP
)
book.z_index = 20
book.mouse_filter = Control.MOUSE_FILTER_IGNORE
stack.add_child(book)
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)
book.add_child(left_page)
var left_layout := VBoxContainer.new()
left_layout.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
left_layout.add_theme_constant_override("separation", 8)
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)
_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)
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)
_catalog_scroll = ScrollContainer.new()
_catalog_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
_catalog_scroll.horizontal_scroll_mode = (
ScrollContainer.SCROLL_MODE_DISABLED
)
_catalog_scroll.follow_focus = true
_catalog_scroll.scroll_vertical_custom_step = CATALOG_ROW_STEP
catalog_scroll_margin.add_child(_catalog_scroll)
_catalog_grid = GridContainer.new()
_catalog_grid.columns = 4
_catalog_grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_catalog_grid.add_theme_constant_override("h_separation", 6)
_catalog_grid.add_theme_constant_override("v_separation", 6)
_catalog_scroll.add_child(_catalog_grid)
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,
)
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)
book.add_child(right_page)
_detail_body = VBoxContainer.new()
_detail_body.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
_detail_body.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_detail_body.size_flags_vertical = Control.SIZE_EXPAND_FILL
_detail_body.add_theme_constant_override(
"separation", DETAIL_SECTION_SEPARATION
)
right_page.add_child(_detail_body)
_apply_page_content_scale(_detail_body)
_show_no_selection()
_build_index_page(right_page)
_build_portrait_overlay()
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
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
)
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
func _on_catalog_scroll_value_changed(_value: float) -> void:
_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
)
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] = []
var category_total: int = 0
if _catalog != null:
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(),
)
for fish: FishDataType in species:
var discovered: bool = _collection_log.has_discovered(fish.id)
var entry: Button = _make_entry(fish, discovered)
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 = (
"No entries match this index."
if _index_has_active_criteria()
else 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)
_update_index_controls(species.size(), category_total)
set_interactive(_interactive)
_refresh_catalog_scroll_indicators.call_deferred()
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 "za" if descending else "az"
LogbookCatalog.SortMode.CATALOG_NUMBER:
return "highlow" if descending else "lowhigh"
LogbookCatalog.SortMode.RARITY:
return (
"rarecommon" if descending else "commonrare"
)
LogbookCatalog.SortMode.VALUE, LogbookCatalog.SortMode.WEIGHT:
return "highlow" if descending else "lowhigh"
_:
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()
func _make_entry(fish: FishDataType, discovered: bool) -> Button:
var entry := Button.new()
entry.custom_minimum_size = CATALOG_ENTRY_SIZE
entry.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
entry.size_flags_vertical = Control.SIZE_SHRINK_CENTER
entry.toggle_mode = true
entry.add_theme_stylebox_override("normal", _entry_style(false))
entry.add_theme_stylebox_override("hover", _entry_style(true))
entry.add_theme_stylebox_override("hover_pressed", _entry_style(true))
entry.add_theme_stylebox_override("focus", _entry_style(true))
entry.add_theme_stylebox_override("pressed", _entry_style(true))
entry.text = ""
if not discovered:
entry.tooltip_text = ""
entry.accessibility_name = "Unknown catalog entry"
_add_entry_content(entry, fish.display_texture, "???", true)
else:
entry.tooltip_text = fish.display_name
entry.accessibility_name = fish.display_name
_add_entry_content(
entry, fish.display_texture, fish.display_name, false
)
return entry
func _add_entry_content(
entry: Button,
portrait: Texture2D,
entry_name: String,
unknown: bool,
) -> 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)
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)
entry.add_child(content_margin)
var content := VBoxContainer.new()
content.mouse_filter = Control.MOUSE_FILTER_IGNORE
content.alignment = BoxContainer.ALIGNMENT_CENTER
content.add_theme_constant_override("separation", 4)
content_margin.add_child(content)
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)
var portrait_view := LogbookPortraitType.new()
portrait_view.configure_uniform_footprint(
portrait,
CATALOG_PORTRAIT_SIZE,
_silhouette_material if unknown else null,
)
portrait_frame.add_child(portrait_view)
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)
func _select_category(category: LogbookCatalog.Category) -> void:
if category == _category:
if _index_open:
_set_index_open(false)
return
_category = category
_set_index_open(false, false)
_selected_id = StringName()
_selected_entry_key = StringName()
for index: int in _category_tabs.size():
(_category_tabs[index] as OrganizerTabType).set_selected(
_category_tab_categories[index] == _category
)
_begin_category_transition()
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)]
)
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)
func _animate_tab_entry() -> void:
for index: int in _category_tabs.size():
(_category_tabs[index] as OrganizerTabType).animate_entrance(
float(index) * 0.025
)
if _index_tab != null:
_index_tab.animate_entrance(float(_category_tabs.size()) * 0.025)
func _settle_tabs_for_close() -> void:
for tab: OrganizerTabType in _category_tabs:
tab.settle_for_close()
if _index_tab != null:
_index_tab.settle_for_close()
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
if _index_body != null:
_index_body.modulate.a = alpha
func _select_entry(entry_key: StringName, fish_id: StringName) -> void:
_set_index_open(false, false)
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)
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)
var summary_columns := HBoxContainer.new()
summary_columns.size_flags_horizontal = Control.SIZE_EXPAND_FILL
summary_columns.add_theme_constant_override("separation", 24)
summary_margin.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)
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
portrait_column.add_child(title)
if fish.display_texture != null:
_detail_portrait_button = Button.new()
_detail_portrait_button.custom_minimum_size = DETAIL_PORTRAIT_SIZE
_detail_portrait_button.size_flags_horizontal = (
Control.SIZE_SHRINK_CENTER
)
_detail_portrait_button.clip_contents = true
_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)
)
_detail_buttons.append(_detail_portrait_button)
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,
DETAIL_PORTRAIT_SIZE,
)
artwork_center.add_child(artwork)
var facts_text: String = LogbookCatalog.facts_for(fish)
var facts_section_title: String = (
"shellfish facts"
if fish.logbook_section == FishDataType.LogbookSection.SHELLFISH
else "fish facts"
)
var facts_button := _make_detail_section_button(
facts_section_title,
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)
var facts_column := VBoxContainer.new()
facts_column.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
facts_column.mouse_filter = Control.MOUSE_FILTER_IGNORE
facts_column.add_theme_constant_override("separation", 6)
facts_button.add_child(facts_column)
var facts_heading := _field_label(
facts_section_title,
16,
)
facts_column.add_child(facts_heading)
var facts := _label(facts_text, 16)
facts.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
facts.size_flags_horizontal = Control.SIZE_EXPAND_FILL
facts_column.add_child(facts)
var quality_button := _make_detail_section_button(
"quality collection",
_quality_overlay_text(fish.id),
)
quality_button.custom_minimum_size = DETAIL_QUALITY_SECTION_SIZE
quality_button.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
_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),
)
stats_button.custom_minimum_size = DETAIL_STATS_SECTION_SIZE
stats_button.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
_detail_body.add_child(stats_button)
var stats_anchor := VBoxContainer.new()
stats_anchor.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
stats_anchor.mouse_filter = Control.MOUSE_FILTER_IGNORE
stats_anchor.size_flags_horizontal = Control.SIZE_EXPAND_FILL
stats_anchor.size_flags_vertical = Control.SIZE_EXPAND_FILL
stats_anchor.alignment = BoxContainer.ALIGNMENT_BEGIN
stats_button.add_child(stats_anchor)
var stats_columns := HBoxContainer.new()
stats_columns.size_flags_horizontal = Control.SIZE_EXPAND_FILL
stats_columns.add_theme_constant_override("separation", 28)
stats_anchor.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)
_add_detail_row(
left_stats,
"catalog number",
"#%03d" % catalog_number if catalog_number > 0 else "unknown",
)
_add_detail_row(
left_stats,
(
"habitat"
if fish.logbook_section == FishDataType.LogbookSection.SHELLFISH
else "body of water"
),
fish.get_habitat_label(),
)
_add_detail_row(
left_stats,
"weight range",
"%.2f%.2f lb" % [
fish.get_minimum_weight(),
fish.get_maximum_weight(),
],
)
_add_detail_row(
left_stats,
"number caught",
str(_collection_log.get_catch_count(fish.id)),
)
_add_detail_row(right_stats, "rarity", fish.get_rarity_name().to_lower())
_add_detail_row(right_stats, "time of day", _availability_text(fish))
_add_detail_row(right_stats, "seasons", fish.get_season_text())
_add_currency_detail_row(
right_stats,
"value range",
FishQualityType.apply_sale_value(
fish.sell_value_min,
FishQualityType.Tier.BORING,
),
FishQualityType.apply_sale_value(
fish.sell_value_max,
FishQualityType.Tier.SHINY,
),
)
_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)
func _build_quality_progress(fish_id: StringName) -> VBoxContainer:
var quality_section := VBoxContainer.new()
quality_section.size_flags_horizontal = Control.SIZE_EXPAND_FILL
quality_section.add_theme_constant_override("separation", 6)
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,
],
17,
)
heading.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
heading.add_theme_color_override("font_color", MUTED_INK)
quality_section.add_child(heading)
var tiers := VBoxContainer.new()
tiers.add_theme_constant_override("separation", 6)
quality_section.add_child(tiers)
for quality: int in FishQualityType.TIER_COUNT:
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
var discovered: bool = _collection_log.has_discovered_quality(
fish_id,
quality,
)
var tier := HBoxContainer.new()
tier.alignment = BoxContainer.ALIGNMENT_CENTER
tier.add_theme_constant_override("separation", 5)
var quality_color: Color = UIPalette.get_quality_color(quality)
var dot := _label("" if discovered else "", 17)
dot.add_theme_color_override("font_color", quality_color)
dot.modulate.a = 1.0 if discovered else 0.48
tier.add_child(dot)
var tier_label := _label(FishQualityType.display_name(quality), 16)
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)
row.add_child(tier)
return quality_section
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(),
],
"number caught: %d" % _collection_log.get_catch_count(fish.id),
"rarity: %s" % fish.get_rarity_name().to_lower(),
"time of day: %s" % _availability_text(fish),
"seasons: %s" % fish.get_season_text(),
"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,
),
],
])
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)
func _add_detail_row(
parent: VBoxContainer,
row_name: String,
value: String,
) -> 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)
var content := _handwritten_value_label(value.to_lower(), 16)
content.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
content.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
row.add_child(heading)
row.add_child(content)
parent.add_child(row)
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)
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", DETAIL_STATS_ROW_SEPARATION
)
return column
func _availability_text(fish: FishDataType) -> String:
if fish.availability == null:
return "unknown"
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"
return "unknown"
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:
_detail_portrait_button = null
_detail_buttons.clear()
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:
if _catalog == null:
return
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
_refresh_catalog()
func _on_collection_changed() -> void:
_refresh_catalog()
func _on_inventory_changed() -> void:
# Ownership filters and favorite-backed views depend on the live cooler,
# while rebuilding the catalog also keeps the selected detail current.
_refresh_catalog()
func _unknown_selection_key(fish: FishDataType) -> StringName:
var number: int = LogbookCatalog.catalog_number(fish)
return StringName("unknown_%d" % number)
func _label(text: String, font_size: int) -> Label:
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:
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
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(".")
var backdrop_style := _portrait_overlay_backdrop_style()
for state: StringName in [
&"normal", &"hover", &"focus", &"pressed", &"disabled",
]:
_portrait_overlay_backdrop.add_theme_stylebox_override(
state, backdrop_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 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)
var card := PanelContainer.new()
card.mouse_filter = Control.MOUSE_FILTER_STOP
card.custom_minimum_size = DETAIL_OVERLAY_CARD_SIZE
card.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
card.size_flags_vertical = Control.SIZE_SHRINK_CENTER
card.add_theme_stylebox_override("panel", _portrait_view_style())
overlay_center.add_child(card)
var margin := MarginContainer.new()
_set_margins(
margin,
DETAIL_OVERLAY_CONTENT_MARGIN,
DETAIL_OVERLAY_CONTENT_MARGIN,
DETAIL_OVERLAY_CONTENT_MARGIN,
DETAIL_OVERLAY_CONTENT_MARGIN,
)
card.add_child(margin)
var overlay_stack := VBoxContainer.new()
overlay_stack.add_theme_constant_override("separation", 20)
margin.add_child(overlay_stack)
_portrait_overlay_title = _field_label(
"", DETAIL_OVERLAY_TITLE_FONT_SIZE
)
_portrait_overlay_title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_portrait_overlay_title.add_theme_color_override(
"font_color", UIPalette.TEXT
)
overlay_stack.add_child(_portrait_overlay_title)
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)
_portrait_overlay_artwork = LogbookPortraitType.new()
_portrait_overlay_artwork.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
_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
)
_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
_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")
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_title.text = "artwork"
_set_detail_overlay_view(&"artwork")
_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
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")
_overlay_return_focus = return_focus
_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 _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()
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
_controller_zone = ControllerZone.DETAILS
set_interactive(_interactive)
if (
restore_focus
and _overlay_return_focus != null
and is_instance_valid(_overlay_return_focus)
and _active
and _interactive
):
_overlay_return_focus.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 = 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
style.set_border_width_all(0)
style.set_corner_radius_all(16)
return style
func _entry_style(highlighted: bool) -> StyleBoxFlat:
var style := StyleBoxFlat.new()
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
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)