Skip to content

Commit 7b05cc2

Browse files
committed
feat(Library): add interface and menus to select install location
1 parent dccb967 commit 7b05cc2

11 files changed

+886
-45
lines changed

core/global/install_manager.gd

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,16 @@ func _process_install_queue() -> void:
8989
# Install the given application using the given provider
9090
install_started.emit(req)
9191
var result: Array
92-
if req._type == REQUEST_TYPE.INSTALL:
93-
req.provider.install(req.item)
94-
result = await req.provider.install_completed
95-
else:
96-
req.provider.update(req.item)
97-
result = await req.provider.update_completed
92+
match req._type:
93+
REQUEST_TYPE.INSTALL:
94+
req.provider.install_to(req.item, req.location, req.options)
95+
result = await req.provider.install_completed
96+
REQUEST_TYPE.UPDATE:
97+
req.provider.update(req.item)
98+
result = await req.provider.update_completed
99+
_:
100+
logger.warn("Unknown request type:", req._type)
101+
result = [req.item, false]
98102
req.success = result[1]
99103
logger.info("Install of '" + req.item.name + "' completed with success: " + str(req.success))
100104

@@ -116,10 +120,14 @@ class Request extends RefCounted:
116120
signal completed(success: bool)
117121
var provider: Library
118122
var item: LibraryLaunchItem
123+
var location: Library.InstallLocation
124+
var options: Dictionary
119125
var progress: float
120126
var success: bool
121127
var _type: REQUEST_TYPE
122128

123-
func _init(library_provider: Library, launch_item: LibraryLaunchItem) -> void:
129+
func _init(library_provider: Library, launch_item: LibraryLaunchItem, to_location: Library.InstallLocation = null, opts: Dictionary = {}) -> void:
124130
provider = library_provider
125131
item = launch_item
132+
location = to_location
133+
options = opts

core/systems/library/library.gd

Lines changed: 82 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,13 @@ signal install_progressed(item: LibraryLaunchItem, percent_completed: float)
2323
signal launch_item_added(item: LibraryLaunchItem)
2424
## Should be emitted when a library item was removed from the library
2525
signal launch_item_removed(item: LibraryLaunchItem)
26+
## Should be emitted when a library item is moved to a different install location
27+
signal move_completed(item: LibraryLaunchItem, success: bool)
28+
## Should be emitted when a library item move is progressing
29+
signal move_progressed(item: LibraryLaunchItem, percent_completed: float)
2630

27-
var LibraryManager := load("res://core/global/library_manager.tres") as LibraryManager
31+
var LibraryManager := load("res://core/global/library_manager.tres") as LibraryManager # TODO: Remove this
32+
var library_manager := load("res://core/global/library_manager.tres") as LibraryManager
2833

2934
## Unique identifier for the library
3035
@export var library_id: String
@@ -45,7 +50,7 @@ var LibraryManager := load("res://core/global/library_manager.tres") as LibraryM
4550

4651
func _init() -> void:
4752
add_to_group("library")
48-
ready.connect(LibraryManager.register_library.bind(self))
53+
ready.connect(library_manager.register_library.bind(self))
4954

5055

5156
# Called when the node enters the scene tree for the first time.
@@ -71,36 +76,99 @@ func get_library_launch_items() -> Array[LibraryLaunchItem]:
7176
return []
7277

7378

