netfishing/ui/logbook_page.gd

987 lines
30 KiB
GDScript
Raw Permalink 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 SILHOUETTE_SHADER: Shader = preload(
"res://ui/logbook_silhouette.gdshader"
)
const OrganizerTabType = preload("res://ui/components/organizer_tab.gd")
const InventoryNotepadType = preload(
"res://ui/components/inventory_notepad.gd"
)
const LogbookPortraitType = preload(
"res://ui/components/logbook_portrait.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(860.0, 480.0)
const INK := Color("251b10")
const MUTED_INK := Color("6d5b45")
const PAPER := Color("f2e6c9")
const PAPER_DARK := Color("e8d7b4")
const LOGBOOK_TAB_LEFT_INSET: float = 34.0
var _collection_log: CollectionLogType
var _inventory: FishInventoryType
var _catalog: FishPoolType
var _category: WaterType.Type = WaterType.Type.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 _category_tabs: Array[Button] = []
var _catalog_scroll: ScrollContainer
var _catalog_grid: GridContainer
var _empty_state: Label
var _detail_body: VBoxContainer
var _detail_portrait_button: Button
var _portrait_overlay: Control
var _portrait_overlay_backdrop: Button
var _portrait_overlay_artwork: LogbookPortraitType
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,
) -> 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()
_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 else Control.FOCUS_NONE
)
tab.mouse_filter = (
Control.MOUSE_FILTER_STOP
if _interactive and not tab.button_pressed
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
if _portrait_overlay != null and _portrait_overlay.visible:
_portrait_overlay_backdrop.grab_focus()
return
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)
var stack := Control.new()
stack.mouse_filter = Control.MOUSE_FILTER_IGNORE
outer.add_child(stack)
var tabs := HBoxContainer.new()
# 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)
tabs.z_index = 10
tabs.mouse_filter = Control.MOUSE_FILTER_PASS
tabs.add_theme_constant_override("separation", 6)
stack.add_child(tabs)
for category: WaterType.Type in [
WaterType.Type.FRESH_WATER,
WaterType.Type.SALT_WATER,
WaterType.Type.OTHER,
]:
var tab := OrganizerTabType.new()
tab.custom_minimum_size = Vector2(124, 38)
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)
_configure_category_focus()
var book := HBoxContainer.new()
book.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
book.offset_top = 50.0
book.z_index = 20
book.add_theme_constant_override("separation", 12)
stack.add_child(book)
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()
_catalog_grid.columns = 5
_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
_detail_body.size_flags_vertical = Control.SIZE_EXPAND_FILL
_detail_body.add_theme_constant_override("separation", 16)
right_margin.add_child(_detail_body)
_show_no_selection()
_build_portrait_overlay()
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)
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 = 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)
func _make_entry(fish: FishDataType, discovered: bool) -> Button:
var entry := Button.new()
entry.custom_minimum_size = Vector2(102, 102)
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_view := LogbookPortraitType.new()
portrait_view.configure(
portrait,
LogbookPortraitType.ENTRY_FRAME_SIZE,
_silhouette_material if unknown else null,
)
content.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: WaterType.Type) -> void:
if category == _category:
return
_category = category
_selected_id = StringName()
_selected_entry_key = StringName()
for index: int in _category_tabs.size():
(_category_tabs[index] as OrganizerTabType).set_selected(
index == int(_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)]
)
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()
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)
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)
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 = (
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)
_detail_body.add_child(_build_quality_progress(fish.id))
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)
_add_detail_row(
left_stats,
"catalog number",
"#%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(),
)
_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", "unknown")
_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,
"value range",
"%d%d fish coin" % [
FishQualityType.apply_sale_value(
fish.sell_value_min,
FishQualityType.Tier.BORING,
),
FishQualityType.apply_sale_value(
fish.sell_value_max,
FishQualityType.Tier.SHINY,
),
],
)
_add_detail_row(
right_stats,
"number owned",
str(_inventory.get_count(fish.id) if _inventory != null else 0),
)
func _build_quality_progress(fish_id: StringName) -> VBoxContainer:
var quality_section := VBoxContainer.new()
quality_section.add_theme_constant_override("separation", 3)
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,
],
14,
)
heading.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
heading.add_theme_color_override("font_color", MUTED_INK)
quality_section.add_child(heading)
var tiers := HBoxContainer.new()
tiers.alignment = BoxContainer.ALIGNMENT_CENTER
tiers.add_theme_constant_override("separation", 10)
quality_section.add_child(tiers)
for quality: int in FishQualityType.TIER_COUNT:
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", 3)
var quality_color: Color = UIPalette.get_quality_color(quality)
var dot := _label("" if discovered else "", 13)
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), 13)
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)
tiers.add_child(tier)
return quality_section
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 _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
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
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:
_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:
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 _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
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:
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)