Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(StateMachine): add state_added signal, more robust signal emission, and tests #387

Merged
merged 27 commits into from
Aug 25, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4d4bac1
fix(StateMachine): add state_added signal, more robust signal emissio…
ShadowApex Aug 16, 2024
ef4712a
fix(StateMachine): refactor menus to use more robust state switching
ShadowApex Aug 16, 2024
9ab7220
fix(Dracula Theme): use correct quick bar panel style
ShadowApex Aug 17, 2024
e343e48
fix(Focus): add option to disable focus wrapping
ShadowApex Aug 17, 2024
e7f5b2d
feat(State Machine Watcher): add node to watch state machine changes
ShadowApex Aug 17, 2024
696738c
feat(Behvaior Node): add base class for behavior nodes
ShadowApex Aug 17, 2024
6a75f73
feat(Tab Setter): add behavior node to set tab index
ShadowApex Aug 17, 2024
1747f59
feat(Text Setter): add behavior node to set label text
ShadowApex Aug 17, 2024
cc9bdb8
fix(State Updater): add configuration warnings if state machine is no…
ShadowApex Aug 17, 2024
f1bef89
fix(Themes): add ThemeUtils class with method to get effective theme
ShadowApex Aug 17, 2024
1b93b7f
chore(Icons): add resource icons for behaviors
ShadowApex Aug 17, 2024
89d20cc
fix(Theme): themes will now be properly set on UI components
ShadowApex Aug 17, 2024
c5296e2
refactor(Menus): update all menus to use new state machine pattern
ShadowApex Aug 17, 2024
3f07584
fix(In Game State): clear and push states depending on in-game
ShadowApex Aug 18, 2024
9117b61
fix(Running Game Card): update to use new states
ShadowApex Aug 18, 2024
6f1855f
fix(Overlay): Un game overlay buttons no longer close the menu.
pastaq Aug 18, 2024
bc94b4f
fix(Focus) Fix focus on some menus
pastaq Aug 20, 2024
2df90bf
fix(Plugin Store Card): update focus and handle back input
ShadowApex Aug 21, 2024
a0533bf
fix(Plugin Settings): fix focus on expanding cards for plugin settings
ShadowApex Aug 21, 2024
175ae61
fix(OSK): Fix the OSK.
pastaq Aug 23, 2024
ebd0186
fix(Expandable Card): don't close card on OSK focus
ShadowApex Aug 23, 2024
68be14d
feat(Blur): add settings option to disable/enable blur when overlay i…
ShadowApex Aug 23, 2024
4503656
fix(Input): handle X button for search
ShadowApex Aug 23, 2024
1d8ad2c
fix(Input): handle left/right triggers for keyboard shifting
ShadowApex Aug 23, 2024
3140e2a
fix(Overlay Mode): Get overlay mode working with new state machines.
pastaq Aug 23, 2024
986b1e6
fix(Disks Menu): Refactor disks menu to work with the new focus methods.
pastaq Aug 23, 2024
c289117
chore(Various): minor fixes from code review
ShadowApex Aug 25, 2024
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
1 change: 1 addition & 0 deletions assets/editor-icons/fluent--brain-circuit-24-filled.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 37 additions & 0 deletions assets/editor-icons/fluent--brain-circuit-24-filled.svg.import
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[remap]

importer="texture"
type="CompressedTexture2D"
uid="uid://dumpjvnmyvle0"
path="res://.godot/imported/fluent--brain-circuit-24-filled.svg-deafc844fbbb2acb1300ed1eab205035.ctex"
metadata={
"vram_texture": false
}

[deps]

source_file="res://assets/editor-icons/fluent--brain-circuit-24-filled.svg"
dest_files=["res://.godot/imported/fluent--brain-circuit-24-filled.svg-deafc844fbbb2acb1300ed1eab205035.ctex"]

[params]

compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false
1 change: 1 addition & 0 deletions assets/editor-icons/fluent--draw-text-24-filled.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 37 additions & 0 deletions assets/editor-icons/fluent--draw-text-24-filled.svg.import
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[remap]

importer="texture"
type="CompressedTexture2D"
uid="uid://b37mlly16qbny"
path="res://.godot/imported/fluent--draw-text-24-filled.svg-62a66cb94069dc7c979f10a171be9c30.ctex"
metadata={
"vram_texture": false
}

[deps]

source_file="res://assets/editor-icons/fluent--draw-text-24-filled.svg"
dest_files=["res://.godot/imported/fluent--draw-text-24-filled.svg-62a66cb94069dc7c979f10a171be9c30.ctex"]

[params]

compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false
8 changes: 7 additions & 1 deletion assets/state/state_machines/global_state_machine.tres
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
[gd_resource type="Resource" script_class="StateMachine" load_steps=2 format=3 uid="uid://cr544el0cqjlm"]
[gd_resource type="Resource" script_class="StateMachine" load_steps=6 format=3 uid="uid://cr544el0cqjlm"]

[ext_resource type="Resource" uid="uid://c640rq4e1xmrn" path="res://assets/state/states/in_game.tres" id="1_dtppc"]
[ext_resource type="Script" path="res://core/systems/state/state_machine.gd" id="1_dw1uo"]
[ext_resource type="Resource" uid="uid://bmgs1ngma1523" path="res://assets/state/states/in_game_menu.tres" id="2_pg6ge"]
[ext_resource type="Resource" uid="uid://cv3vduo0ojk1u" path="res://assets/state/states/menu.tres" id="3_4n6bo"]
[ext_resource type="Resource" uid="uid://dgbe422crufa4" path="res://assets/state/states/popup.tres" id="4_j3a4g"]

[resource]
script = ExtResource("1_dw1uo")
logger_name = "GlobalStateMachine"
minimum_states = 1
allowed_states = Array[Resource("res://core/systems/state/state.gd")]([ExtResource("1_dtppc"), ExtResource("2_pg6ge"), ExtResource("3_4n6bo"), ExtResource("4_j3a4g")])
8 changes: 8 additions & 0 deletions assets/state/state_machines/menu_state_machine.tres
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[gd_resource type="Resource" script_class="StateMachine" load_steps=2 format=3 uid="uid://bcr6c0281lb5b"]

[ext_resource type="Script" path="res://core/systems/state/state_machine.gd" id="1_18avi"]

[resource]
script = ExtResource("1_18avi")
logger_name = "MenuStateMachine"
minimum_states = 0
14 changes: 14 additions & 0 deletions assets/state/state_machines/popup_state_machine.tres
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[gd_resource type="Resource" script_class="StateMachine" load_steps=7 format=3 uid="uid://cadriyl38ny5y"]

[ext_resource type="Resource" uid="uid://e7bbebwf7guj" path="res://assets/state/states/main_menu.tres" id="1_7fvmm"]
[ext_resource type="Resource" uid="uid://bp807nlks8eq1" path="res://assets/state/states/quick_bar_menu.tres" id="2_detpa"]
[ext_resource type="Resource" uid="uid://bw0mtk7sso8m2" path="res://assets/state/states/power_menu.tres" id="3_0mhn6"]
[ext_resource type="Resource" uid="uid://dja3m1mevv6xw" path="res://assets/state/states/osk.tres" id="4_qu5fi"]
[ext_resource type="Resource" uid="uid://db5gbdl3xgwlq" path="res://assets/state/states/help_menu.tres" id="5_5ytlq"]
[ext_resource type="Script" path="res://core/systems/state/state_machine.gd" id="6_82te1"]

