diff --git a/scenes/menus/storybook/components/storybook.gd b/scenes/menus/storybook/components/storybook.gd index 6f23edb8f6..87e76c0937 100644 --- a/scenes/menus/storybook/components/storybook.gd +++ b/scenes/menus/storybook/components/storybook.gd @@ -2,6 +2,7 @@ # SPDX-License-Identifier: MPL-2.0 class_name Storybook extends CanvasLayer + ## Offers a choice of quests by scanning a given [member quest_directory]. ## Emitted when the player chooses a quest from the storybook, with @@ -13,11 +14,20 @@ signal selected(quest: Quest, restart: bool) ## Quests to show in the storybook. @export var quests: Array[Quest] +@export var quests_per_page: int = 8: + set(value): + quests_per_page = value + quests_per_spread = value * 2 + +var quests_per_spread: int = 16 var _current_spread_index: int = -1 var _navigation_locked: bool = false +var _current_list_page: int = 0 + +@onready var left_quest_list: VBoxContainer = %LeftQuestList +@onready var right_quest_list: VBoxContainer = %RightQuestList -@onready var quest_list: VBoxContainer = %QuestList @onready var quest_container: ScrollContainer = %QuestContainer @onready var storybook_page: StorybookPage = %StorybookPage @onready var back_button: Button = %BackButton @@ -27,27 +37,39 @@ var _navigation_locked: bool = false func _ready() -> void: animated_book.animation_finished.connect(_on_animation_finished) + _populate_quest_lists() - var previous_button: Button = null - for i in quests.size(): - var quest: Quest = quests[i] - var button := Button.new() - button.text = quest.get_title() - button.theme_type_variation = "FlatButton" - quest_list.add_child(button) - button.set_meta("quest_index", i) - button.pressed.connect(_on_quest_button_pressed.bind(button)) - button.focus_next = back_button.get_path() +## Clears and regenerates the quest buttons based on the current page view +func _populate_quest_lists() -> void: + #Clear out any existing buttons from previous views + for child in left_quest_list.get_children(): + child.queue_free() + for child in right_quest_list.get_children(): + child.queue_free() + + #Calculate the quest slices for this specific book spread + var left_start: int = _current_list_page * quests_per_page * 2 + var left_end: int = left_start + quests_per_page + var right_start: int = left_end + var right_end: int = right_start + quests_per_page - button.focus_entered.connect(quest_container.ensure_control_visible.bind(button)) + var previous_button: Button = null - if previous_button: - button.focus_neighbor_top = previous_button.get_path() - previous_button.focus_neighbor_bottom = button.get_path() + # Building the left page + for i in range(left_start, min(left_end, quests.size())): + previous_button = _create_quest_button(i, left_quest_list, previous_button) - previous_button = button + #Building the right page + for i in range(right_start, min(right_end, quests.size())): + previous_button = _create_quest_button(i, right_quest_list, previous_button) + # If the right page is empty, add a blank Control spacer so it maintains its width + if right_quest_list.get_child_count() == 0: + var spacer: Control = Control.new() + spacer.custom_minimum_size.x = 500 + right_quest_list.add_child(spacer) + #Connect UI Focus back to the back button safely if previous_button: previous_button.focus_neighbor_bottom = back_button.get_path() back_button.focus_neighbor_top = previous_button.get_path() @@ -55,14 +77,37 @@ func _ready() -> void: reset_focus() +## Method to build individual buttons (StoryQuests) and manage the focus chains +func _create_quest_button( + quest_index: int, parent_container: VBoxContainer, prev_btn: Button +) -> Button: + var quest: Quest = quests[quest_index] + var button := Button.new() + button.text = quest.get_title() + button.theme_type_variation = "FlatButton" + parent_container.add_child(button) + button.set_meta("quest_index", quest_index) + + button.pressed.connect(_on_quest_button_pressed.bind(button)) + button.focus_next = back_button.get_path() + button.focus_entered.connect(quest_container.ensure_control_visible.bind(button)) + + if prev_btn: + button.focus_neighbor_top = prev_btn.get_path() + prev_btn.focus_neighbor_bottom = button.get_path() + + return button + + ## Show/hide index or detail pages func _update_page_visibility() -> void: if _current_spread_index == 0: quest_container.visible = true storybook_page.visible = false - if quest_list.get_child_count() > 0: - var first_button: Button = quest_list.get_child(0) + # Grab focus on the first visible item of the left page + if left_quest_list.get_child_count() > 0: + var first_button: Button = left_quest_list.get_child(0) if first_button and is_instance_valid(first_button) and not first_button.has_focus(): first_button.grab_focus() else: @@ -78,8 +123,6 @@ func _update_page_visibility() -> void: if not storybook_page.play_button.has_focus(): storybook_page.play_button.grab_focus() - # TODO: move the back button into the page scene & - # set the focus relationships in the inspector. back_button.focus_previous = storybook_page.play_button.get_path() storybook_page.play_button.focus_next = back_button.get_path() @@ -128,7 +171,6 @@ func _switch_to_page(spread_index: int) -> void: else: animated_book.play("book_left") ui_container.visible = false - else: _update_page_visibility() _navigation_locked = false @@ -137,25 +179,45 @@ func _switch_to_page(spread_index: int) -> void: func _on_animation_finished() -> void: _navigation_locked = false ui_container.visible = true - _update_page_visibility() func _on_left_button_pressed() -> void: if _navigation_locked: return + + # If we are on the main index, turn pages back inside the list + if _current_spread_index == 0 and _current_list_page > 0: + _current_list_page -= 1 + animated_book.play("book_left") + ui_container.visible = false + await animated_book.animation_finished + _populate_quest_lists() + return + _switch_to_page(_current_spread_index - 1) func _on_right_button_pressed() -> void: if _navigation_locked: return + + # If we are on the main index, check if there are more quests to reveal on a new page + if _current_spread_index == 0: + var max_visible_so_far: int = (_current_list_page + 1) * quests_per_page * 2 + if quests.size() > max_visible_so_far: + _current_list_page += 1 + animated_book.play("book_right") + ui_container.visible = false + await animated_book.animation_finished + _populate_quest_lists() + return + _switch_to_page(_current_spread_index + 1) func _input(event: InputEvent) -> void: if event.is_action_pressed(&"ui_cancel"): - # Go back get_viewport().set_input_as_handled() selected.emit(null, false) elif event.is_action_pressed("next_tab"): diff --git a/scenes/menus/storybook/storybook.tscn b/scenes/menus/storybook/storybook.tscn index 8aa38e52c7..d9003d4ff2 100644 --- a/scenes/menus/storybook/storybook.tscn +++ b/scenes/menus/storybook/storybook.tscn @@ -68,15 +68,13 @@ layout_mode = 2 [node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/CenterContainer/StoryBookContent" unique_id=448414689] layout_mode = 1 -anchors_preset = 8 +anchors_preset = -1 anchor_left = 0.5 -anchor_top = 0.5 anchor_right = 0.5 -anchor_bottom = 0.5 -offset_left = -365.0 -offset_top = -192.0 -offset_right = 73.0 -offset_bottom = 360.0 +offset_left = -105.0 +offset_top = -191.0 +offset_right = 333.0 +offset_bottom = 361.0 grow_horizontal = 2 grow_vertical = 2 scale = Vector2(0.8, 0.8) @@ -87,18 +85,35 @@ unique_name_in_owner = true layout_mode = 2 horizontal_scroll_mode = 0 -[node name="LeftPage" type="MarginContainer" parent="VBoxContainer/CenterContainer/StoryBookContent/MarginContainer/QuestContainer" unique_id=103149296] +[node name="BookSpread" type="HBoxContainer" parent="VBoxContainer/CenterContainer/StoryBookContent/MarginContainer/QuestContainer" unique_id=1606272031] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 55 + +[node name="LeftPage" type="MarginContainer" parent="VBoxContainer/CenterContainer/StoryBookContent/MarginContainer/QuestContainer/BookSpread" unique_id=103149296] layout_mode = 2 size_flags_horizontal = 3 theme_override_constants/margin_left = 40 -[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/CenterContainer/StoryBookContent/MarginContainer/QuestContainer/LeftPage" unique_id=434752580] +[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/CenterContainer/StoryBookContent/MarginContainer/QuestContainer/BookSpread/LeftPage" unique_id=434752580] layout_mode = 2 -[node name="QuestList" type="VBoxContainer" parent="VBoxContainer/CenterContainer/StoryBookContent/MarginContainer/QuestContainer/LeftPage/VBoxContainer" unique_id=887510218] +[node name="LeftQuestList" type="VBoxContainer" parent="VBoxContainer/CenterContainer/StoryBookContent/MarginContainer/QuestContainer/BookSpread/LeftPage/VBoxContainer" unique_id=887510218] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 4 +size_flags_vertical = 6 + +[node name="RightPage" type="MarginContainer" parent="VBoxContainer/CenterContainer/StoryBookContent/MarginContainer/QuestContainer/BookSpread" unique_id=1947571017] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/CenterContainer/StoryBookContent/MarginContainer/QuestContainer/BookSpread/RightPage" unique_id=687423957] +layout_mode = 2 + +[node name="RightQuestList" type="VBoxContainer" parent="VBoxContainer/CenterContainer/StoryBookContent/MarginContainer/QuestContainer/BookSpread/RightPage/VBoxContainer" unique_id=1110923687] unique_name_in_owner = true layout_mode = 2 -size_flags_vertical = 3 [node name="StorybookPage" parent="VBoxContainer/CenterContainer/StoryBookContent" unique_id=1375899951 instance=ExtResource("3_n2i2u")] unique_name_in_owner = true