79+
## Returns an array of available install locations for this library provider.
80+
## This method should be overridden in the child class.
81+
## Example:
82+
## [codeblock]
83+
## func get_available_install_locations() -> Array[InstallLocation]:
84+
## var location := InstallLocation.new()
85+
## location.name = "/"
86+
## return [location]
87+
## [/codeblock]
88+
func get_available_install_locations(item: LibraryLaunchItem = null) -> Array[InstallLocation]:
89+
return []
90+
91+
92+
## Returns an array of install options for the given [LibraryLaunchItem].
93+
## Install options are arbitrary and are provider-specific. They allow the user
94+
## to select things like the language of a game to install, etc.
95+
## Example:
96+
## [codeblock]
97+
## func get_install_options(item: LibraryLaunchItem) -> Array[InstallOption]:
98+
## var option := InstallOption.new()
99+
## option.id = "lang"
100+
## option.name = "Language"
101+
## option.description = "Language of the game to install"
102+
## option.values = ["english", "spanish"]
103+
## option.value_type = TYPE_STRING
104+
## return [option]
105+
## [/codeblock]
106+
func get_install_options(item: LibraryLaunchItem) -> Array[InstallOption]:
107+
return []
108+
109+
110+
## This method should be overridden if the library requires executing callbacks
111+
## at certain points in an app's lifecycle, such as when an app is starting or
112+
## stopping.
113+
func get_app_lifecycle_hooks() -> Array[AppLifecycleHook]:
114+
var hooks: Array[AppLifecycleHook]
115+
return hooks
116+
117+
118+
## [DEPRECATED]
74119
## Installs the given library item. This method should be overriden in the
75120
## child class, if it supports it.
76121
func install(item: LibraryLaunchItem) -> void:
77122
pass
78123

79124

125+
## Installs the given library item to the given location. This method should be
126+
## overridden in the child class, if it supports it.
127+
func install_to(item: LibraryLaunchItem, location: InstallLocation = null, options: Dictionary = {}) -> void:
128+
install(item)
129+
130+
80131
## Updates the given library item. This method should be overriden in the
81132
## child class, if it supports it.
82133
func update(item: LibraryLaunchItem) -> void:
83134
pass
84135

85136

137+
## Should return true if the given library item has an update available
138+
func has_update(item: LibraryLaunchItem) -> bool:
139+
return false
140+
141+
86142
## Uninstalls the given library item. This method should be overriden in the
87143
## child class if it supports it.
88144
func uninstall(item: LibraryLaunchItem) -> void:
89145
pass
90146

91147

92-
## Should return true if the given library item has an update available
93-
func has_update(item: LibraryLaunchItem) -> bool:
94-
return false
95-
96-
97-
## This method should be overridden if the library requires executing callbacks
98-
## at certain points in an app's lifecycle, such as when an app is starting or
99-
## stopping.
100-
func get_app_lifecycle_hooks() -> Array[AppLifecycleHook]:
101-
var hooks: Array[AppLifecycleHook]
102-
return hooks
148+
## Move the given library item to the given install location. This method should
149+
## be overriden in the child class if it supports it.
150+
func move(item: LibraryLaunchItem, to_location: InstallLocation) -> void:
151+
pass
103152

104153

105154
func _exit_tree() -> void:
106-
LibraryManager.unregister_library(self)
155+
library_manager.unregister_library(self)
156+
157+
158+
## InstallLocation defines a place where a [LibraryLaunchItem] can be installed.
159+
class InstallLocation extends RefCounted:
160+
var id: String
161+
var name: String
162+
var description: String
163+
var icon: Texture2D
164+
var total_space_mb: int
165+
var free_space_mb: int
166+
167+
168+
## InstallOption defines an arbitrary install option for a [LibraryLaunchitem].
169+
class InstallOption extends RefCounted:
170+
var id: String
171+
var name: String
172+
var description: String
173+
var values: Array[Variant]
174+
var value_type: int

core/ui/card_ui/launch/game_launch_menu.gd

Lines changed: 61 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,17 @@ var logger := Log.get_logger("GameLaunchMenu")
1818

1919
@export var launch_item: LibraryLaunchItem
2020

