Skip to content

Commit

Permalink
feat(Library): add interface and menus to select install location
Browse files Browse the repository at this point in the history
  • Loading branch information
ShadowApex committed Jan 16, 2025
1 parent dccb967 commit 3032c5c
Show file tree
Hide file tree
Showing 11 changed files with 886 additions and 45 deletions.
22 changes: 15 additions & 7 deletions core/global/install_manager.gd
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,16 @@ func _process_install_queue() -> void:
# Install the given application using the given provider
install_started.emit(req)
var result: Array
if req._type == REQUEST_TYPE.INSTALL:
req.provider.install(req.item)
result = await req.provider.install_completed
else:
req.provider.update(req.item)
result = await req.provider.update_completed
match req._type:
REQUEST_TYPE.INSTALL:
req.provider.install_to(req.item, req.location, req.options)
result = await req.provider.install_completed
REQUEST_TYPE.UPDATE:
req.provider.update(req.item)
result = await req.provider.update_completed
_:
logger.warn("Unknown request type:", req._type)
result = [req.item, false]
req.success = result[1]
logger.info("Install of '" + req.item.name + "' completed with success: " + str(req.success))

Expand All @@ -116,10 +120,14 @@ class Request extends RefCounted:
signal completed(success: bool)
var provider: Library
var item: LibraryLaunchItem
var location: Library.InstallLocation
var options: Dictionary
var progress: float
var success: bool
var _type: REQUEST_TYPE

func _init(library_provider: Library, launch_item: LibraryLaunchItem) -> void:
func _init(library_provider: Library, launch_item: LibraryLaunchItem, to_location: Library.InstallLocation = null, opts: Dictionary = {}) -> void:
provider = library_provider
item = launch_item
location = to_location
options = opts
96 changes: 82 additions & 14 deletions core/systems/library/library.gd
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,13 @@ signal install_progressed(item: LibraryLaunchItem, percent_completed: float)
signal launch_item_added(item: LibraryLaunchItem)
## Should be emitted when a library item was removed from the library
signal launch_item_removed(item: LibraryLaunchItem)
## Should be emitted when a library item is moved to a different install location
signal move_completed(item: LibraryLaunchItem, success: bool)
## Should be emitted when a library item move is progressing
signal move_progressed(item: LibraryLaunchItem, percent_completed: float)

var LibraryManager := load("res://core/global/library_manager.tres") as LibraryManager
var LibraryManager := load("res://core/global/library_manager.tres") as LibraryManager # TODO: Remove this
var library_manager := load("res://core/global/library_manager.tres") as LibraryManager

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

func _init() -> void:
add_to_group("library")
ready.connect(LibraryManager.register_library.bind(self))
ready.connect(library_manager.register_library.bind(self))


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


## Returns an array of available install locations for this library provider.
## This method should be overridden in the child class.
## Example:
## [codeblock]
## func get_available_install_locations() -> Array[InstallLocation]:
## var location := InstallLocation.new()
## location.name = "/"
## return [location]
## [/codeblock]
func get_available_install_locations(item: LibraryLaunchItem = null) -> Array[InstallLocation]:
return []


## Returns an array of install options for the given [LibraryLaunchItem].
## Install options are arbitrary and are provider-specific. They allow the user
## to select things like the language of a game to install, etc.
## Example:
## [codeblock]
## func get_install_options(item: LibraryLaunchItem) -> Array[InstallOption]:
## var option := InstallOption.new()
## option.id = "lang"
## option.name = "Language"
## option.description = "Language of the game to install"
## option.values = ["english", "spanish"]
## option.value_type = TYPE_STRING
## return [option]
## [/codeblock]
func get_install_options(item: LibraryLaunchItem) -> Array[InstallOption]:
return []


## This method should be overridden if the library requires executing callbacks
## at certain points in an app's lifecycle, such as when an app is starting or
## stopping.
func get_app_lifecycle_hooks() -> Array[AppLifecycleHook]:
var hooks: Array[AppLifecycleHook]
return hooks


## [DEPRECATED]
## Installs the given library item. This method should be overriden in the
## child class, if it supports it.
func install(item: LibraryLaunchItem) -> void:
pass


## Installs the given library item to the given location. This method should be
## overridden in the child class, if it supports it.
func install_to(item: LibraryLaunchItem, location: InstallLocation = null, options: Dictionary = {}) -> void:
install(item)


## Updates the given library item. This method should be overriden in the
## child class, if it supports it.
func update(item: LibraryLaunchItem) -> void:
pass


## Should return true if the given library item has an update available
func has_update(item: LibraryLaunchItem) -> bool:
return false


## Uninstalls the given library item. This method should be overriden in the
## child class if it supports it.
func uninstall(item: LibraryLaunchItem) -> void:
pass


## Should return true if the given library item has an update available
func has_update(item: LibraryLaunchItem) -> bool:
return false


## This method should be overridden if the library requires executing callbacks
## at certain points in an app's lifecycle, such as when an app is starting or
## stopping.
func get_app_lifecycle_hooks() -> Array[AppLifecycleHook]:
var hooks: Array[AppLifecycleHook]
return hooks
## Move the given library item to the given install location. This method should
## be overriden in the child class if it supports it.
func move(item: LibraryLaunchItem, to_location: InstallLocation) -> void:
pass


func _exit_tree() -> void:
LibraryManager.unregister_library(self)
library_manager.unregister_library(self)


## InstallLocation defines a place where a [LibraryLaunchItem] can be installed.
class InstallLocation extends RefCounted:
var id: String
var name: String
var description: String
var icon: Texture2D
var total_space_mb: int
var free_space_mb: int