[resource]
script = ExtResource("6_82te1")
logger_name = "PopupStateMachine"
minimum_states = 0
allowed_states = Array[Resource("res://core/systems/state/state.gd")]([ExtResource("1_7fvmm"), ExtResource("2_detpa"), ExtResource("3_0mhn6"), ExtResource("4_qu5fi"), ExtResource("5_5ytlq")])
8 changes: 8 additions & 0 deletions assets/state/states/menu.tres
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[gd_resource type="Resource" script_class="State" load_steps=2 format=3 uid="uid://cv3vduo0ojk1u"]

[ext_resource type="Script" path="res://core/systems/state/state.gd" id="1_8q8t2"]

[resource]
script = ExtResource("1_8q8t2")
name = "menu"
data = {}
8 changes: 8 additions & 0 deletions assets/state/states/popup.tres
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[gd_resource type="Resource" script_class="State" load_steps=2 format=3 uid="uid://dgbe422crufa4"]

[ext_resource type="Script" path="res://core/systems/state/state.gd" id="1_t0m3s"]

[resource]
script = ExtResource("1_t0m3s")
name = "popup"
data = {}
2 changes: 1 addition & 1 deletion assets/styles/dracula/quick_bar_panel.tres
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[gd_resource type="StyleBoxFlat" format=3 uid="uid://br2w1fnanqkfa"]

[resource]
bg_color = Color(0.0823529, 0.0862745, 0.105882, 1)
bg_color = Color(0.156863, 0.164706, 0.211765, 1)
corner_radius_bottom_left = 10
shadow_size = 5
shadow_offset = Vector2(-4, 4)
6 changes: 3 additions & 3 deletions assets/themes/card_ui-dracula.tres
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
[ext_resource type="StyleBox" uid="uid://y3qbwgrt2wai" path="res://assets/styles/dracula/settings_menu_panel.tres" id="10_rxp5w"]
[ext_resource type="StyleBox" uid="uid://dj0ysj0o6beba" path="res://assets/styles/dracula/power_menu_panel.tres" id="10_u1qhh"]
[ext_resource type="StyleBox" uid="uid://bifp73vg5vmau" path="res://assets/styles/dracula/plugin_store_card_panel.tres" id="11_83cpl"]
[ext_resource type="StyleBox" uid="uid://daavpt58e7jlj" path="res://assets/styles/darksoul/quick_bar_panel.tres" id="14_fm0xr"]
[ext_resource type="StyleBox" uid="uid://br2w1fnanqkfa" path="res://assets/styles/dracula/quick_bar_panel.tres" id="15_354e6"]
ShadowApex marked this conversation as resolved.
Show resolved Hide resolved