21-
@onready var banner: TextureRect = $%BannerTexture
22-
@onready var logo: TextureRect = $%LogoTexture
23-
@onready var launch_button := $%LaunchButton
24-
@onready var loading: Control = $%LoadingAnimation
25-
@onready var player := $%AnimationPlayer
26-
@onready var progress_container := $%ProgressContainer
27-
@onready var progress_bar: ProgressBar = $%ProgressBar
28-
@onready var delete_container := $%DeleteContainer
29-
@onready var delete_button := $%DeleteButton
21+
@onready var banner := $%BannerTexture as TextureRect
22+
@onready var logo := $%LogoTexture as TextureRect
23+
@onready var launch_button := $%LaunchButton as CardButton
24+
@onready var loading := $%LoadingAnimation as Control
25+
@onready var player := $%AnimationPlayer as AnimationPlayer
26+
@onready var progress_container := $%ProgressContainer as MarginContainer
27+
@onready var progress_bar := $%ProgressBar as ProgressBar
28+
@onready var delete_container := $%DeleteContainer as MarginContainer
29+
@onready var delete_button := $%DeleteButton as CardIconButton
30+
@onready var location_menu := $%InstallLocationDialog as InstallLocationDialog
31+
@onready var options_menu := $%InstallOptionsDialog as InstallOptionDialog
3032

3133

3234
# Called when the node enters the scene tree for the first time.
@@ -55,7 +57,11 @@ func _process(_delta: float) -> void:
5557
func _on_state_entered(_from: State) -> void:
5658
# Fade in the banner texture
5759
player.play("fade_in")
58-
60+
61+
# Ensure dialogs are hidden
62+
location_menu.visible = false
63+
options_menu.visible = false
64+
5965
# Focus the first entry on state change
6066
launch_button.grab_focus.call_deferred()
6167

@@ -96,11 +102,11 @@ func _on_state_entered(_from: State) -> void:
96102

97103
# Load the banner for the game
98104
var logo_texture = await (
99-
BoxArtManager . get_boxart_or_placeholder(library_item, BoxArtProvider.LAYOUT.LOGO)
105+
BoxArtManager.get_boxart_or_placeholder(library_item, BoxArtProvider.LAYOUT.LOGO)
100106
)
101107
logo.texture = logo_texture
102108
var banner_texture = await (
103-
BoxArtManager . get_boxart_or_placeholder(library_item, BoxArtProvider.LAYOUT.BANNER)
109+
BoxArtManager.get_boxart_or_placeholder(library_item, BoxArtProvider.LAYOUT.BANNER)
104110
)
105111
banner.texture = banner_texture
106112

@@ -119,15 +125,15 @@ func _update_launch_button() -> void:
119125
if not launch_item:
120126
return
121127
if launch_item.installed:
122-
launch_button.text = "Play Now"
128+
launch_button.text = tr("Play Now")
123129
else:
124-
launch_button.text = "Install"
130+
launch_button.text = tr("Install")
125131
if LaunchManager.is_running(launch_item.name):
126-
launch_button.text = "Resume"
132+
launch_button.text = tr("Resume")
127133
if InstallManager.is_queued(launch_item):
128-
launch_button.text = "Queued"
134+
launch_button.text = tr("Queued")
129135
if InstallManager.is_installing(launch_item):
130-
launch_button.text = "Installing"
136+
launch_button.text = tr("Installing")
131137

132138

133139
func _update_uninstall_button() -> void:
@@ -162,16 +168,50 @@ func _on_install() -> void:
162168
# Do nothing if we're already installing
163169
if InstallManager.is_queued_or_installing(launch_item):
164170
return
165-
var notify := Notification.new("Installing " + launch_item.name)
166-
NotificationManager.show(notify)
167171