## InstallOption defines an arbitrary install option for a [LibraryLaunchitem].
class InstallOption extends RefCounted:
var id: String
var name: String
var description: String
var values: Array[Variant]
var value_type: int
82 changes: 61 additions & 21 deletions core/ui/card_ui/launch/game_launch_menu.gd
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,17 @@ var logger := Log.get_logger("GameLaunchMenu")

@export var launch_item: LibraryLaunchItem

@onready var banner: TextureRect = $%BannerTexture
@onready var logo: TextureRect = $%LogoTexture
@onready var launch_button := $%LaunchButton
@onready var loading: Control = $%LoadingAnimation
@onready var player := $%AnimationPlayer
@onready var progress_container := $%ProgressContainer
@onready var progress_bar: ProgressBar = $%ProgressBar
@onready var delete_container := $%DeleteContainer
@onready var delete_button := $%DeleteButton
@onready var banner := $%BannerTexture as TextureRect
@onready var logo := $%LogoTexture as TextureRect
@onready var launch_button := $%LaunchButton as CardButton
@onready var loading := $%LoadingAnimation as Control
@onready var player := $%AnimationPlayer as AnimationPlayer
@onready var progress_container := $%ProgressContainer as MarginContainer
@onready var progress_bar := $%ProgressBar as ProgressBar
@onready var delete_container := $%DeleteContainer as MarginContainer
@onready var delete_button := $%DeleteButton as CardIconButton
@onready var location_menu := $%InstallLocationDialog as InstallLocationDialog
@onready var options_menu := $%InstallOptionsDialog as InstallOptionDialog


# Called when the node enters the scene tree for the first time.
Expand Down Expand Up @@ -55,7 +57,11 @@ func _process(_delta: float) -> void:
func _on_state_entered(_from: State) -> void:
# Fade in the banner texture
player.play("fade_in")


# Ensure dialogs are hidden
location_menu.visible = false
options_menu.visible = false

# Focus the first entry on state change
launch_button.grab_focus.call_deferred()

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

# Load the banner for the game
var logo_texture = await (
BoxArtManager . get_boxart_or_placeholder(library_item, BoxArtProvider.LAYOUT.LOGO)
BoxArtManager.get_boxart_or_placeholder(library_item, BoxArtProvider.LAYOUT.LOGO)
)
logo.texture = logo_texture
var banner_texture = await (
BoxArtManager . get_boxart_or_placeholder(library_item, BoxArtProvider.LAYOUT.BANNER)
BoxArtManager.get_boxart_or_placeholder(library_item, BoxArtProvider.LAYOUT.BANNER)
)
banner.texture = banner_texture

Expand All @@ -119,15 +125,15 @@ func _update_launch_button() -> void:
if not launch_item:
return
if launch_item.installed:
launch_button.text = "Play Now"
launch_button.text = tr("Play Now")
else:
launch_button.text = "Install"
launch_button.text = tr("Install")
if LaunchManager.is_running(launch_item.name):
launch_button.text = "Resume"
launch_button.text = tr("Resume")
if InstallManager.is_queued(launch_item):
launch_button.text = "Queued"
launch_button.text = tr("Queued")
if InstallManager.is_installing(launch_item):
launch_button.text = "Installing"
launch_button.text = tr("Installing")


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

# Create an install request
# Get the library provider for this launch item
var provider := LibraryManager.get_library_by_id(launch_item._provider_id)
var install_req := InstallManager.Request.new(provider, launch_item)

# If multiple install locations are available, ask the user where to
# install.
var location: Library.InstallLocation = null
var locations := await provider.get_available_install_locations(launch_item)
if locations.size() > 0:
# Open the install location menu and wait for the user to select an
# install location.
location_menu.open(launch_button, locations)
var result := await location_menu.choice_selected as Array
var accepted := result[0] as bool
var choice := result[1] as Library.InstallLocation
if not accepted:
return
location = choice

# If install options are available for this library item, ask the user
# to select from the options.
var options := {}
var available_options := await provider.get_install_options(launch_item)
if available_options.size() > 0:
# Open the install option menu and wait for the user to select install
# options.
options_menu.open(launch_button, available_options)
var result := await options_menu.choice_selected as Array
var accepted := result[0] as bool
var choices := result[1] as Dictionary
if not accepted:
return
options = choices

# Create an install request
var install_req := InstallManager.Request.new(provider, launch_item, location, options)

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

# Display a notification
var notify := Notification.new("Installing " + launch_item.name)
NotificationManager.show(notify)

# Start the install
InstallManager.install(install_req)

Expand Down
14 changes: 13 additions & 1 deletion core/ui/card_ui/launch/game_launch_menu.tscn
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[gd_scene load_steps=22 format=3 uid="uid://bcdk1lj6enq3l"]
[gd_scene load_steps=24 format=3 uid="uid://bcdk1lj6enq3l"]

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

[sub_resource type="Animation" id="Animation_ou6f5"]
Expand Down Expand Up @@ -252,3 +254,13 @@ size_flags_vertical = 4
theme_override_styles/fill = SubResource("StyleBoxFlat_7fb8y")
value = 50.0
rounded = true

[node name="InstallLocationDialog" parent="." instance=ExtResource("18_j25yi")]
unique_name_in_owner = true
visible = false
layout_mode = 1

[node name="InstallOptionsDialog" parent="." instance=ExtResource("19_k020t")]
unique_name_in_owner = true
visible = false
layout_mode = 1
3 changes: 1 addition & 2 deletions core/ui/components/dropdown.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ autowrap_mode = 3
[node name="OptionButton" type="OptionButton" parent="."]
unique_name_in_owner = true
layout_mode = 2
item_count = 1
fit_to_longest_item = false
item_count = 1
popup/item_0/text = "None"
popup/item_0/id = 0
Loading

0 comments on commit 3032c5c

Please sign in to comment.