[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_s2sy6"]
bg_color = Color(0.133333, 0.137255, 0.180392, 1)
Expand Down Expand Up @@ -112,14 +112,14 @@ MainMenu/styles/panel = ExtResource("7_n80rp")
Notification/base_type = &"PanelContainer"
Notification/styles/panel = ExtResource("9_sy73e")
Panel/styles/panel = ExtResource("2_f7nj8")
PanelContainer/styles/panel = ExtResource("2_f7nj8")
PanelContainer/styles/panel = ExtResource("4_wqh56")
PluginStoreCard/base_type = &"PanelContainer"
PluginStoreCard/styles/panel = ExtResource("11_83cpl")
PowerMenu/base_type = &"PanelContainer"
PowerMenu/styles/panel = ExtResource("10_u1qhh")
ProgressBar/styles/fill = SubResource("StyleBoxFlat_4nys6")
QuickBar/base_type = &"PanelContainer"
QuickBar/styles/panel = ExtResource("14_fm0xr")
QuickBar/styles/panel = ExtResource("15_354e6")
RoundedPanel/base_type = &"PanelContainer"
RoundedPanel/styles/panel = SubResource("StyleBoxFlat_knf7f")
SearchBar/base_type = &"PanelContainer"
Expand Down
2 changes: 2 additions & 0 deletions core/systems/input/back_input_handler.gd
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
extends Node
class_name BackInputHandler

## DEPRECATED: Use [InputWatcher] with [StateUpdater] instead

## The state machine to use to update when back input is pressed
@export var state_machine: StateMachine = preload(
"res://assets/state/state_machines/global_state_machine.tres"
Expand Down
20 changes: 15 additions & 5 deletions core/systems/input/focus_group.gd
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ class_name FocusGroup
@export var focus_stack: FocusStack
## The InputEvent that will trigger focusing a parent focus group
@export var back_action := "ogui_east"
## Whether or not to wrap around focus chains
@export var wrap_focus: bool = true

@export_category("Focus Group Neighbors")
@export var focus_neighbor_bottom: FocusGroup
Expand Down Expand Up @@ -144,7 +146,7 @@ func _input(event: InputEvent) -> void:
return

# Only handle back button pressed and when the guide button is not held
if not event.is_action_pressed(back_action) or Input.is_action_pressed("ogui_guide"):
if not event.is_action_released(back_action) or Input.is_action_pressed("ogui_guide"):
return

# Only handle input if a focus stack is defined
Expand Down Expand Up @@ -421,12 +423,20 @@ func _vbox_set_focus_tree(control_children: Array[Control]) -> void:
child.focus_next = control_children[i + 1].get_path()
child.focus_neighbor_bottom = control_children[i + 1].get_path()
else:
child.focus_next = control_children[0].get_path()
child.focus_neighbor_bottom = control_children[0].get_path()
if wrap_focus:
child.focus_next = control_children[0].get_path()
child.focus_neighbor_bottom = control_children[0].get_path()
else:
child.focus_next = control_children[i].get_path()
child.focus_neighbor_bottom = control_children[i].get_path()

# Index -1
child.focus_previous = control_children[i - 1].get_path()
child.focus_neighbor_top = control_children[i - 1].get_path()
if i == 0 and not wrap_focus:
child.focus_previous = control_children[i].get_path()
child.focus_neighbor_top = control_children[i].get_path()
else:
child.focus_previous = control_children[i - 1].get_path()
child.focus_neighbor_top = control_children[i - 1].get_path()

# Block leaving the UI element unless B button is pressed.
child.focus_neighbor_left = control_children[i].get_path()
Expand Down
28 changes: 14 additions & 14 deletions core/systems/input/input_manager.gd
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ var audio_manager := load("res://core/global/audio_manager.tres") as AudioManage
var input_plumber := load("res://core/systems/input/input_plumber.tres") as InputPlumber

## State machine to use to switch menu states in response to input events.
var state_machine := (
preload("res://assets/state/state_machines/global_state_machine.tres") as StateMachine
var popup_state_machine := (
preload("res://assets/state/state_machines/popup_state_machine.tres") as StateMachine
)
var in_game_menu_state := preload("res://assets/state/states/in_game_menu.tres") as State
var main_menu_state := preload("res://assets/state/states/main_menu.tres") as State
Expand Down Expand Up @@ -188,15 +188,15 @@ func _main_menu_input(event: InputEvent) -> void:
return

# Open the main menu
var state := state_machine.current_state()
var state := popup_state_machine.current_state()
var menu_state := main_menu_state

if state == menu_state:
state_machine.pop_state()
popup_state_machine.pop_state()
elif state in [quick_bar_state, osk_state]:
state_machine.replace_state(menu_state)
popup_state_machine.replace_state(menu_state)
else:
state_machine.push_state(menu_state)
popup_state_machine.push_state(menu_state)


## Handle quick bar menu events to open the quick bar menu
Expand All @@ -205,13 +205,13 @@ func _on_quick_bar_open(event: InputEvent) -> void:
if not event.is_pressed():
return

var state := state_machine.current_state()
var state := popup_state_machine.current_state()
if state == quick_bar_state:
state_machine.pop_state()
popup_state_machine.pop_state()
elif state in [main_menu_state, in_game_menu_state, osk_state]:
state_machine.replace_state(quick_bar_state)
popup_state_machine.replace_state(quick_bar_state)
else:
state_machine.push_state(quick_bar_state)
popup_state_machine.push_state(quick_bar_state)


## Handle OSK events for bringing up the on-screen keyboard
Expand All @@ -224,13 +224,13 @@ func _osk_input(event: InputEvent) -> void:
context.type = KeyboardContext.TYPE.X11
osk.open(context)

var state := state_machine.current_state()
var state := popup_state_machine.current_state()
if state == osk_state:
state_machine.pop_state()
popup_state_machine.pop_state()
elif state in [main_menu_state, in_game_menu_state, quick_bar_state]:
state_machine.replace_state(osk_state)
popup_state_machine.replace_state(osk_state)
else:
state_machine.push_state(osk_state)
popup_state_machine.push_state(osk_state)


## Handle audio input events such as mute, volume up, and volume down
Expand Down
25 changes: 25 additions & 0 deletions core/systems/input/input_watcher.gd
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,52 @@ signal input_released
## If true, consumes the event, marking it as handled so no other nodes
## try to handle this input event.
@export var stop_propagation: bool
## Always process inputs or only when parent node is visible
@export_flags("When visible", "When child focused") var process_input_mode: int = 3

## Name of the input action in the InputMap to watch for
var action: String

@onready var logger := Log.get_logger("InputWatcher", Log.LEVEL.INFO)


func _ready() -> void:
if action.is_empty():
set_process_input(false)

if process_input_mode == 0:
return

# Only process input if the parent node is visible
var parent := get_parent()
if parent is Control:
var control := parent as Control
var on_visibility := func():
set_process_input(control.is_visible_in_tree())
control.visibility_changed.connect(on_visibility)
set_process_input(control.is_visible_in_tree())


func _input(event: InputEvent) -> void:
if not event.is_action(action):
return

# Only process input if a child node has focus
if (process_input_mode & 2):
var focus_owner := get_viewport().gui_get_focus_owner()
var parent := get_parent()
if not parent.is_ancestor_of(focus_owner):
return

if event.is_pressed():
input_pressed.emit()
elif event.is_released():
input_released.emit()

# Stop the event from propagating
if stop_propagation:
var parent := get_parent()
logger.debug("Consuming input event '{action}' for node {n}".format({"action": action, "n": str(parent)}))
get_viewport().set_input_as_handled()


Expand Down
29 changes: 29 additions & 0 deletions core/systems/state/state.gd
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,38 @@
extends Resource
class_name State

## Object for tracking the current state of a [StateMachine]
##
## A [State] represents some state of the application, such as the currently
## focused menu. Together with a [StateMachine], a [State] can be used to listen
## for signals whenever the state of the application changes.
##
## A [State] takes advantage of the fact that Godot resources are globally
## unique. This allows you to load a [State] resource from anywhere in the project
## to subscribe to state changes.

## Emitted whenever a [StateMachine] has set this [State] as the current state.
## The "from" [State] will be populated with the last [State] the [StateMachine]
## was in.
signal state_entered(from: State)
## Emitted whenever a [StateMachine] has left this [State]. The "to" [State] will
## be populated with the new current [State] of the [StateMachine].
signal state_exited(to: State)
## Emitted whenever a [StateMachine] has added this [State] to its state stack.
signal state_added
## Emitted whenever a [StateMachine] has removed this [State] from its state stack.
signal state_removed
## Emitted whenever a [StateMachine] calls "refresh" on the current [State]
signal refreshed

## Optional human-readable name for the state
@export var name: String
## DEPRECATED: Use 'set_meta()' or 'get_meta()' instead
@export var data: Dictionary


func _to_string() -> String:
if not name.is_empty():
return "<State:{name}>".format({"name": name})
return "<State:{rid}>".format({"rid": get_rid()})

Loading
Loading