Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 85 additions & 23 deletions scenes/menus/storybook/components/storybook.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Comment thread
alcole2 marked this conversation as resolved.

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
Expand All @@ -27,42 +37,77 @@ 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()

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)
Comment on lines +108 to +110

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this really working? When I press the "Next" action and switch to the next 2 pages, the focus is lost. We don't have enough storyquests for testing it in the actual game, but it can be reproduced using the test scene:

Grabacion.de.pantalla.desde.2026-07-27.12-44-15.mp4

So you can't use the keyboard to select the quest anymore.

if first_button and is_instance_valid(first_button) and not first_button.has_focus():
first_button.grab_focus()
else:
Expand All @@ -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()

Expand Down Expand Up @@ -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
Expand All @@ -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"):
Expand Down
37 changes: 26 additions & 11 deletions scenes/menus/storybook/storybook.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down