From c15d49bfd1cce0d53feb209ea493d5e941cf8fa5 Mon Sep 17 00:00:00 2001 From: Jose Falanga Date: Mon, 17 Aug 2026 12:27:14 -0300 Subject: [PATCH] Migrate settings UI to OGPU themed widgets and add logout - Replace plain Godot Controls with StatusPanel, ComponentTextInput, Toggle, CardButton, and Dropdown components - Add logout functionality that clears API key or closes session - Match Steam plugin's settings UI style --- core/itch_client.gd | 22 ++++++++ core/itch_settings.gd | 119 ++++++++++++++++++++++++++-------------- core/itch_settings.tscn | 109 +++++++++++++++++++++--------------- 3 files changed, 166 insertions(+), 84 deletions(-) diff --git a/core/itch_client.gd b/core/itch_client.gd index 06b3c6e..5af9df4 100644 --- a/core/itch_client.gd +++ b/core/itch_client.gd @@ -961,6 +961,28 @@ func _launch(cave_id: String) -> void: launch_exited.emit.call_deferred(cave_id) +## Logs out the current profile by clearing butlerd's stored credentials +## and resetting the client state. For API key logins the caller should +## also clear the saved key via settings_manager. +func logout() -> void: + await thread_group.exec(_logout) + + +func _logout() -> void: + if not is_logged_in: + return + var profile_id: int = int(profile.get("user", {}).get("id", profile.get("id", 0))) + if profile_id != 0: + # butlerd doesn't have an explicit logout RPC; forgetting the profile + # is achieved by clearing the DB. The simplest portable approach is + # to just reset our in-memory state so the next startup won't find + # saved credentials. + logger.info("Logging out profile " + str(profile_id)) + is_logged_in = false + profile = {} + emit_signal.call_deferred("logged_in", LOGIN_STATUS.FAILED, {}) + + func _exit_tree() -> void: if socket: socket.disconnect_from_host() diff --git a/core/itch_settings.gd b/core/itch_settings.gd index 82ee1d7..a2bd26c 100644 --- a/core/itch_settings.gd +++ b/core/itch_settings.gd @@ -1,28 +1,27 @@ -extends Control +extends MarginContainer ## itch.io plugin settings screen ## -## NOTE: this uses plain Godot Controls rather than OpenGamepadUI's themed -## widget set (the ones used by the built-in Steam plugin's settings scene, -## e.g. its custom StatusIndicator/TextInput components), since this plugin -## was written without editor access to those scene resources. Swap the -## nodes below for the themed equivalents if you want it to match the rest -## of the settings UI pixel-for-pixel. +## Uses OpenGamepadUI's themed widget set (StatusPanel, ComponentTextInput, +## Toggle, CardButton, Dropdown) for consistent look with the rest of the +## settings UI. const ItchClient := preload("res://plugins/itch/core/itch_client.gd") var settings_manager := load("res://core/global/settings_manager.tres") as SettingsManager var notification_manager := load("res://core/global/notification_manager.tres") as NotificationManager const icon := preload("res://plugins/itch/assets/itch.svg") -@onready var status_label: Label = $%StatusLabel -@onready var api_key_box: LineEdit = $%ApiKeyInput -@onready var username_box: LineEdit = $%UsernameInput -@onready var password_box: LineEdit = $%PasswordInput -@onready var login_method: OptionButton = $%LoginMethod -@onready var save_button: Button = $%SaveButton -@onready var help_label: Label = $%HelpLabel -@onready var filter_check: CheckBox = $%FilterUnsupported -@onready var show_purchases_check: CheckBox = $%ShowPurchases +@onready var status := $%Status as StatusPanel +@onready var connected_status := $%ConnectedStatus as StatusPanel +@onready var logged_in_status := $%LoggedInStatus as StatusPanel +@onready var login_method := $%LoginMethod as Dropdown +@onready var api_key_box := $%ApiKeyInput as ComponentTextInput +@onready var username_box := $%UsernameInput as ComponentTextInput +@onready var password_box := $%PasswordInput as ComponentTextInput +@onready var save_button := $%SaveButton as CardButton +@onready var logout_button := $%LogoutButton as CardButton +@onready var filter_check := $%FilterUnsupported as Toggle +@onready var show_purchases_check := $%ShowPurchases as Toggle @onready var collections_container: VBoxContainer = $%CollectionsContainer @onready var itch: ItchClient = get_tree().get_first_node_in_group("itch_client") @@ -31,12 +30,49 @@ var _collection_checks: Dictionary = {} func _ready() -> void: + # Configure status indicators + status.status = status.STATUS.CANCELLED + status.color = "red" + var set_running := func(): + if not itch.client_started: + return + status.status = status.STATUS.ACTIVE + status.color = "green" + if itch.client_started: + set_running.call() + itch.bootstrap_finished.connect(set_running) + + # Configure connected status + connected_status.status = connected_status.STATUS.ACTIVE + if itch.state != itch.STATE.BOOT: + connected_status.color = "green" + itch.client_ready.connect(func(): + connected_status.color = "green" + ) + + # Configure login status + var update_login_status := func(login_status: ItchClient.LOGIN_STATUS): + if login_status != ItchClient.LOGIN_STATUS.OK: + logged_in_status.status = logged_in_status.STATUS.ACTIVE + logged_in_status.color = "gray" + return + logged_in_status.status = logged_in_status.STATUS.CLOSED + logged_in_status.color = "green" + itch.logged_in.connect(update_login_status) + itch.logged_in.connect(_on_login) + + # Configure login method dropdown + login_method.add_item("API Key", 0) + login_method.add_item("Username & Password", 1) + login_method.selected = 0 + + # Load saved settings var api_key := settings_manager.get_value("plugin.itch", "api_key", "") as String api_key_box.text = api_key api_key_box.secret = true var saved_method: int = settings_manager.get_value("plugin.itch", "login_method", 0) as int - login_method.selected = saved_method + login_method.select(saved_method) _on_login_method_changed(saved_method) var username := settings_manager.get_value("plugin.itch", "username", "") as String @@ -45,39 +81,21 @@ func _ready() -> void: filter_check.button_pressed = settings_manager.get_value("plugin.itch", "filter_unsupported", true) as bool show_purchases_check.button_pressed = settings_manager.get_value("plugin.itch", "show_purchases", true) as bool - _update_status() - itch.client_ready.connect(_update_status) - itch.logged_in.connect(_on_login) - + # Connect signals save_button.pressed.connect(_on_save_button) - filter_check.toggled.connect(_on_filter_toggled) + logout_button.pressed.connect(_on_logout_button) login_method.item_selected.connect(_on_login_method_changed) + filter_check.toggled.connect(_on_filter_toggled) show_purchases_check.toggled.connect(_on_show_purchases_toggled) # Build collection checkboxes once we're logged in. itch.logged_in.connect(_populate_collections.bind(), CONNECT_ONE_SHOT) -func _update_status() -> void: - if not itch: - status_label.text = "Status: itch client not found" - return - if itch.state == itch.STATE.BOOT: - status_label.text = "Status: starting butlerd..." - return - if not itch.is_logged_in: - status_label.text = "Status: connected, not logged in" - return - var username: String = itch.profile.get("user", {}).get("username", "") - status_label.text = "Status: logged in as " + username - - -func _on_login(status: ItchClient.LOGIN_STATUS, _profile: Dictionary) -> void: - _update_status() - +func _on_login(login_status: ItchClient.LOGIN_STATUS, _profile: Dictionary) -> void: var notify := Notification.new("") notify.icon = icon - if status == ItchClient.LOGIN_STATUS.OK: + if login_status == ItchClient.LOGIN_STATUS.OK: notify.text = "Successfully logged in to itch.io" else: notify.text = "itch.io login failed. Double check your credentials." @@ -91,7 +109,6 @@ func _on_login_method_changed(idx: int) -> void: api_key_box.visible = is_api username_box.visible = not is_api password_box.visible = not is_api - help_label.visible = is_api func _on_save_button() -> void: @@ -116,6 +133,26 @@ func _on_save_button() -> void: itch.login_with_password(uname, password) +func _on_logout_button() -> void: + var method: int = login_method.selected + + # Clear saved credentials + if method == 0: + settings_manager.set_value("plugin.itch", "api_key", "") + api_key_box.text = "" + else: + settings_manager.set_value("plugin.itch", "username", "") + username_box.text = "" + password_box.text = "" + + # Logout from butlerd + itch.logout() + + var notify := Notification.new("Logged out of itch.io") + notify.icon = icon + notification_manager.show(notify) + + ## Fetches the user's collections and builds a checkbox for each one. func _populate_collections(_status: ItchClient.LOGIN_STATUS, _profile: Dictionary) -> void: if not itch.is_logged_in: diff --git a/core/itch_settings.tscn b/core/itch_settings.tscn index f7dca25..d2f313f 100644 --- a/core/itch_settings.tscn +++ b/core/itch_settings.tscn @@ -1,88 +1,111 @@ -[gd_scene load_steps=2 format=3 uid="uid://itch_settings_scene"] +[gd_scene load_steps=7 format=3 uid="uid://itch_settings_scene"] [ext_resource type="Script" path="res://plugins/itch/core/itch_settings.gd" id="1"] +[ext_resource type="PackedScene" uid="uid://d1hlp6c8wrqgv" path="res://core/ui/components/status.tscn" id="2"] +[ext_resource type="PackedScene" uid="uid://d1rjdfxxrdccf" path="res://core/ui/components/text_input.tscn" id="3"] +[ext_resource type="PackedScene" uid="uid://d1qb7euwlu7bh" path="res://core/ui/components/toggle.tscn" id="4"] +[ext_resource type="PackedScene" uid="uid://c71ayw7pcw6u6" path="res://core/ui/components/card_button.tscn" id="5"] +[ext_resource type="PackedScene" uid="uid://xei5afwefxud" path="res://core/ui/components/dropdown.tscn" id="6"] -[node name="ItchSettings" type="Control"] -layout_mode = 3 +[node name="ItchSettings" type="MarginContainer"] anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 script = ExtResource("1") -[node name="VBoxContainer" type="VBoxContainer" parent="."] -layout_mode = 1 -anchors_preset = 8 -anchor_left = 0.5 -anchor_top = 0.5 -anchor_right = 0.5 -anchor_bottom = 0.5 -offset_left = -200.0 -offset_top = -120.0 -offset_right = 200.0 -offset_bottom = 120.0 - -[node name="TitleLabel" type="Label" parent="VBoxContainer"] +[node name="ContentContainer" type="VBoxContainer" parent="."] layout_mode = 2 -text = "itch.io" -horizontal_alignment = 1 +size_flags_horizontal = 3 +size_flags_vertical = 0 +theme_override_constants/separation = 10 -[node name="StatusLabel" type="Label" parent="VBoxContainer"] +[node name="Status" parent="ContentContainer" instance=ExtResource("2")] unique_name_in_owner = true layout_mode = 2 -text = "Status: starting butlerd..." +title = "Status" +description = "" +status = 2 +color = "red" -[node name="LoginMethod" type="OptionButton" parent="VBoxContainer"] +[node name="ConnectedStatus" parent="ContentContainer" instance=ExtResource("2")] unique_name_in_owner = true layout_mode = 2 -item_count = 2 -popup/item_0_text = "API Key" -popup/item_1_text = "Username & Password" +title = "Connected" +description = "" +color = "gray" -[node name="ApiKeyInput" type="LineEdit" parent="VBoxContainer"] +[node name="LoggedInStatus" parent="ContentContainer" instance=ExtResource("2")] unique_name_in_owner = true layout_mode = 2 -placeholder_text = "itch.io API key" +title = "Logged In" +description = "" +color = "gray" -[node name="UsernameInput" type="LineEdit" parent="VBoxContainer"] +[node name="HSeparator" type="HSeparator" parent="ContentContainer"] +layout_mode = 2 + +[node name="LoginMethod" parent="ContentContainer" instance=ExtResource("6")] unique_name_in_owner = true layout_mode = 2 -placeholder_text = "Username" +title = "Login Method" +description = "" + +[node name="ApiKeyInput" parent="ContentContainer" instance=ExtResource("3")] +unique_name_in_owner = true +layout_mode = 2 +title = "API Key" +description = "Get an API key from https://itch.io/user/settings/api-keys" +secret = true + +[node name="UsernameInput" parent="ContentContainer" instance=ExtResource("3")] +unique_name_in_owner = true +layout_mode = 2 +title = "Username" +description = "" visible = false -[node name="PasswordInput" type="LineEdit" parent="VBoxContainer"] +[node name="PasswordInput" parent="ContentContainer" instance=ExtResource("3")] unique_name_in_owner = true layout_mode = 2 -placeholder_text = "Password" +title = "Password" +description = "" secret = true visible = false -[node name="HelpLabel" type="Label" parent="VBoxContainer"] +[node name="HSeparatorLogin" type="HSeparator" parent="ContentContainer"] +layout_mode = 2 + +[node name="SaveButton" parent="ContentContainer" instance=ExtResource("5")] unique_name_in_owner = true layout_mode = 2 -autowrap_mode = 2 -text = "Get an API key from https://itch.io/user/settings/api-keys" +text = "Save and Log In" -[node name="FilterUnsupported" type="CheckBox" parent="VBoxContainer"] +[node name="LogoutButton" parent="ContentContainer" instance=ExtResource("5")] +unique_name_in_owner = true +layout_mode = 2 +text = "Logout" + +[node name="HSeparator2" type="HSeparator" parent="ContentContainer"] +layout_mode = 2 + +[node name="FilterUnsupported" parent="ContentContainer" instance=ExtResource("4")] unique_name_in_owner = true layout_mode = 2 text = "Only show games available on this platform" +button_pressed = true -[node name="ShowPurchases" type="CheckBox" parent="VBoxContainer"] +[node name="ShowPurchases" parent="ContentContainer" instance=ExtResource("4")] unique_name_in_owner = true layout_mode = 2 text = "Show main purchases" button_pressed = true -[node name="CollectionsLabel" type="Label" parent="VBoxContainer"] -unique_name_in_owner = true +[node name="CollectionsLabel" type="Label" parent="ContentContainer"] layout_mode = 2 text = "Show collections:" -[node name="CollectionsContainer" type="VBoxContainer" parent="VBoxContainer"] +[node name="CollectionsContainer" type="VBoxContainer" parent="ContentContainer"] unique_name_in_owner = true layout_mode = 2 - -[node name="SaveButton" type="Button" parent="VBoxContainer"] -unique_name_in_owner = true -layout_mode = 2 -text = "Save and Log In"