168-
# Create an install request
172+
# Get the library provider for this launch item
169173
var provider := LibraryManager.get_library_by_id(launch_item._provider_id)
170-
var install_req := InstallManager.Request.new(provider, launch_item)
174+
175+
# If multiple install locations are available, ask the user where to
176+
# install.
177+
var location: Library.InstallLocation = null
178+
var locations := await provider.get_available_install_locations(launch_item)
179+
if locations.size() > 0:
180+
# Open the install location menu and wait for the user to select an
181+
# install location.
182+
location_menu.open(launch_button, locations)
183+
var result := await location_menu.choice_selected as Array
184+
var accepted := result[0] as bool
185+
var choice := result[1] as Library.InstallLocation
186+
if not accepted:
187+
return
188+
location = choice
189+
190+
# If install options are available for this library item, ask the user
191+
# to select from the options.
192+
var options := {}
193+
var available_options := await provider.get_install_options(launch_item)
194+
if available_options.size() > 0:
195+
# Open the install option menu and wait for the user to select install
196+
# options.
197+
options_menu.open(launch_button, available_options)
198+
var result := await options_menu.choice_selected as Array
199+
var accepted := result[0] as bool
200+
var choices := result[1] as Dictionary
201+
if not accepted:
202+
return
203+
options = choices
204+
205+
# Create an install request
206+
var install_req := InstallManager.Request.new(provider, launch_item, location, options)
171207

172208
# Update the progress bar with install progress of the request
173209
progress_bar.value = 0
174210

211+
# Display a notification
212+
var notify := Notification.new("Installing " + launch_item.name)
213+
NotificationManager.show(notify)
214+
175215
# Start the install
176216
InstallManager.install(install_req)
177217

core/ui/card_ui/launch/game_launch_menu.tscn

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[gd_scene load_steps=22 format=3 uid="uid://bcdk1lj6enq3l"]
1+
[gd_scene load_steps=24 format=3 uid="uid://bcdk1lj6enq3l"]
22

33
[ext_resource type="Script" path="res://core/ui/card_ui/launch/game_launch_menu.gd" id="1_u3ehs"]
44
[ext_resource type="Texture2D" uid="uid://d1mksukdkqorr" path="res://assets/images/placeholder-grid-banner.png" id="2_oae7b"]
@@ -15,7 +15,9 @@
1515
[ext_resource type="PackedScene" uid="uid://cr83fmlociwko" path="res://core/ui/components/card_icon_button.tscn" id="15_f3ktw"]
1616
[ext_resource type="PackedScene" uid="uid://b76dvfuouhlwd" path="res://core/systems/state/state_updater.tscn" id="15_lat8h"]
1717
[ext_resource type="Resource" uid="uid://cx8u1y5j7vyss" path="res://assets/state/states/gamepad_settings.tres" id="17_7ydn0"]
18+
[ext_resource type="PackedScene" uid="uid://b4u8djfdc4kea" path="res://core/ui/components/install_location_dialog.tscn" id="18_j25yi"]
1819
[ext_resource type="Resource" uid="uid://3vw3bk76d88w" path="res://assets/state/states/game_settings.tres" id="19_b21vy"]
20+
[ext_resource type="PackedScene" uid="uid://18axsy5my1x6" path="res://core/ui/components/install_options_dialog.tscn" id="19_k020t"]
1921
[ext_resource type="Texture2D" uid="uid://dj1ohb74chydb" path="res://assets/ui/icons/round-delete-forever.svg" id="21_agq5k"]
2022

2123
[sub_resource type="Animation" id="Animation_ou6f5"]
@@ -252,3 +254,13 @@ size_flags_vertical = 4
252254
theme_override_styles/fill = SubResource("StyleBoxFlat_7fb8y")
253255
value = 50.0
254256
rounded = true
257+
258+
[node name="InstallLocationDialog" parent="." instance=ExtResource("18_j25yi")]
259+
unique_name_in_owner = true
260+
visible = false
261+
layout_mode = 1
262+
263+
[node name="InstallOptionsDialog" parent="." instance=ExtResource("19_k020t")]
264+
unique_name_in_owner = true
265+
visible = false
266+
layout_mode = 1

core/ui/components/dropdown.tscn

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ autowrap_mode = 3
3333
[node name="OptionButton" type="OptionButton" parent="."]
3434
unique_name_in_owner = true
3535
layout_mode = 2
36-
item_count = 1
3736
fit_to_longest_item = false
37+
item_count = 1
3838
popup/item_0/text = "None"
39-
popup/item_0/id = 0

0 commit comments

Comments
 (0)