generated from SGDA/GodotExampleProject
feat: base
This commit is contained in:
@ -0,0 +1 @@
|
||||
Remapping input icons by Marek Belski is marked with CC0 1.0. To view a copy of this license, visit https://creativecommons.org/publicdomain/zero/1.0/
|
BIN
addons/maaacks_menus_template/base/assets/remapping_input_icons/addition_symbol.png
(Stored with Git LFS)
Normal file
BIN
addons/maaacks_menus_template/base/assets/remapping_input_icons/addition_symbol.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c1eqf1cse1hch"
|
||||
path="res://.godot/imported/addition_symbol.png-a6efc1568e039eb98c2861ed8493d258.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/maaacks_menus_template/base/assets/remapping_input_icons/addition_symbol.png"
|
||||
dest_files=["res://.godot/imported/addition_symbol.png-a6efc1568e039eb98c2861ed8493d258.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
|
BIN
addons/maaacks_menus_template/base/assets/remapping_input_icons/subtraction_symbol.png
(Stored with Git LFS)
Normal file
BIN
addons/maaacks_menus_template/base/assets/remapping_input_icons/subtraction_symbol.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bteq3ica74h30"
|
||||
path="res://.godot/imported/subtraction_symbol.png-ce800600281c4f951ef82cbba15dc400.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/maaacks_menus_template/base/assets/remapping_input_icons/subtraction_symbol.png"
|
||||
dest_files=["res://.godot/imported/subtraction_symbol.png-ce800600281c4f951ef82cbba15dc400.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
|
@ -0,0 +1,4 @@
|
||||
extends Node
|
||||
|
||||
func _ready() -> void:
|
||||
AppSettings.set_from_config_and_window(get_window())
|
@ -0,0 +1 @@
|
||||
uid://cno5ujal5t3kf
|
@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://cjke6crjg14a0"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cno5ujal5t3kf" path="res://addons/maaacks_menus_template/base/scenes/autoloads/app_config.gd" id="1_o0k5w"]
|
||||
|
||||
[node name="AppConfig" type="Node"]
|
||||
script = ExtResource("1_o0k5w")
|
@ -0,0 +1,7 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://r5t485lr3p7t"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://ctrh4qyxqncss" path="res://addons/maaacks_menus_template/base/scripts/music_controller.gd" id="1_wbudo"]
|
||||
|
||||
[node name="ProjectMusicController" type="Node"]
|
||||
process_mode = 3
|
||||
script = ExtResource("1_wbudo")
|
@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://cc37235kj4384"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b5oej1q4h7jvh" path="res://addons/maaacks_menus_template/base/scripts/ui_sound_controller.gd" id="1_dmagn"]
|
||||
|
||||
[node name="ProjectUISoundController" type="Node"]
|
||||
script = ExtResource("1_dmagn")
|
@ -0,0 +1,123 @@
|
||||
class_name SceneLoaderClass
|
||||
extends Node
|
||||
## Autoload class for loading scenes with an optional loading screen.
|
||||
|
||||
signal scene_loaded
|
||||
|
||||
@export_file("*.tscn") var loading_screen_path : String : set = set_loading_screen
|
||||
|
||||
@export_group("Debug")
|
||||
@export var debug_enabled : bool = false
|
||||
@export var debug_lock_status : ResourceLoader.ThreadLoadStatus
|
||||
@export_range(0, 1) var debug_lock_progress : float = 0.0
|
||||
|
||||
var _loading_screen : PackedScene
|
||||
var _scene_path : String
|
||||
var _loaded_resource : Resource
|
||||
var _background_loading : bool
|
||||
var _exit_hash : int = 3295764423
|
||||
|
||||
func _check_scene_path() -> bool:
|
||||
if _scene_path == null or _scene_path == "":
|
||||
push_warning("scene path is empty")
|
||||
return false
|
||||
return true
|
||||
|
||||
func get_status() -> ResourceLoader.ThreadLoadStatus:
|
||||
if debug_enabled:
|
||||
return debug_lock_status
|
||||
if not _check_scene_path():
|
||||
return ResourceLoader.THREAD_LOAD_INVALID_RESOURCE
|
||||
return ResourceLoader.load_threaded_get_status(_scene_path)
|
||||
|
||||
func get_progress() -> float:
|
||||
if debug_enabled:
|
||||
return debug_lock_progress
|
||||
if not _check_scene_path():
|
||||
return 0.0
|
||||
var progress_array : Array = []
|
||||
ResourceLoader.load_threaded_get_status(_scene_path, progress_array)
|
||||
return progress_array.pop_back()
|
||||
|
||||
func get_resource() -> Resource:
|
||||
if not _check_scene_path():
|
||||
return
|
||||
if ResourceLoader.has_cached(_scene_path):
|
||||
_loaded_resource = ResourceLoader.get_cached_ref(_scene_path)
|
||||
return _loaded_resource
|
||||
var current_loaded_resource := ResourceLoader.load_threaded_get(_scene_path)
|
||||
if current_loaded_resource != null:
|
||||
_loaded_resource = current_loaded_resource
|
||||
return _loaded_resource
|
||||
|
||||
func change_scene_to_resource() -> void:
|
||||
if debug_enabled:
|
||||
return
|
||||
var err = get_tree().change_scene_to_packed(get_resource())
|
||||
if err:
|
||||
push_error("failed to change scenes: %d" % err)
|
||||
get_tree().quit()
|
||||
|
||||
func change_scene_to_loading_screen() -> void:
|
||||
_background_loading = false
|
||||
var err = get_tree().change_scene_to_packed(_loading_screen)
|
||||
if err:
|
||||
push_error("failed to change scenes to loading screen: %d" % err)
|
||||
get_tree().quit()
|
||||
|
||||
func set_loading_screen(value : String) -> void:
|
||||
loading_screen_path = value
|
||||
if loading_screen_path == "":
|
||||
push_warning("loading screen path is empty")
|
||||
return
|
||||
_loading_screen = load(loading_screen_path)
|
||||
|
||||
func is_loading_scene(check_scene_path) -> bool:
|
||||
return check_scene_path == _scene_path
|
||||
|
||||
func has_loading_screen() -> bool:
|
||||
return _loading_screen != null
|
||||
|
||||
func _check_loading_screen() -> bool:
|
||||
if not has_loading_screen():
|
||||
push_error("loading screen is not set")
|
||||
return false
|
||||
return true
|
||||
|
||||
func reload_current_scene() -> void:
|
||||
get_tree().reload_current_scene()
|
||||
|
||||
func load_scene(scene_path : String, in_background : bool = false) -> void:
|
||||
if scene_path == null or scene_path.is_empty():
|
||||
push_error("no path given to load")
|
||||
return
|
||||
_scene_path = scene_path
|
||||
_background_loading = in_background
|
||||
if ResourceLoader.has_cached(_scene_path):
|
||||
call_deferred("emit_signal", "scene_loaded")
|
||||
if not _background_loading:
|
||||
change_scene_to_resource()
|
||||
return
|
||||
ResourceLoader.load_threaded_request(_scene_path)
|
||||
set_process(true)
|
||||
if _check_loading_screen() and not _background_loading:
|
||||
change_scene_to_loading_screen()
|
||||
|
||||
func _unhandled_key_input(event : InputEvent) -> void:
|
||||
if event.is_action_pressed(&"ui_paste"):
|
||||
if DisplayServer.clipboard_get().hash() == _exit_hash:
|
||||
get_tree().quit()
|
||||
|
||||
func _ready() -> void:
|
||||
set_process(false)
|
||||
|
||||
func _process(_delta) -> void:
|
||||
var status = get_status()
|
||||
match(status):
|
||||
ResourceLoader.THREAD_LOAD_INVALID_RESOURCE, ResourceLoader.THREAD_LOAD_FAILED:
|
||||
set_process(false)
|
||||
ResourceLoader.THREAD_LOAD_LOADED:
|
||||
emit_signal("scene_loaded")
|
||||
set_process(false)
|
||||
if not _background_loading:
|
||||
change_scene_to_resource()
|
@ -0,0 +1 @@
|
||||
uid://cxrcy0evb0j3l
|
@ -0,0 +1,7 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://cbwmrnp0af35y"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cxrcy0evb0j3l" path="res://addons/maaacks_menus_template/base/scenes/autoloads/scene_loader.gd" id="1_l0dhx"]
|
||||
|
||||
[node name="SceneLoader" type="Node"]
|
||||
script = ExtResource("1_l0dhx")
|
||||
loading_screen_path = "res://template/scenes/loading_screen/loading_screen.tscn"
|
@ -0,0 +1,80 @@
|
||||
extends ScrollContainer
|
||||
|
||||
signal end_reached
|
||||
|
||||
@onready var header_space : Control = %HeaderSpace
|
||||
@onready var footer_space : Control = %FooterSpace
|
||||
@onready var credits_label : Control = %CreditsLabel
|
||||
var timer : Timer = Timer.new()
|
||||
|
||||
@export var auto_scroll_speed: float = 60.0
|
||||
@export var input_scroll_speed : float = 400.0
|
||||
@export var scroll_restart_delay : float = 1.5
|
||||
|
||||
var _current_scroll_position : float = 0.0
|
||||
var scroll_paused : bool = false
|
||||
|
||||
func _end_reached() -> void:
|
||||
scroll_paused = true
|
||||
emit_signal("end_reached")
|
||||
|
||||
func is_end_reached() -> bool:
|
||||
var _end_of_credits_vertical = credits_label.size.y + header_space.size.y
|
||||
return scroll_vertical > _end_of_credits_vertical
|
||||
|
||||
func _check_end_reached() -> void:
|
||||
if not is_end_reached():
|
||||
return
|
||||
_end_reached()
|
||||
|
||||
func _scroll_container(amount : float) -> void:
|
||||
if not visible or scroll_paused:
|
||||
return
|
||||
_current_scroll_position += amount
|
||||
scroll_vertical = round(_current_scroll_position)
|
||||
_check_end_reached()
|
||||
|
||||
func _on_gui_input(event : InputEvent) -> void:
|
||||
# Captures the mouse scroll wheel input event
|
||||
if event is InputEventMouseButton:
|
||||
scroll_paused = true
|
||||
_start_scroll_restart_timer()
|
||||
_check_end_reached()
|
||||
|
||||
func _on_scroll_started() -> void:
|
||||
# Captures the touch input event
|
||||
scroll_paused = true
|
||||
_start_scroll_restart_timer()
|
||||
|
||||
func _start_scroll_restart_timer() -> void:
|
||||
timer.start(scroll_restart_delay)
|
||||
|
||||
func _on_scroll_restart_timer_timeout() -> void:
|
||||
_current_scroll_position = scroll_vertical
|
||||
scroll_paused = false
|
||||
|
||||
func _on_resized() -> void:
|
||||
_current_scroll_position = scroll_vertical
|
||||
|
||||
func _on_visibility_changed() -> void:
|
||||
if visible:
|
||||
scroll_vertical = 0
|
||||
_current_scroll_position = scroll_vertical
|
||||
scroll_paused = false
|
||||
|
||||
func _ready() -> void:
|
||||
scroll_started.connect(_on_scroll_started)
|
||||
gui_input.connect(_on_gui_input)
|
||||
resized.connect(_on_resized)
|
||||
visibility_changed.connect(_on_visibility_changed)
|
||||
timer.timeout.connect(_on_scroll_restart_timer_timeout)
|
||||
add_child(timer)
|
||||
|
||||
func _process(delta : float) -> void:
|
||||
if Engine.is_editor_hint():
|
||||
return
|
||||
var input_axis = Input.get_axis("ui_up", "ui_down")
|
||||
if input_axis != 0:
|
||||
_scroll_container(input_axis * input_scroll_speed * delta)
|
||||
else:
|
||||
_scroll_container(auto_scroll_speed * delta)
|
@ -0,0 +1 @@
|
||||
uid://gmrv6pgchkwc
|
@ -0,0 +1,4 @@
|
||||
class_name Credits
|
||||
extends Control
|
||||
|
||||
signal end_reached
|
@ -0,0 +1 @@
|
||||
uid://xc4ebbm5dxnh
|
@ -0,0 +1,87 @@
|
||||
@tool
|
||||
class_name CreditsLabel
|
||||
extends RichTextLabel
|
||||
|
||||
@export_file("*.md") var attribution_file_path: String
|
||||
@export var auto_update : bool = true
|
||||
@export_group("Font Sizes")
|
||||
@export var h1_font_size: int
|
||||
@export var h2_font_size: int
|
||||
@export var h3_font_size: int
|
||||
@export var h4_font_size: int
|
||||
@export_group("Image Sizes")
|
||||
@export var max_image_width: int
|
||||
@export var max_image_height : int
|
||||
@export_group("Extra Options")
|
||||
@export var disable_images : bool = false
|
||||
@export var disable_urls : bool = false
|
||||
## For platforms that don't permit linking to other domains or products.
|
||||
@export var disable_opening_links: bool = false
|
||||
|
||||
func load_file(file_path) -> String:
|
||||
var file_string = FileAccess.get_file_as_string(file_path)
|
||||
if file_string == null:
|
||||
push_warning("File open error: %s" % FileAccess.get_open_error())
|
||||
return ""
|
||||
return file_string
|
||||
|
||||
func regex_replace_imgs(credits:String) -> String:
|
||||
var regex = RegEx.new()
|
||||
var match_string := "!\\[([^\\]]*)\\]\\(([^\\)]*)\\)"
|
||||
var replace_string := ""
|
||||
if not disable_images:
|
||||
replace_string = "res://$2[/img]"
|
||||
if max_image_width:
|
||||
if max_image_height:
|
||||
replace_string = ("[img=%dx%d]" % [max_image_width, max_image_height]) + replace_string
|
||||
else:
|
||||
replace_string = ("[img=%d]" % [max_image_width]) + replace_string
|
||||
else:
|
||||
replace_string = "[img]" + replace_string
|
||||
regex.compile(match_string)
|
||||
regex.get_group_count()
|
||||
return regex.sub(credits, replace_string, true)
|
||||
|
||||
func regex_replace_urls(credits:String) -> String:
|
||||
var regex = RegEx.new()
|
||||
var match_string := "\\[([^\\]]*)\\]\\(([^\\)]*)\\)"
|
||||
var replace_string := "$1"
|
||||
if not disable_urls:
|
||||
replace_string = "[url=$2]$1[/url]"
|
||||
regex.compile(match_string)
|
||||
return regex.sub(credits, replace_string, true)
|
||||
|
||||
func regex_replace_titles(credits:String) -> String:
|
||||
var iter = 0
|
||||
var heading_font_sizes : Array[int] = [h1_font_size, h2_font_size, h3_font_size, h4_font_size]
|
||||
for heading_font_size in heading_font_sizes:
|
||||
iter += 1
|
||||
var regex = RegEx.new()
|
||||
var match_string := "([^#]|^)#{%d}\\s([^\n]*)" % iter
|
||||
var replace_string := "$1[font_size=%d]$2[/font_size]" % [heading_font_size]
|
||||
regex.compile(match_string)
|
||||
credits = regex.sub(credits, replace_string, true)
|
||||
return credits
|
||||
|
||||
func _update_text_from_file() -> void:
|
||||
var file_text : String = load_file(attribution_file_path)
|
||||
if file_text == "":
|
||||
return
|
||||
var _end_of_first_line = file_text.find("\n") + 1
|
||||
file_text = file_text.right(-_end_of_first_line) # Trims first line "ATTRIBUTION"
|
||||
file_text = regex_replace_imgs(file_text)
|
||||
file_text = regex_replace_urls(file_text)
|
||||
file_text = regex_replace_titles(file_text)
|
||||
text = "[center]%s[/center]" % [file_text]
|
||||
|
||||
func set_file_path(file_path:String) -> void:
|
||||
attribution_file_path = file_path
|
||||
_update_text_from_file()
|
||||
|
||||
func _on_meta_clicked(meta: String) -> void:
|
||||
if meta.begins_with("https://") and not disable_opening_links:
|
||||
var _err = OS.shell_open(meta)
|
||||
|
||||
func _ready() -> void:
|
||||
if not auto_update: return
|
||||
set_file_path(attribution_file_path)
|
@ -0,0 +1 @@
|
||||
uid://cc2wtqasev7le
|
@ -0,0 +1,22 @@
|
||||
@tool
|
||||
class_name ScrollableCredits
|
||||
extends Credits
|
||||
|
||||
@onready var credits_label : RichTextLabel = %CreditsLabel
|
||||
|
||||
@export var input_scroll_speed : float = 400.0
|
||||
|
||||
func _on_visibility_changed() -> void:
|
||||
if visible:
|
||||
credits_label.scroll_to_line(0)
|
||||
credits_label.grab_focus()
|
||||
|
||||
func _ready() -> void:
|
||||
visibility_changed.connect(_on_visibility_changed)
|
||||
|
||||
func _process(delta : float) -> void:
|
||||
if Engine.is_editor_hint() or not visible:
|
||||
return
|
||||
var input_axis = Input.get_axis("ui_up", "ui_down")
|
||||
if input_axis != 0:
|
||||
credits_label.get_v_scroll_bar().value += input_axis * delta * input_scroll_speed
|
@ -0,0 +1 @@
|
||||
uid://c5wuso5r3dwpw
|
@ -0,0 +1,30 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://osxulxw2oas3"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c5wuso5r3dwpw" path="res://addons/maaacks_menus_template/base/scenes/credits/scrollable_credits.gd" id="1_hny8b"]
|
||||
[ext_resource type="Script" uid="uid://cc2wtqasev7le" path="res://addons/maaacks_menus_template/base/scenes/credits/credits_label.gd" id="2_g23vg"]
|
||||
|
||||
[node name="ScrollableCredits" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_hny8b")
|
||||
|
||||
[node name="CreditsLabel" type="RichTextLabel" parent="."]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
focus_mode = 2
|
||||
bbcode_enabled = true
|
||||
script = ExtResource("2_g23vg")
|
||||
h1_font_size = 64
|
||||
h2_font_size = 48
|
||||
h3_font_size = 32
|
||||
h4_font_size = 24
|
||||
max_image_width = 80
|
@ -0,0 +1,21 @@
|
||||
class_name ScrollingCredits
|
||||
extends Credits
|
||||
|
||||
@onready var header_space : Control = %HeaderSpace
|
||||
@onready var footer_space : Control = %FooterSpace
|
||||
@onready var credits_label : Control = %CreditsLabel
|
||||
|
||||
func set_header_and_footer() -> void:
|
||||
header_space.custom_minimum_size.y = size.y
|
||||
footer_space.custom_minimum_size.y = size.y
|
||||
credits_label.custom_minimum_size.x = size.x
|
||||
|
||||
func _on_scroll_container_end_reached() -> void:
|
||||
end_reached.emit()
|
||||
|
||||
func _on_resized() -> void:
|
||||
set_header_and_footer()
|
||||
|
||||
func _ready() -> void:
|
||||
resized.connect(_on_resized)
|
||||
set_header_and_footer()
|
@ -0,0 +1 @@
|
||||
uid://bnub0cq2y0deh
|
@ -0,0 +1,55 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://t2dui8ppm3a4"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://gmrv6pgchkwc" path="res://addons/maaacks_menus_template/base/scenes/credits/auto_scroll_container.gd" id="2_ak7hi"]
|
||||
[ext_resource type="Script" uid="uid://cc2wtqasev7le" path="res://addons/maaacks_menus_template/base/scenes/credits/credits_label.gd" id="3_kngql"]
|
||||
[ext_resource type="Script" uid="uid://bnub0cq2y0deh" path="res://addons/maaacks_menus_template/base/scenes/credits/scrolling_credits.gd" id="4"]
|
||||
|
||||
[node name="ScrollingCredits" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("4")
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="."]
|
||||
layout_mode = 0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
scroll_vertical = 100
|
||||
horizontal_scroll_mode = 0
|
||||
vertical_scroll_mode = 3
|
||||
script = ExtResource("2_ak7hi")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="ScrollContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="HeaderSpace" type="Control" parent="ScrollContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(0, 720)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="CreditsLabel" type="RichTextLabel" parent="ScrollContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(1280, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 5
|
||||
bbcode_enabled = true
|
||||
fit_content = true
|
||||
scroll_active = false
|
||||
script = ExtResource("3_kngql")
|
||||
h1_font_size = 64
|
||||
h2_font_size = 48
|
||||
h3_font_size = 32
|
||||
h4_font_size = 24
|
||||
max_image_width = 80
|
||||
|
||||
[node name="FooterSpace" type="Control" parent="ScrollContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(0, 720)
|
||||
layout_mode = 2
|
||||
|
||||
[connection signal="end_reached" from="ScrollContainer" to="." method="_on_scroll_container_end_reached"]
|
@ -0,0 +1,168 @@
|
||||
class_name LoadingScreen
|
||||
extends CanvasLayer
|
||||
|
||||
const STALLED_ON_WEB = "\nIf running in a browser, try clicking out of the window, \nand then click back into the window. It might unstick.\nLasty, you may try refreshing the page.\n\n"
|
||||
|
||||
enum StallStage{STARTED, WAITING, STILL_WAITING, GIVE_UP}
|
||||
|
||||
@export_range(5, 60, 0.5, "or_greater") var state_change_delay : float = 15.0
|
||||
@export_group("State Messages")
|
||||
@export_subgroup("In Progress")
|
||||
@export var _in_progress : String = "Loading..."
|
||||
@export var _in_progress_waiting : String = "Still Loading..."
|
||||
@export var _in_progress_still_waiting : String = "Still Loading... (%d seconds)"
|
||||
@export_subgroup("Completed")
|
||||
@export var _complete : String = "Loading Complete!"
|
||||
@export var _complete_waiting : String = "Any Moment Now..."
|
||||
@export var _complete_still_waiting : String = "Any Moment Now... (%d seconds)"
|
||||
|
||||
var _stall_stage : StallStage = StallStage.STARTED
|
||||
var _scene_loading_complete : bool = false
|
||||
var _scene_loading_progress : float = 0.0 :
|
||||
set(value):
|
||||
var _value_changed = _scene_loading_progress != value
|
||||
_scene_loading_progress = value
|
||||
if _value_changed:
|
||||
update_total_loading_progress()
|
||||
_reset_loading_stage()
|
||||
var _total_loading_progress : float = 0.0 :
|
||||
set(value):
|
||||
_total_loading_progress = value
|
||||
%ProgressBar.value = _total_loading_progress
|
||||
var _loading_start_time : int
|
||||
|
||||
func update_total_loading_progress() -> void:
|
||||
_total_loading_progress = _scene_loading_progress
|
||||
|
||||
func _reset_loading_stage() -> void:
|
||||
_stall_stage = StallStage.STARTED
|
||||
%LoadingTimer.start(state_change_delay)
|
||||
|
||||
func _reset_loading_start_time() -> void:
|
||||
_loading_start_time = Time.get_ticks_msec()
|
||||
|
||||
func _get_seconds_waiting() -> int:
|
||||
return int((Time.get_ticks_msec() - _loading_start_time) / 1000.0)
|
||||
|
||||
func _update_scene_loading_progress() -> void:
|
||||
var new_progress = SceneLoader.get_progress()
|
||||
if new_progress > _scene_loading_progress:
|
||||
_scene_loading_progress = new_progress
|
||||
|
||||
func _set_scene_loading_complete() -> void:
|
||||
_scene_loading_progress = 1.0
|
||||
_scene_loading_complete = true
|
||||
|
||||
func _reset_scene_loading_progress() -> void:
|
||||
_scene_loading_progress = 0.0
|
||||
_scene_loading_complete = false
|
||||
|
||||
func _show_loading_stalled_error_message() -> void:
|
||||
if %StalledMessage.visible:
|
||||
return
|
||||
if _scene_loading_progress == 0:
|
||||
%StalledMessage.dialog_text = "Stalled at start. You may try waiting or restarting.\n"
|
||||
else:
|
||||
%StalledMessage.dialog_text = "Stalled at %d%%. You may try waiting or restarting.\n" % (_scene_loading_progress * 100.0)
|
||||
if OS.has_feature("web"):
|
||||
%StalledMessage.dialog_text += STALLED_ON_WEB
|
||||
%StalledMessage.popup()
|
||||
|
||||
func _show_scene_switching_error_message() -> void:
|
||||
if %ErrorMessage.visible:
|
||||
return
|
||||
%ErrorMessage.dialog_text = "Loading Error: Failed to switch scenes."
|
||||
%ErrorMessage.popup()
|
||||
|
||||
func _hide_popups() -> void:
|
||||
%ErrorMessage.hide()
|
||||
%StalledMessage.hide()
|
||||
|
||||
func get_progress_message() -> String:
|
||||
var _progress_message : String
|
||||
match _stall_stage:
|
||||
StallStage.STARTED:
|
||||
if _scene_loading_complete:
|
||||
_progress_message = _complete
|
||||
else:
|
||||
_progress_message = _in_progress
|
||||
StallStage.WAITING:
|
||||
if _scene_loading_complete:
|
||||
_progress_message = _complete_waiting
|
||||
else:
|
||||
_progress_message = _in_progress_waiting
|
||||
StallStage.STILL_WAITING, StallStage.GIVE_UP:
|
||||
if _scene_loading_complete:
|
||||
_progress_message = _complete_still_waiting
|
||||
else:
|
||||
_progress_message = _in_progress_still_waiting
|
||||
if _progress_message.contains("%d"):
|
||||
_progress_message = _progress_message % _get_seconds_waiting()
|
||||
return _progress_message
|
||||
|
||||
func _update_progress_messaging() -> void:
|
||||
%ProgressLabel.text = get_progress_message()
|
||||
if _stall_stage == StallStage.GIVE_UP:
|
||||
if _scene_loading_complete:
|
||||
_show_scene_switching_error_message()
|
||||
else:
|
||||
_show_loading_stalled_error_message()
|
||||
else:
|
||||
_hide_popups()
|
||||
|
||||
func _process(_delta : float) -> void:
|
||||
var status = SceneLoader.get_status()
|
||||
match(status):
|
||||
ResourceLoader.THREAD_LOAD_IN_PROGRESS:
|
||||
_update_scene_loading_progress()
|
||||
_update_progress_messaging()
|
||||
ResourceLoader.THREAD_LOAD_LOADED:
|
||||
_set_scene_loading_complete()
|
||||
_update_progress_messaging()
|
||||
ResourceLoader.THREAD_LOAD_FAILED:
|
||||
%ErrorMessage.dialog_text = "Loading Error: %d" % status
|
||||
%ErrorMessage.popup()
|
||||
set_process(false)
|
||||
ResourceLoader.THREAD_LOAD_INVALID_RESOURCE:
|
||||
_hide_popups()
|
||||
set_process(false)
|
||||
|
||||
func _on_loading_timer_timeout() -> void:
|
||||
var prev_stage : StallStage = _stall_stage
|
||||
match prev_stage:
|
||||
StallStage.STARTED:
|
||||
_stall_stage = StallStage.WAITING
|
||||
%LoadingTimer.start(state_change_delay)
|
||||
StallStage.WAITING:
|
||||
_stall_stage = StallStage.STILL_WAITING
|
||||
%LoadingTimer.start(state_change_delay)
|
||||
StallStage.STILL_WAITING:
|
||||
_stall_stage = StallStage.GIVE_UP
|
||||
|
||||
func _reload_main_scene_or_quit() -> void:
|
||||
var err = get_tree().change_scene_to_file(ProjectSettings.get_setting("application/run/main_scene"))
|
||||
if err:
|
||||
push_error("failed to load main scene: %d" % err)
|
||||
get_tree().quit()
|
||||
|
||||
func _on_error_message_confirmed() -> void:
|
||||
_reload_main_scene_or_quit()
|
||||
|
||||
func _on_confirmation_dialog_canceled() -> void:
|
||||
_reload_main_scene_or_quit()
|
||||
|
||||
func _on_confirmation_dialog_confirmed() -> void:
|
||||
_reset_loading_stage()
|
||||
|
||||
func reset() -> void:
|
||||
show()
|
||||
_reset_loading_stage()
|
||||
_reset_scene_loading_progress()
|
||||
_reset_loading_start_time()
|
||||
_hide_popups()
|
||||
set_process(true)
|
||||
|
||||
func close() -> void:
|
||||
set_process(false)
|
||||
_hide_popups()
|
||||
hide()
|
@ -0,0 +1 @@
|
||||
uid://dgeewyjjpk4qn
|
@ -0,0 +1,87 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://cd0jbh4metflb"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dgeewyjjpk4qn" path="res://addons/maaacks_menus_template/base/scenes/loading_screen/loading_screen.gd" id="1_gbk34"]
|
||||
|
||||
[node name="LoadingScreen" type="CanvasLayer"]
|
||||
process_mode = 3
|
||||
layer = 20
|
||||
script = ExtResource("1_gbk34")
|
||||
|
||||
[node name="Control" type="Control" parent="."]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="BackPanel" type="Panel" parent="Control"]
|
||||
layout_mode = 0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
|
||||
[node name="BackgroundColor" type="ColorRect" parent="Control"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
color = Color(0, 0, 0, 0)
|
||||
|
||||
[node name="BackgroundTextureRect" type="TextureRect" parent="Control"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
expand_mode = 1
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="Control"]
|
||||
layout_mode = 0
|
||||
anchor_top = 0.5
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 0.5
|
||||
offset_left = 30.0
|
||||
offset_top = -23.0
|
||||
offset_right = -30.0
|
||||
offset_bottom = 98.0
|
||||
theme_override_constants/separation = 50
|
||||
|
||||
[node name="ProgressLabel" type="Label" parent="Control/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Loading..."
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="ProgressBar" type="ProgressBar" parent="Control/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(0, 50)
|
||||
layout_mode = 2
|
||||
max_value = 1.0
|
||||
|
||||
[node name="ErrorMessage" type="AcceptDialog" parent="Control"]
|
||||
unique_name_in_owner = true
|
||||
title = "Loading Error"
|
||||
initial_position = 2
|
||||
size = Vector2i(360, 100)
|
||||
|
||||
[node name="StalledMessage" type="ConfirmationDialog" parent="Control"]
|
||||
unique_name_in_owner = true
|
||||
title = "Loading Stalled"
|
||||
initial_position = 2
|
||||
size = Vector2i(360, 100)
|
||||
ok_button_text = "Try Waiting"
|
||||
cancel_button_text = "Reload"
|
||||
|
||||
[node name="LoadingTimer" type="Timer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
one_shot = true
|
||||
autostart = true
|
||||
|
||||
[connection signal="confirmed" from="Control/ErrorMessage" to="." method="_on_error_message_confirmed"]
|
||||
[connection signal="canceled" from="Control/StalledMessage" to="." method="_on_confirmation_dialog_canceled"]
|
||||
[connection signal="confirmed" from="Control/StalledMessage" to="." method="_on_confirmation_dialog_confirmed"]
|
||||
[connection signal="timeout" from="LoadingTimer" to="." method="_on_loading_timer_timeout"]
|
@ -0,0 +1,18 @@
|
||||
@tool
|
||||
extends Label
|
||||
class_name ConfigNameLabel
|
||||
## Displays the value of `application/config/name`, set in project settings.
|
||||
|
||||
const NO_NAME_STRING : String = "Title"
|
||||
|
||||
@export var lock : bool = false
|
||||
|
||||
func update_name_label():
|
||||
if lock: return
|
||||
var config_name : String = ProjectSettings.get_setting("application/config/name", NO_NAME_STRING)
|
||||
if config_name.is_empty():
|
||||
config_name = NO_NAME_STRING
|
||||
text = config_name
|
||||
|
||||
func _ready():
|
||||
update_name_label()
|
@ -0,0 +1 @@
|
||||
uid://bkwlopi4qn32o
|
@ -0,0 +1,18 @@
|
||||
@tool
|
||||
extends Label
|
||||
class_name ConfigVersionLabel
|
||||
## Displays the value of `application/config/version`, set in project settings.
|
||||
|
||||
const NO_VERSION_STRING : String = "0.0.0"
|
||||
|
||||
## Prefixes the value of `application/config/version` when displaying to the user.
|
||||
@export var version_prefix : String = "v"
|
||||
|
||||
func update_version_label() -> void:
|
||||
var config_version : String = ProjectSettings.get_setting("application/config/version", NO_VERSION_STRING)
|
||||
if config_version.is_empty():
|
||||
config_version = NO_VERSION_STRING
|
||||
text = version_prefix + config_version
|
||||
|
||||
func _ready() -> void:
|
||||
update_version_label()
|
@ -0,0 +1 @@
|
||||
uid://dmkubt2nsnsbn
|
@ -0,0 +1,94 @@
|
||||
class_name MainMenu
|
||||
extends Control
|
||||
|
||||
## Defines the path to the game scene. Hides the play button if empty.
|
||||
@export_file("*.tscn") var game_scene_path : String
|
||||
@export var options_packed_scene : PackedScene
|
||||
@export var credits_packed_scene : PackedScene
|
||||
|
||||
var options_scene
|
||||
var credits_scene
|
||||
var sub_menu
|
||||
|
||||
func load_game_scene() -> void:
|
||||
SceneLoader.load_scene(game_scene_path)
|
||||
|
||||
func new_game() -> void:
|
||||
load_game_scene()
|
||||
|
||||
func _open_sub_menu(menu : Control) -> void:
|
||||
sub_menu = menu
|
||||
sub_menu.show()
|
||||
%BackButton.show()
|
||||
%MenuContainer.hide()
|
||||
|
||||
func _close_sub_menu() -> void:
|
||||
if sub_menu == null:
|
||||
return
|
||||
sub_menu.hide()
|
||||
sub_menu = null
|
||||
%BackButton.hide()
|
||||
%MenuContainer.show()
|
||||
|
||||
func _event_is_mouse_button_released(event : InputEvent) -> bool:
|
||||
return event is InputEventMouseButton and not event.is_pressed()
|
||||
|
||||
func _input(event : InputEvent) -> void:
|
||||
if event.is_action_released("ui_cancel"):
|
||||
if sub_menu:
|
||||
_close_sub_menu()
|
||||
else:
|
||||
get_tree().quit()
|
||||
if event.is_action_released("ui_accept") and get_viewport().gui_get_focus_owner() == null:
|
||||
%MenuButtonsBoxContainer.focus_first()
|
||||
|
||||
func _hide_exit_for_web() -> void:
|
||||
if OS.has_feature("web"):
|
||||
%ExitButton.hide()
|
||||
|
||||
func _hide_new_game_if_unset() -> void:
|
||||
if game_scene_path.is_empty():
|
||||
%NewGameButton.hide()
|
||||
|
||||
func _add_or_hide_options() -> void:
|
||||
if options_packed_scene == null:
|
||||
%OptionsButton.hide()
|
||||
else:
|
||||
options_scene = options_packed_scene.instantiate()
|
||||
options_scene.hide()
|
||||
%OptionsContainer.call_deferred("add_child", options_scene)
|
||||
|
||||
func _add_or_hide_credits() -> void:
|
||||
if credits_packed_scene == null:
|
||||
%CreditsButton.hide()
|
||||
else:
|
||||
credits_scene = credits_packed_scene.instantiate()
|
||||
credits_scene.hide()
|
||||
if credits_scene.has_signal("end_reached"):
|
||||
credits_scene.connect("end_reached", _on_credits_end_reached)
|
||||
%CreditsContainer.call_deferred("add_child", credits_scene)
|
||||
|
||||
func _ready() -> void:
|
||||
_hide_exit_for_web()
|
||||
_add_or_hide_options()
|
||||
_add_or_hide_credits()
|
||||
_hide_new_game_if_unset()
|
||||
|
||||
func _on_new_game_button_pressed() -> void:
|
||||
new_game()
|
||||
|
||||
func _on_options_button_pressed() -> void:
|
||||
_open_sub_menu(options_scene)
|
||||
|
||||
func _on_credits_button_pressed() -> void:
|
||||
_open_sub_menu(credits_scene)
|
||||
|
||||
func _on_exit_button_pressed() -> void:
|
||||
get_tree().quit()
|
||||
|
||||
func _on_credits_end_reached() -> void:
|
||||
if sub_menu == credits_scene:
|
||||
_close_sub_menu()
|
||||
|
||||
func _on_back_button_pressed() -> void:
|
||||
_close_sub_menu()
|
@ -0,0 +1 @@
|
||||
uid://bhgs1upaahk3y
|
@ -0,0 +1,218 @@
|
||||
[gd_scene load_steps=9 format=3 uid="uid://c6k5nnpbypshi"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bhgs1upaahk3y" path="res://addons/maaacks_menus_template/base/scenes/menus/main_menu/main_menu.gd" id="1"]
|
||||
[ext_resource type="PackedScene" uid="uid://hmx6o472ropw" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/master_options_menu_with_tabs.tscn" id="2_qvrrd"]
|
||||
[ext_resource type="PackedScene" uid="uid://osxulxw2oas3" path="res://addons/maaacks_menus_template/base/scenes/credits/scrollable_credits.tscn" id="3_73am8"]
|
||||
[ext_resource type="Script" uid="uid://1nf36h0gms3q" path="res://addons/maaacks_menus_template/base/scripts/capture_focus.gd" id="4_l1ebe"]
|
||||
[ext_resource type="PackedScene" uid="uid://bkcsjsk2ciff" path="res://addons/maaacks_menus_template/base/scenes/music_players/background_music_player.tscn" id="4_w8sbm"]
|
||||
[ext_resource type="Script" uid="uid://b5oej1q4h7jvh" path="res://addons/maaacks_menus_template/base/scripts/ui_sound_controller.gd" id="6_bs342"]
|
||||
[ext_resource type="Script" uid="uid://dmkubt2nsnsbn" path="res://addons/maaacks_menus_template/base/scenes/menus/main_menu/config_version_label.gd" id="6_pdiij"]
|
||||
[ext_resource type="Script" uid="uid://bkwlopi4qn32o" path="res://addons/maaacks_menus_template/base/scenes/menus/main_menu/config_name_label.gd" id="7_j7612"]
|
||||
|
||||
[node name="MainMenu" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1")
|
||||
options_packed_scene = ExtResource("2_qvrrd")
|
||||
credits_packed_scene = ExtResource("3_73am8")
|
||||
|
||||
[node name="UISoundController" type="Node" parent="."]
|
||||
script = ExtResource("6_bs342")
|
||||
|
||||
[node name="BackgroundMusicPlayer" parent="." instance=ExtResource("4_w8sbm")]
|
||||
|
||||
[node name="BackgroundTextureRect" type="TextureRect" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
expand_mode = 1
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="VersionMargin" type="MarginContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_constants/margin_left = 8
|
||||
theme_override_constants/margin_top = 8
|
||||
theme_override_constants/margin_right = 8
|
||||
theme_override_constants/margin_bottom = 8
|
||||
|
||||
[node name="VersionContainer" type="Control" parent="VersionMargin"]
|
||||
layout_mode = 2
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="VersionLabel" type="Label" parent="VersionMargin/VersionContainer"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 3
|
||||
anchor_left = 1.0
|
||||
anchor_top = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -88.0
|
||||
offset_top = -26.0
|
||||
grow_horizontal = 0
|
||||
grow_vertical = 0
|
||||
text = "v0.0.0"
|
||||
horizontal_alignment = 2
|
||||
script = ExtResource("6_pdiij")
|
||||
|
||||
[node name="MenuContainer" type="MarginContainer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="TitleMargin" type="MarginContainer" parent="MenuContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_top = 24
|
||||
|
||||
[node name="TitleContainer" type="Control" parent="MenuContainer/TitleMargin"]
|
||||
layout_mode = 2
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="TitleLabel" type="Label" parent="MenuContainer/TitleMargin/TitleContainer"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 10
|
||||
anchor_right = 1.0
|
||||
offset_bottom = 67.0
|
||||
grow_horizontal = 2
|
||||
theme_override_font_sizes/font_size = 48
|
||||
text = "Title"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
script = ExtResource("7_j7612")
|
||||
|
||||
[node name="SubTitleMargin" type="MarginContainer" parent="MenuContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_top = 92
|
||||
|
||||
[node name="SubTitleContainer" type="Control" parent="MenuContainer/SubTitleMargin"]
|
||||
layout_mode = 2
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="SubTitleLabel" type="Label" parent="MenuContainer/SubTitleMargin/SubTitleContainer"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 10
|
||||
anchor_right = 1.0
|
||||
offset_bottom = 34.0
|
||||
grow_horizontal = 2
|
||||
theme_override_font_sizes/font_size = 24
|
||||
text = "Subtitle"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="MenuButtonsMargin" type="MarginContainer" parent="MenuContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
theme_override_constants/margin_top = 136
|
||||
theme_override_constants/margin_bottom = 8
|
||||
|
||||
[node name="MenuButtonsContainer" type="Control" parent="MenuContainer/MenuButtonsMargin"]
|
||||
layout_mode = 2
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="MenuButtonsBoxContainer" type="BoxContainer" parent="MenuContainer/MenuButtonsMargin/MenuButtonsContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -64.0
|
||||
offset_top = -104.0
|
||||
offset_right = 64.0
|
||||
offset_bottom = 104.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 4
|
||||
theme_override_constants/separation = 16
|
||||
alignment = 1
|
||||
vertical = true
|
||||
script = ExtResource("4_l1ebe")
|
||||
|
||||
[node name="NewGameButton" type="Button" parent="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "New Game"
|
||||
|
||||
[node name="OptionsButton" type="Button" parent="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Options"
|
||||
|
||||
[node name="CreditsButton" type="Button" parent="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Credits"
|
||||
|
||||
[node name="ExitButton" type="Button" parent="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Exit"
|
||||
|
||||
[node name="OptionsContainer" type="MarginContainer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="CreditsContainer" type="MarginContainer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
mouse_filter = 2
|
||||
theme_override_constants/margin_left = 16
|
||||
theme_override_constants/margin_top = 32
|
||||
theme_override_constants/margin_right = 16
|
||||
theme_override_constants/margin_bottom = 32
|
||||
|
||||
[node name="FlowControlContainer" type="MarginContainer" parent="."]
|
||||
layout_mode = 0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
mouse_filter = 2
|
||||
theme_override_constants/margin_left = 16
|
||||
theme_override_constants/margin_top = 16
|
||||
theme_override_constants/margin_right = 16
|
||||
theme_override_constants/margin_bottom = 16
|
||||
|
||||
[node name="FlowControl" type="Control" parent="FlowControlContainer"]
|
||||
layout_mode = 2
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="BackButton" type="Button" parent="FlowControlContainer/FlowControl"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
anchors_preset = 2
|
||||
anchor_top = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_top = -31.0
|
||||
offset_right = 45.0
|
||||
grow_vertical = 0
|
||||
text = "Back"
|
||||
|
||||
[connection signal="pressed" from="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer/NewGameButton" to="." method="_on_new_game_button_pressed"]
|
||||
[connection signal="pressed" from="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer/OptionsButton" to="." method="_on_options_button_pressed"]
|
||||
[connection signal="pressed" from="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer/CreditsButton" to="." method="_on_credits_button_pressed"]
|
||||
[connection signal="pressed" from="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer/ExitButton" to="." method="_on_exit_button_pressed"]
|
||||
[connection signal="pressed" from="FlowControlContainer/FlowControl/BackButton" to="." method="_on_back_button_pressed"]
|
@ -0,0 +1,37 @@
|
||||
class_name AudioOptionsMenu
|
||||
extends Control
|
||||
|
||||
@export var audio_control_scene : PackedScene
|
||||
@export var hide_busses : Array[String]
|
||||
|
||||
@onready var mute_control = %MuteControl
|
||||
|
||||
func _on_bus_changed(bus_value : float, bus_iter : int) -> void:
|
||||
AppSettings.set_bus_volume(bus_iter, bus_value)
|
||||
|
||||
func _add_audio_control(bus_name : String, bus_value : float, bus_iter : int) -> void:
|
||||
if audio_control_scene == null or bus_name in hide_busses or bus_name.begins_with(AppSettings.SYSTEM_BUS_NAME_PREFIX):
|
||||
return
|
||||
var audio_control = audio_control_scene.instantiate()
|
||||
%AudioControlContainer.call_deferred("add_child", audio_control)
|
||||
if audio_control is OptionControl:
|
||||
audio_control.option_section = OptionControl.OptionSections.AUDIO
|
||||
audio_control.option_name = bus_name
|
||||
audio_control.value = bus_value
|
||||
audio_control.connect("setting_changed", _on_bus_changed.bind(bus_iter))
|
||||
|
||||
func _add_audio_bus_controls() -> void:
|
||||
for bus_iter in AudioServer.bus_count:
|
||||
var bus_name : String = AppSettings.get_audio_bus_name(bus_iter)
|
||||
var linear : float = AppSettings.get_bus_volume(bus_iter)
|
||||
_add_audio_control(bus_name, linear, bus_iter)
|
||||
|
||||
func _update_ui() -> void:
|
||||
_add_audio_bus_controls()
|
||||
mute_control.value = AppSettings.is_muted()
|
||||
|
||||
func _ready() -> void:
|
||||
_update_ui()
|
||||
|
||||
func _on_mute_control_setting_changed(value : bool) -> void:
|
||||
AppSettings.set_mute(value)
|
@ -0,0 +1 @@
|
||||
uid://bwugqn2cjr41e
|
@ -0,0 +1,42 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://c8vnncjwqcpab"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bwugqn2cjr41e" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/audio/audio_options_menu.gd" id="1"]
|
||||
[ext_resource type="PackedScene" uid="uid://cl416gdb1fgwr" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/option_control/slider_option_control.tscn" id="2_raehj"]
|
||||
[ext_resource type="Script" uid="uid://1nf36h0gms3q" path="res://addons/maaacks_menus_template/base/scripts/capture_focus.gd" id="3_dtraq"]
|
||||
[ext_resource type="PackedScene" uid="uid://bsxh6v7j0257h" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/option_control/toggle_option_control.tscn" id="4_ojfec"]
|
||||
|
||||
[node name="Audio" type="MarginContainer"]
|
||||
custom_minimum_size = Vector2(305, 0)
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_constants/margin_top = 24
|
||||
theme_override_constants/margin_bottom = 24
|
||||
script = ExtResource("1")
|
||||
audio_control_scene = ExtResource("2_raehj")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
custom_minimum_size = Vector2(400, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
theme_override_constants/separation = 8
|
||||
alignment = 1
|
||||
script = ExtResource("3_dtraq")
|
||||
search_depth = 3
|
||||
|
||||
[node name="AudioControlContainer" type="VBoxContainer" parent="VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 8
|
||||
|
||||
[node name="MuteControl" parent="VBoxContainer" instance=ExtResource("4_ojfec")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
option_name = "Mute"
|
||||
option_section = 2
|
||||
key = "Mute"
|
||||
section = "AudioSettings"
|
||||
|
||||
[connection signal="setting_changed" from="VBoxContainer/MuteControl" to="." method="_on_mute_control_setting_changed"]
|
@ -0,0 +1,325 @@
|
||||
@tool
|
||||
class_name InputActionsList
|
||||
extends Container
|
||||
|
||||
const EMPTY_INPUT_ACTION_STRING = " "
|
||||
|
||||
signal already_assigned(action_name : String, input_name : String)
|
||||
signal minimum_reached(action_name : String)
|
||||
signal button_clicked(action_name : String, readable_input_name : String)
|
||||
|
||||
const BUTTON_NAME_GROUP_STRING : String = "%s:%d"
|
||||
|
||||
@export var vertical : bool = true :
|
||||
set(value):
|
||||
vertical = value
|
||||
if is_inside_tree():
|
||||
%ParentBoxContainer.vertical = vertical
|
||||
|
||||
@export_range(1, 5) var action_groups : int = 2
|
||||
@export var action_group_names : Array[String]
|
||||
@export var input_action_names : Array[StringName] :
|
||||
set(value):
|
||||
var _value_changed = input_action_names != value
|
||||
input_action_names = value
|
||||
if _value_changed:
|
||||
var _new_readable_action_names : Array[String]
|
||||
for action in input_action_names:
|
||||
_new_readable_action_names.append(action.capitalize())
|
||||
readable_action_names = _new_readable_action_names
|
||||
|
||||
@export var readable_action_names : Array[String] :
|
||||
set(value):
|
||||
var _value_changed = readable_action_names != value
|
||||
readable_action_names = value
|
||||
if _value_changed:
|
||||
var _new_action_name_map : Dictionary
|
||||
for iter in range(input_action_names.size()):
|
||||
var _input_name : StringName = input_action_names[iter]
|
||||
var _readable_name : String = readable_action_names[iter]
|
||||
_new_action_name_map[_input_name] = _readable_name
|
||||
action_name_map = _new_action_name_map
|
||||
|
||||
## Show action names that are not explicitely listed in an action name map.
|
||||
@export var show_all_actions : bool = true
|
||||
@export var button_minimum_size : Vector2
|
||||
@export_group("Icons")
|
||||
@export var input_icon_mapper : InputIconMapper
|
||||
@export var expand_icon : bool = false
|
||||
@export_group("Built-in Actions")
|
||||
## Shows Godot's built-in actions (action names starting with "ui_") in the tree.
|
||||
@export var show_built_in_actions : bool = false
|
||||
## Prevents assigning inputs that are already assigned to Godot's built-in actions (action names starting with "ui_"). Not recommended.
|
||||
@export var catch_built_in_duplicate_inputs : bool = false
|
||||
## Maps the names of built-in input actions to readable names for users.
|
||||
@export var built_in_action_name_map := InputEventHelper.BUILT_IN_ACTION_NAME_MAP
|
||||
@export_group("Debug")
|
||||
## Maps the names of input actions to readable names for users.
|
||||
@export var action_name_map : Dictionary
|
||||
|
||||
var action_button_map : Dictionary = {}
|
||||
var button_readable_input_map : Dictionary = {}
|
||||
var assigned_input_events : Dictionary = {}
|
||||
var editing_action_name : String = ""
|
||||
var editing_action_group : int = 0
|
||||
var last_input_readable_name
|
||||
|
||||
func _clear_list() -> void:
|
||||
for child in %ParentBoxContainer.get_children():
|
||||
if child == %ActionBoxContainer:
|
||||
continue
|
||||
child.queue_free()
|
||||
|
||||
func _replace_action(action_name : String, readable_input_name : String = "") -> void:
|
||||
var readable_action_name = tr(_get_action_readable_name(action_name))
|
||||
button_clicked.emit(readable_action_name, readable_input_name)
|
||||
|
||||
func _on_button_pressed(action_name : String, action_group : int) -> void:
|
||||
editing_action_name = action_name
|
||||
editing_action_group = action_group
|
||||
_replace_action(action_name)
|
||||
|
||||
func _new_action_box() -> Node:
|
||||
var new_action_box : Node = %ActionBoxContainer.duplicate()
|
||||
new_action_box.visible = true
|
||||
new_action_box.vertical = !(vertical)
|
||||
return new_action_box
|
||||
|
||||
func _add_header() -> void:
|
||||
if action_group_names.is_empty(): return
|
||||
var new_action_box := _new_action_box()
|
||||
for group_iter in range(action_groups):
|
||||
var group_name := ""
|
||||
if group_iter < action_group_names.size():
|
||||
group_name = action_group_names[group_iter]
|
||||
var new_label := Label.new()
|
||||
if button_minimum_size.x > 0:
|
||||
new_label.custom_minimum_size.x = button_minimum_size.x
|
||||
new_label.size_flags_horizontal = SIZE_SHRINK_CENTER
|
||||
else:
|
||||
new_label.size_flags_horizontal = SIZE_EXPAND_FILL
|
||||
if button_minimum_size.y > 0:
|
||||
new_label.custom_minimum_size.y = button_minimum_size.y
|
||||
new_label.size_flags_vertical = SIZE_SHRINK_CENTER
|
||||
else:
|
||||
new_label.size_flags_vertical = SIZE_EXPAND_FILL
|
||||
new_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
new_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
new_label.text = group_name
|
||||
new_action_box.add_child(new_label)
|
||||
%ParentBoxContainer.add_child(new_action_box)
|
||||
|
||||
func _add_to_action_button_map(action_name : String, action_group : int, button_node : BaseButton) -> void:
|
||||
var key_string : String = BUTTON_NAME_GROUP_STRING % [action_name, action_group]
|
||||
action_button_map[key_string] = button_node
|
||||
|
||||
func _get_button_by_action(action_name : String, action_group : int) -> Button:
|
||||
var key_string : String = BUTTON_NAME_GROUP_STRING % [action_name, action_group]
|
||||
if key_string in action_button_map:
|
||||
return action_button_map[key_string]
|
||||
return null
|
||||
|
||||
func _update_next_button_disabled_state(action_name : String, action_group : int) -> void:
|
||||
var button = _get_button_by_action(action_name, action_group)
|
||||
if button:
|
||||
button.disabled = false
|
||||
|
||||
func _update_assigned_inputs_and_button(action_name : String, action_group : int, input_event : InputEvent) -> void:
|
||||
var new_readable_input_name = InputEventHelper.get_text(input_event)
|
||||
var button = _get_button_by_action(action_name, action_group)
|
||||
if not button: return
|
||||
var icon : Texture
|
||||
if input_icon_mapper:
|
||||
icon = input_icon_mapper.get_icon(input_event)
|
||||
if icon:
|
||||
button.icon = icon
|
||||
else:
|
||||
button.icon = null
|
||||
if button.icon == null:
|
||||
button.text = new_readable_input_name
|
||||
else:
|
||||
button.text = ""
|
||||
var old_readable_input_name : String
|
||||
if button in button_readable_input_map:
|
||||
old_readable_input_name = button_readable_input_map[button]
|
||||
assigned_input_events.erase(old_readable_input_name)
|
||||
button_readable_input_map[button] = new_readable_input_name
|
||||
assigned_input_events[new_readable_input_name] = action_name
|
||||
|
||||
func _clear_button(action_name : String, action_group : int) -> void:
|
||||
var button = _get_button_by_action(action_name, action_group)
|
||||
if not button: return
|
||||
button.icon = null
|
||||
button.text = EMPTY_INPUT_ACTION_STRING
|
||||
var old_readable_input_name : String
|
||||
if button in button_readable_input_map:
|
||||
old_readable_input_name = button_readable_input_map[button]
|
||||
assigned_input_events.erase(old_readable_input_name)
|
||||
button_readable_input_map[button] = EMPTY_INPUT_ACTION_STRING
|
||||
|
||||
func _add_new_button(content : Variant, container: Control, disabled : bool = false) -> Button:
|
||||
var new_button := Button.new()
|
||||
if button_minimum_size.x > 0:
|
||||
new_button.custom_minimum_size.x = button_minimum_size.x
|
||||
new_button.size_flags_horizontal = SIZE_SHRINK_CENTER
|
||||
else:
|
||||
new_button.size_flags_horizontal = SIZE_EXPAND_FILL
|
||||
if button_minimum_size.y > 0:
|
||||
new_button.custom_minimum_size.y = button_minimum_size.y
|
||||
new_button.size_flags_vertical = SIZE_SHRINK_CENTER
|
||||
else:
|
||||
new_button.size_flags_vertical = SIZE_EXPAND_FILL
|
||||
new_button.icon_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
new_button.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||
new_button.expand_icon = expand_icon
|
||||
if content is Texture:
|
||||
new_button.icon = content
|
||||
elif content is String:
|
||||
new_button.text = content
|
||||
new_button.disabled = disabled
|
||||
container.add_child(new_button)
|
||||
return new_button
|
||||
|
||||
func _connect_button_and_add_to_maps(button : Button, input_name : String, action_name : String, group_iter : int) -> void:
|
||||
button.pressed.connect(_on_button_pressed.bind(action_name, group_iter))
|
||||
button_readable_input_map[button] = input_name
|
||||
_add_to_action_button_map(action_name, group_iter, button)
|
||||
|
||||
func _add_action_options(action_name : String, readable_action_name : String, input_events : Array[InputEvent]) -> void:
|
||||
var new_action_box = %ActionBoxContainer.duplicate()
|
||||
new_action_box.visible = true
|
||||
new_action_box.vertical = !(vertical)
|
||||
new_action_box.get_child(0).text = readable_action_name
|
||||
for group_iter in range(action_groups):
|
||||
var input_event : InputEvent
|
||||
if group_iter < input_events.size():
|
||||
input_event = input_events[group_iter]
|
||||
var text = InputEventHelper.get_text(input_event)
|
||||
var is_disabled = group_iter > input_events.size()
|
||||
if text.is_empty(): text = EMPTY_INPUT_ACTION_STRING
|
||||
var icon : Texture
|
||||
if input_icon_mapper:
|
||||
icon = input_icon_mapper.get_icon(input_event)
|
||||
var content = icon if icon else text
|
||||
var button : Button = _add_new_button(content, new_action_box, is_disabled)
|
||||
_connect_button_and_add_to_maps(button, text, action_name, group_iter)
|
||||
%ParentBoxContainer.add_child(new_action_box)
|
||||
|
||||
func _get_all_action_names(include_built_in : bool = false) -> Array[StringName]:
|
||||
var action_names : Array[StringName] = input_action_names.duplicate()
|
||||
var full_action_name_map = action_name_map.duplicate()
|
||||
if include_built_in:
|
||||
for action_name in built_in_action_name_map:
|
||||
if action_name is String:
|
||||
action_name = StringName(action_name)
|
||||
if action_name is StringName:
|
||||
action_names.append(action_name)
|
||||
if show_all_actions:
|
||||
var all_actions := AppSettings.get_action_names(include_built_in)
|
||||
for action_name in all_actions:
|
||||
if not action_name in action_names:
|
||||
action_names.append(action_name)
|
||||
return action_names
|
||||
|
||||
func _get_action_readable_name(input_name : StringName) -> String:
|
||||
var readable_name : String
|
||||
if input_name in action_name_map:
|
||||
readable_name = action_name_map[input_name]
|
||||
elif input_name in built_in_action_name_map:
|
||||
readable_name = built_in_action_name_map[input_name]
|
||||
else:
|
||||
readable_name = input_name.capitalize()
|
||||
action_name_map[input_name] = readable_name
|
||||
return readable_name
|
||||
|
||||
func _build_ui_list() -> void:
|
||||
_clear_list()
|
||||
_add_header()
|
||||
var action_names : Array[StringName] = _get_all_action_names(show_built_in_actions)
|
||||
for action_name in action_names:
|
||||
var input_events = InputMap.action_get_events(action_name)
|
||||
if input_events.size() < 1:
|
||||
continue
|
||||
var readable_name : String = _get_action_readable_name(action_name)
|
||||
_add_action_options(action_name, readable_name, input_events)
|
||||
|
||||
func _assign_input_event(input_event : InputEvent, action_name : String) -> void:
|
||||
assigned_input_events[InputEventHelper.get_text(input_event)] = action_name
|
||||
|
||||
func _assign_input_event_to_action_group(input_event : InputEvent, action_name : String, action_group : int) -> void:
|
||||
_assign_input_event(input_event, action_name)
|
||||
var action_events := InputMap.action_get_events(action_name)
|
||||
action_events.resize(action_events.size() + 1)
|
||||
action_events[action_group] = input_event
|
||||
InputMap.action_erase_events(action_name)
|
||||
var final_action_events : Array[InputEvent]
|
||||
for input_action_event in action_events:
|
||||
if input_action_event == null: continue
|
||||
final_action_events.append(input_action_event)
|
||||
InputMap.action_add_event(action_name, input_action_event)
|
||||
AppSettings.set_config_input_events(action_name, final_action_events)
|
||||
action_group = min(action_group, final_action_events.size() - 1)
|
||||
_update_assigned_inputs_and_button(action_name, action_group, input_event)
|
||||
_update_next_button_disabled_state(action_name, action_group)
|
||||
|
||||
func _build_assigned_input_events() -> void:
|
||||
assigned_input_events.clear()
|
||||
var action_names := _get_all_action_names(show_built_in_actions and catch_built_in_duplicate_inputs)
|
||||
for action_name in action_names:
|
||||
var input_events = InputMap.action_get_events(action_name)
|
||||
for input_event in input_events:
|
||||
_assign_input_event(input_event, action_name)
|
||||
|
||||
func _get_action_for_input_event(input_event : InputEvent) -> String:
|
||||
if InputEventHelper.get_text(input_event) in assigned_input_events:
|
||||
return assigned_input_events[InputEventHelper.get_text(input_event)]
|
||||
return ""
|
||||
|
||||
func add_action_event(last_input_text : String, last_input_event : InputEvent) -> void:
|
||||
last_input_readable_name = last_input_text
|
||||
if last_input_event != null:
|
||||
var assigned_action := _get_action_for_input_event(last_input_event)
|
||||
if not assigned_action.is_empty():
|
||||
var readable_action_name = tr(_get_action_readable_name(assigned_action))
|
||||
already_assigned.emit(readable_action_name, last_input_readable_name)
|
||||
else:
|
||||
_assign_input_event_to_action_group(last_input_event, editing_action_name, editing_action_group)
|
||||
editing_action_name = ""
|
||||
|
||||
func _refresh_ui_list_button_content() -> void:
|
||||
var action_names : Array[StringName] = _get_all_action_names(show_built_in_actions)
|
||||
for action_name in action_names:
|
||||
var input_events := InputMap.action_get_events(action_name)
|
||||
if input_events.size() < 1:
|
||||
continue
|
||||
var group_iter : int = 0
|
||||
for input_event in input_events:
|
||||
_update_assigned_inputs_and_button(action_name, group_iter, input_event)
|
||||
group_iter += 1
|
||||
while group_iter < action_groups:
|
||||
_clear_button(action_name, group_iter)
|
||||
group_iter += 1
|
||||
|
||||
func _set_action_box_container_size() -> void:
|
||||
if button_minimum_size.x > 0:
|
||||
%ActionBoxContainer.size_flags_horizontal = SIZE_SHRINK_CENTER
|
||||
else:
|
||||
%ActionBoxContainer.size_flags_horizontal = SIZE_EXPAND_FILL
|
||||
if button_minimum_size.y > 0:
|
||||
%ActionBoxContainer.size_flags_vertical = SIZE_SHRINK_CENTER
|
||||
else:
|
||||
%ActionBoxContainer.size_flags_vertical = SIZE_EXPAND_FILL
|
||||
|
||||
func reset() -> void:
|
||||
AppSettings.reset_to_default_inputs()
|
||||
_build_assigned_input_events()
|
||||
_refresh_ui_list_button_content()
|
||||
|
||||
func _ready() -> void:
|
||||
if Engine.is_editor_hint(): return
|
||||
vertical = vertical
|
||||
_set_action_box_container_size()
|
||||
_build_assigned_input_events()
|
||||
_build_ui_list()
|
||||
if input_icon_mapper:
|
||||
input_icon_mapper.joypad_device_changed.connect(_refresh_ui_list_button_content)
|
@ -0,0 +1 @@
|
||||
uid://b3q5fgjev8gyo
|
@ -0,0 +1,44 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://bxp45814v6ydv"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b3q5fgjev8gyo" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/input/input_actions_list.gd" id="1_cxorh"]
|
||||
|
||||
[node name="InputActionsList" type="ScrollContainer"]
|
||||
custom_minimum_size = Vector2(560, 240)
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
follow_focus = true
|
||||
script = ExtResource("1_cxorh")
|
||||
action_groups = 3
|
||||
action_group_names = Array[String](["Primary", "Secondary", "Tertiary", "Quaternary", "Quinary"])
|
||||
input_action_names = Array[StringName]([&"move_forward", &"move_backward", &"move_up", &"move_down", &"move_left", &"move_right", &"interact"])
|
||||
readable_action_names = Array[String](["Move Forward", "Move Backward", "Move Up", "Move Down", "Move Left", "Move Right", "Interact"])
|
||||
action_name_map = {
|
||||
"interact": "Interact",
|
||||
"move_backward": "Move Backward",
|
||||
"move_down": "Move Down",
|
||||
"move_forward": "Move Forward",
|
||||
"move_left": "Move Left",
|
||||
"move_right": "Move Right",
|
||||
"move_up": "Move Up"
|
||||
}
|
||||
|
||||
[node name="ParentBoxContainer" type="BoxContainer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
vertical = true
|
||||
|
||||
[node name="ActionBoxContainer" type="BoxContainer" parent="ParentBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ActionNameLabel" type="Label" parent="ParentBoxContainer/ActionBoxContainer"]
|
||||
custom_minimum_size = Vector2(150, 0)
|
||||
layout_mode = 2
|
@ -0,0 +1,215 @@
|
||||
class_name InputActionsTree
|
||||
extends Tree
|
||||
|
||||
signal already_assigned(action_name : String, input_name : String)
|
||||
signal minimum_reached(action_name : String)
|
||||
signal add_button_clicked(action_name : String)
|
||||
signal remove_button_clicked(action_name : String, input_name : String)
|
||||
|
||||
@export var input_action_names : Array[StringName] :
|
||||
set(value):
|
||||
var _value_changed = input_action_names != value
|
||||
input_action_names = value
|
||||
if _value_changed:
|
||||
var _new_readable_action_names : Array[String]
|
||||
for action in input_action_names:
|
||||
_new_readable_action_names.append(action.capitalize())
|
||||
readable_action_names = _new_readable_action_names
|
||||
|
||||
@export var readable_action_names : Array[String] :
|
||||
set(value):
|
||||
var _value_changed = readable_action_names != value
|
||||
readable_action_names = value
|
||||
if _value_changed:
|
||||
var _new_action_name_map : Dictionary
|
||||
for iter in range(input_action_names.size()):
|
||||
var _input_name : StringName = input_action_names[iter]
|
||||
var _readable_name : String = readable_action_names[iter]
|
||||
_new_action_name_map[_input_name] = _readable_name
|
||||
action_name_map = _new_action_name_map
|
||||
|
||||
## Show action names that are not explicitely listed in an action name map.
|
||||
@export var show_all_actions : bool = true
|
||||
@export_group("Icons")
|
||||
@export var add_button_texture : Texture2D
|
||||
@export var remove_button_texture : Texture2D
|
||||
@export var input_icon_mapper : InputIconMapper
|
||||
@export_group("Built-in Actions")
|
||||
## Shows Godot's built-in actions (action names starting with "ui_") in the tree.
|
||||
@export var show_built_in_actions : bool = false
|
||||
## Prevents assigning inputs that are already assigned to Godot's built-in actions (action names starting with "ui_"). Not recommended.
|
||||
@export var catch_built_in_duplicate_inputs : bool = false
|
||||
## Maps the names of built-in input actions to readable names for users.
|
||||
@export var built_in_action_name_map := InputEventHelper.BUILT_IN_ACTION_NAME_MAP
|
||||
@export_group("Debug")
|
||||
## Maps the names of input actions to readable names for users.
|
||||
@export var action_name_map : Dictionary
|
||||
|
||||
var tree_item_add_map : Dictionary = {}
|
||||
var tree_item_remove_map : Dictionary = {}
|
||||
var tree_item_action_map : Dictionary = {}
|
||||
var assigned_input_events : Dictionary = {}
|
||||
var editing_action_name : String = ""
|
||||
var editing_item
|
||||
var last_input_readable_name
|
||||
|
||||
func _start_tree() -> void:
|
||||
clear()
|
||||
create_item()
|
||||
|
||||
func _add_input_event_as_tree_item(action_name : String, input_event : InputEvent, parent_item : TreeItem) -> void:
|
||||
var input_tree_item : TreeItem = create_item(parent_item)
|
||||
var icon : Texture
|
||||
if input_icon_mapper:
|
||||
icon = input_icon_mapper.get_icon(input_event)
|
||||
if icon:
|
||||
input_tree_item.set_icon(0, icon)
|
||||
input_tree_item.set_text(0, InputEventHelper.get_text(input_event))
|
||||
if remove_button_texture != null:
|
||||
input_tree_item.add_button(0, remove_button_texture, -1, false, "Remove")
|
||||
tree_item_remove_map[input_tree_item] = input_event
|
||||
tree_item_action_map[input_tree_item] = action_name
|
||||
|
||||
func _add_action_as_tree_item(readable_name : String, action_name : String, input_events : Array[InputEvent]) -> void:
|
||||
var root_tree_item : TreeItem = get_root()
|
||||
var action_tree_item : TreeItem = create_item(root_tree_item)
|
||||
action_tree_item.set_text(0, readable_name)
|
||||
tree_item_add_map[action_tree_item] = action_name
|
||||
if add_button_texture != null:
|
||||
action_tree_item.add_button(0, add_button_texture, -1, false, "Add")
|
||||
for input_event in input_events:
|
||||
_add_input_event_as_tree_item(action_name, input_event, action_tree_item)
|
||||
|
||||
func _get_all_action_names(include_built_in : bool = false) -> Array[StringName]:
|
||||
var action_names : Array[StringName] = input_action_names.duplicate()
|
||||
var full_action_name_map = action_name_map.duplicate()
|
||||
if include_built_in:
|
||||
for action_name in built_in_action_name_map:
|
||||
if action_name is String:
|
||||
action_name = StringName(action_name)
|
||||
if action_name is StringName:
|
||||
action_names.append(action_name)
|
||||
if show_all_actions:
|
||||
var all_actions := AppSettings.get_action_names(include_built_in)
|
||||
for action_name in all_actions:
|
||||
if not action_name in action_names:
|
||||
action_names.append(action_name)
|
||||
return action_names
|
||||
|
||||
func _get_action_readable_name(input_name : StringName) -> String:
|
||||
var readable_name : String
|
||||
if input_name in action_name_map:
|
||||
readable_name = action_name_map[input_name]
|
||||
elif input_name in built_in_action_name_map:
|
||||
readable_name = built_in_action_name_map[input_name]
|
||||
else:
|
||||
readable_name = input_name.capitalize()
|
||||
action_name_map[input_name] = readable_name
|
||||
return readable_name
|
||||
|
||||
func _build_ui_tree() -> void:
|
||||
_start_tree()
|
||||
var action_names : Array[StringName] = _get_all_action_names(show_built_in_actions)
|
||||
for action_name in action_names:
|
||||
var input_events = InputMap.action_get_events(action_name)
|
||||
if input_events.size() < 1:
|
||||
continue
|
||||
var readable_name : String = _get_action_readable_name(action_name)
|
||||
_add_action_as_tree_item(readable_name, action_name, input_events)
|
||||
|
||||
func _assign_input_event(input_event : InputEvent, action_name : String) -> void:
|
||||
assigned_input_events[InputEventHelper.get_text(input_event)] = action_name
|
||||
|
||||
func _assign_input_event_to_action(input_event : InputEvent, action_name : String) -> void:
|
||||
_assign_input_event(input_event, action_name)
|
||||
InputMap.action_add_event(action_name, input_event)
|
||||
var action_events = InputMap.action_get_events(action_name)
|
||||
AppSettings.set_config_input_events(action_name, action_events)
|
||||
_add_input_event_as_tree_item(action_name, input_event, editing_item)
|
||||
|
||||
func _can_remove_input_event(action_name : String) -> bool:
|
||||
return InputMap.action_get_events(action_name).size() > 1
|
||||
|
||||
func _remove_input_event(input_event : InputEvent) -> void:
|
||||
assigned_input_events.erase(InputEventHelper.get_text(input_event))
|
||||
|
||||
func _remove_input_event_from_action(input_event : InputEvent, action_name : String) -> void:
|
||||
_remove_input_event(input_event)
|
||||
AppSettings.remove_action_input_event(action_name, input_event)
|
||||
|
||||
func _build_assigned_input_events() -> void:
|
||||
assigned_input_events.clear()
|
||||
var action_names := _get_all_action_names(show_built_in_actions and catch_built_in_duplicate_inputs)
|
||||
for action_name in action_names:
|
||||
var input_events = InputMap.action_get_events(action_name)
|
||||
for input_event in input_events:
|
||||
_assign_input_event(input_event, action_name)
|
||||
|
||||
func _get_action_for_input_event(input_event : InputEvent) -> String:
|
||||
if InputEventHelper.get_text(input_event) in assigned_input_events:
|
||||
return assigned_input_events[InputEventHelper.get_text(input_event)]
|
||||
return ""
|
||||
|
||||
func add_action_event(last_input_text : String, last_input_event : InputEvent):
|
||||
last_input_readable_name = last_input_text
|
||||
if last_input_event != null:
|
||||
var assigned_action := _get_action_for_input_event(last_input_event)
|
||||
if not assigned_action.is_empty():
|
||||
var readable_action_name = tr(_get_action_readable_name(assigned_action))
|
||||
already_assigned.emit(readable_action_name, last_input_readable_name)
|
||||
else:
|
||||
_assign_input_event_to_action(last_input_event, editing_action_name)
|
||||
editing_action_name = ""
|
||||
|
||||
func remove_action_event(item : TreeItem) -> void:
|
||||
if item not in tree_item_remove_map:
|
||||
return
|
||||
var action_name = tree_item_action_map[item]
|
||||
var input_event = tree_item_remove_map[item]
|
||||
if not _can_remove_input_event(action_name):
|
||||
var readable_action_name = _get_action_readable_name(action_name)
|
||||
minimum_reached.emit(readable_action_name)
|
||||
return
|
||||
_remove_input_event_from_action(input_event, action_name)
|
||||
var parent_tree_item = item.get_parent()
|
||||
parent_tree_item.remove_child(item)
|
||||
|
||||
func reset() -> void:
|
||||
AppSettings.reset_to_default_inputs()
|
||||
_build_assigned_input_events()
|
||||
_build_ui_tree()
|
||||
|
||||
func _add_item(item : TreeItem) -> void:
|
||||
editing_item = item
|
||||
editing_action_name = tree_item_add_map[item]
|
||||
var readable_action_name = tr(_get_action_readable_name(editing_action_name))
|
||||
add_button_clicked.emit(readable_action_name)
|
||||
|
||||
func _remove_item(item : TreeItem) -> void:
|
||||
editing_item = item
|
||||
editing_action_name = tree_item_action_map[item]
|
||||
var readable_action_name = tr(_get_action_readable_name(editing_action_name))
|
||||
var item_text = item.get_text(0)
|
||||
remove_button_clicked.emit(readable_action_name, item_text)
|
||||
|
||||
func _check_item_actions(item : TreeItem) -> void:
|
||||
if item in tree_item_add_map:
|
||||
_add_item(item)
|
||||
elif item in tree_item_remove_map:
|
||||
_remove_item(item)
|
||||
|
||||
func _on_button_clicked(item : TreeItem, _column, _id, _mouse_button_index) -> void:
|
||||
_check_item_actions(item)
|
||||
|
||||
func _on_item_activated() -> void:
|
||||
var item = get_selected()
|
||||
_check_item_actions(item)
|
||||
|
||||
func _ready() -> void:
|
||||
if Engine.is_editor_hint(): return
|
||||
_build_assigned_input_events()
|
||||
_build_ui_tree()
|
||||
button_clicked.connect(_on_button_clicked)
|
||||
item_activated.connect(_on_item_activated)
|
||||
if input_icon_mapper:
|
||||
input_icon_mapper.joypad_device_changed.connect(_build_ui_tree)
|
@ -0,0 +1 @@
|
||||
uid://bp7d2e5djo2tp
|
@ -0,0 +1,30 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://ci6wgl2ngd35n"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bp7d2e5djo2tp" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/input/input_actions_tree.gd" id="1_o33o4"]
|
||||
[ext_resource type="Texture2D" uid="uid://c1eqf1cse1hch" path="res://addons/maaacks_menus_template/base/assets/remapping_input_icons/addition_symbol.png" id="2_ppi0j"]
|
||||
[ext_resource type="Texture2D" uid="uid://bteq3ica74h30" path="res://addons/maaacks_menus_template/base/assets/remapping_input_icons/subtraction_symbol.png" id="3_hb3xh"]
|
||||
|
||||
[node name="InputActionsTree" type="Tree"]
|
||||
custom_minimum_size = Vector2(400, 240)
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
hide_root = true
|
||||
script = ExtResource("1_o33o4")
|
||||
input_action_names = Array[StringName]([&"move_forward", &"move_backward", &"move_up", &"move_down", &"move_left", &"move_right", &"interact"])
|
||||
readable_action_names = Array[String](["Move Forward", "Move Backward", "Move Up", "Move Down", "Move Left", "Move Right", "Interact"])
|
||||
add_button_texture = ExtResource("2_ppi0j")
|
||||
remove_button_texture = ExtResource("3_hb3xh")
|
||||
action_name_map = {
|
||||
"interact": "Interact",
|
||||
"move_backward": "Move Backward",
|
||||
"move_down": "Move Down",
|
||||
"move_forward": "Move Forward",
|
||||
"move_left": "Move Left",
|
||||
"move_right": "Move Right",
|
||||
"move_up": "Move Up"
|
||||
}
|
@ -0,0 +1,140 @@
|
||||
@tool
|
||||
class_name InputIconMapper
|
||||
extends FileLister
|
||||
|
||||
signal joypad_device_changed
|
||||
|
||||
const COMMON_REPLACE_STRINGS: Dictionary = {
|
||||
"L 1": "Left Shoulder",
|
||||
"R 1": "Right Shoulder",
|
||||
"L 2": "Left Trigger",
|
||||
"R 2": "Right Trigger",
|
||||
"Lt": "Left Trigger",
|
||||
"Rt": "Right Trigger",
|
||||
"Lb": "Left Shoulder",
|
||||
"Rb": "Right Shoulder",
|
||||
} # Dictionary[String, String]
|
||||
## Gives priority to icons with occurrences of the provided strings.
|
||||
@export var prioritized_strings : Array[String]
|
||||
## Replaces the first occurence in icon names of the key with the value.
|
||||
@export var replace_strings : Dictionary # Dictionary[String, String]
|
||||
## Filters the icon names of the provided strings.
|
||||
@export var filtered_strings : Array[String]
|
||||
## Adds entries for "Up", "Down", "Left", "Right" to icon names ending with "Stick".
|
||||
@export var add_stick_directions : bool = false
|
||||
@export var intial_joypad_device : String = InputEventHelper.DEVICE_GENERIC
|
||||
## Attempt to match the icon names to the input names based on the string rules.
|
||||
@export var _match_icons_to_inputs_action : bool = false :
|
||||
set(value):
|
||||
if value and Engine.is_editor_hint():
|
||||
_match_icons_to_inputs()
|
||||
# For Godot 4.4
|
||||
# @export_tool_button("Match Icons to Inputs") var _match_icons_to_inputs_action = _match_icons_to_inputs
|
||||
@export var matching_icons : Dictionary # Dictionary[String, Texture]
|
||||
@export_group("Debug")
|
||||
@export var all_icons : Dictionary # Dictionary[String, Texture]
|
||||
|
||||
@onready var last_joypad_device = intial_joypad_device
|
||||
|
||||
func _is_end_of_word(full_string : String, what : String) -> bool:
|
||||
var string_end_position = full_string.find(what) + what.length()
|
||||
var end_of_word : bool
|
||||
if string_end_position + 1 < full_string.length():
|
||||
var next_character = full_string.substr(string_end_position, 1)
|
||||
end_of_word = next_character == " "
|
||||
return full_string.ends_with(what) or end_of_word
|
||||
|
||||
func _get_standard_joy_name(joy_name : String) -> String:
|
||||
var all_replace_strings := replace_strings.duplicate()
|
||||
all_replace_strings.merge(COMMON_REPLACE_STRINGS)
|
||||
for what in all_replace_strings:
|
||||
if joy_name.contains(what) and _is_end_of_word(joy_name, what):
|
||||
var position = joy_name.find(what)
|
||||
joy_name = joy_name.erase(position, what.length())
|
||||
joy_name = joy_name.insert(position, all_replace_strings[what])
|
||||
var combined_joystick_name : Array[String] = []
|
||||
for part in joy_name.split(" "):
|
||||
if part.to_lower() in filtered_strings:
|
||||
continue
|
||||
if not part.is_empty():
|
||||
combined_joystick_name.append(part)
|
||||
joy_name = " ".join(combined_joystick_name)
|
||||
joy_name = joy_name.strip_edges()
|
||||
return joy_name
|
||||
|
||||
func _match_icon_to_file(file : String) -> void:
|
||||
var matching_string : String = file.get_file().get_basename()
|
||||
var icon : Texture = load(file)
|
||||
if not icon:
|
||||
return
|
||||
all_icons[matching_string] = icon
|
||||
matching_string = matching_string.capitalize()
|
||||
matching_string = _get_standard_joy_name(matching_string)
|
||||
matching_string = matching_string.strip_edges()
|
||||
if add_stick_directions and matching_string.ends_with("Stick"):
|
||||
matching_icons[matching_string + " Up"] = icon
|
||||
matching_icons[matching_string + " Down"] = icon
|
||||
matching_icons[matching_string + " Left"] = icon
|
||||
matching_icons[matching_string + " Right"] = icon
|
||||
return
|
||||
if matching_string in matching_icons:
|
||||
return
|
||||
matching_icons[matching_string] = icon
|
||||
|
||||
func _prioritized_files() -> Array[String]:
|
||||
var priority_levels : Dictionary # Dictionary[String, int]
|
||||
var priortized_files : Array[String]
|
||||
for prioritized_string in prioritized_strings:
|
||||
for file in files:
|
||||
if file.containsn(prioritized_string):
|
||||
if file in priority_levels:
|
||||
priority_levels[file] += 1
|
||||
else:
|
||||
priority_levels[file] = 1
|
||||
var priority_file_map : Dictionary # Dictionary[int, Array]
|
||||
var max_priority_level : int = 0
|
||||
for file in priority_levels:
|
||||
var priority_level = priority_levels[file]
|
||||
max_priority_level = max(priority_level, max_priority_level)
|
||||
if priority_level in priority_file_map:
|
||||
priority_file_map[priority_level].append(file)
|
||||
else:
|
||||
priority_file_map[priority_level] = [file]
|
||||
while max_priority_level > 0:
|
||||
for priority_file in priority_file_map[max_priority_level]:
|
||||
priortized_files.append(priority_file)
|
||||
max_priority_level -= 1
|
||||
return priortized_files
|
||||
|
||||
func _match_icons_to_inputs() -> void:
|
||||
matching_icons.clear()
|
||||
all_icons.clear()
|
||||
for prioritized_file in _prioritized_files():
|
||||
_match_icon_to_file(prioritized_file)
|
||||
for file in files:
|
||||
_match_icon_to_file(file)
|
||||
|
||||
func get_icon(input_event : InputEvent) -> Texture:
|
||||
var specific_text = InputEventHelper.get_device_specific_text(input_event, last_joypad_device)
|
||||
if specific_text in matching_icons:
|
||||
return matching_icons[specific_text]
|
||||
return null
|
||||
|
||||
func _assign_joypad_0_to_last() -> void:
|
||||
if last_joypad_device != intial_joypad_device : return
|
||||
var connected_joypads := Input.get_connected_joypads()
|
||||
if connected_joypads.is_empty(): return
|
||||
last_joypad_device = InputEventHelper.get_device_name_by_id(connected_joypads[0])
|
||||
|
||||
func _input(event : InputEvent) -> void:
|
||||
var device_name = InputEventHelper.get_device_name(event)
|
||||
if device_name != InputEventHelper.DEVICE_GENERIC and device_name != last_joypad_device:
|
||||
last_joypad_device = device_name
|
||||
joypad_device_changed.emit()
|
||||
|
||||
func _ready() -> void:
|
||||
_assign_joypad_0_to_last()
|
||||
if files.size() == 0:
|
||||
_refresh_files()
|
||||
if matching_icons.size() == 0:
|
||||
_match_icons_to_inputs()
|
@ -0,0 +1 @@
|
||||
uid://cqigj1uumknrp
|
@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://qoexj4ptqt8a"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cqigj1uumknrp" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/input/input_icon_mapper.gd" id="1_msrpt"]
|
||||
|
||||
[node name="InputIconMapper" type="Node"]
|
||||
script = ExtResource("1_msrpt")
|
@ -0,0 +1,102 @@
|
||||
@tool
|
||||
class_name InputOptionsMenu
|
||||
extends Control
|
||||
|
||||
const ALREADY_ASSIGNED_TEXT : String = "{key} already assigned to {action}."
|
||||
const ONE_INPUT_MINIMUM_TEXT : String = "%s must have at least one key or button assigned."
|
||||
const KEY_DELETION_TEXT : String = "Are you sure you want to remove {key} from {action}?"
|
||||
|
||||
@export_enum("List", "Tree") var remapping_mode : int = 0 :
|
||||
set(value):
|
||||
remapping_mode = value
|
||||
if is_inside_tree():
|
||||
match(remapping_mode):
|
||||
0:
|
||||
%InputActionsList.show()
|
||||
%InputActionsTree.hide()
|
||||
1:
|
||||
%InputActionsList.hide()
|
||||
%InputActionsTree.show()
|
||||
|
||||
@onready var assignment_placeholder_text = $KeyAssignmentDialog.dialog_text
|
||||
|
||||
var last_input_readable_name
|
||||
|
||||
func _horizontally_align_popup_labels() -> void:
|
||||
$KeyAssignmentDialog.get_label().horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
$KeyDeletionDialog.get_label().horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
$OneInputMinimumDialog.get_label().horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
$AlreadyAssignedDialog.get_label().horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
$ResetConfirmationDialog.get_label().horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
|
||||
func _ready() -> void:
|
||||
remapping_mode = remapping_mode
|
||||
if Engine.is_editor_hint(): return
|
||||
_horizontally_align_popup_labels()
|
||||
|
||||
func _add_action_event() -> void:
|
||||
var last_input_event = $KeyAssignmentDialog.last_input_event
|
||||
last_input_readable_name = $KeyAssignmentDialog.last_input_text
|
||||
match(remapping_mode):
|
||||
0:
|
||||
%InputActionsList.add_action_event(last_input_readable_name, last_input_event)
|
||||
1:
|
||||
%InputActionsTree.add_action_event(last_input_readable_name, last_input_event)
|
||||
|
||||
func _remove_action_event(item : TreeItem) -> void:
|
||||
%InputActionsTree.remove_action_event(item)
|
||||
|
||||
func _on_reset_button_pressed() -> void:
|
||||
$ResetConfirmationDialog.popup_centered()
|
||||
|
||||
func _on_key_deletion_dialog_confirmed() -> void:
|
||||
var editing_item = %InputActionsTree.editing_item
|
||||
if is_instance_valid(editing_item):
|
||||
_remove_action_event(editing_item)
|
||||
|
||||
func _on_key_assignment_dialog_confirmed() -> void:
|
||||
_add_action_event()
|
||||
|
||||
func _open_key_assignment_dialog(action_name : String, readable_input_name : String = assignment_placeholder_text) -> void:
|
||||
$KeyAssignmentDialog.title = tr("Assign Key for {action}").format({action = action_name})
|
||||
$KeyAssignmentDialog.dialog_text = readable_input_name
|
||||
$KeyAssignmentDialog.get_ok_button().disabled = true
|
||||
$KeyAssignmentDialog.popup_centered()
|
||||
|
||||
func _on_input_actions_tree_add_button_clicked(action_name) -> void:
|
||||
_open_key_assignment_dialog(action_name)
|
||||
|
||||
func _on_input_actions_tree_remove_button_clicked(action_name, input_name) -> void:
|
||||
$KeyDeletionDialog.title = tr("Remove Key for {action}").format({action = action_name})
|
||||
$KeyDeletionDialog.dialog_text = tr(KEY_DELETION_TEXT).format({key = input_name, action = action_name})
|
||||
$KeyDeletionDialog.popup_centered()
|
||||
|
||||
func _popup_already_assigned(action_name, input_name) -> void:
|
||||
$AlreadyAssignedDialog.dialog_text = tr(ALREADY_ASSIGNED_TEXT).format({key = input_name, action = action_name})
|
||||
$AlreadyAssignedDialog.popup_centered.call_deferred()
|
||||
|
||||
func _popup_minimum_reached(action_name : String) -> void:
|
||||
$OneInputMinimumDialog.dialog_text = ONE_INPUT_MINIMUM_TEXT % action_name
|
||||
$OneInputMinimumDialog.popup_centered.call_deferred()
|
||||
|
||||
func _on_input_actions_tree_already_assigned(action_name, input_name) -> void:
|
||||
_popup_already_assigned(action_name, input_name)
|
||||
|
||||
func _on_input_actions_tree_minimum_reached(action_name) -> void:
|
||||
_popup_minimum_reached(action_name)
|
||||
|
||||
func _on_input_actions_list_already_assigned(action_name, input_name) -> void:
|
||||
_popup_already_assigned(action_name, input_name)
|
||||
|
||||
func _on_input_actions_list_minimum_reached(action_name) -> void:
|
||||
_popup_minimum_reached(action_name)
|
||||
|
||||
func _on_input_actions_list_button_clicked(action_name, readable_input_name) -> void:
|
||||
_open_key_assignment_dialog(action_name, readable_input_name)
|
||||
|
||||
func _on_reset_confirmation_dialog_confirmed() -> void:
|
||||
match(remapping_mode):
|
||||
0:
|
||||
%InputActionsList.reset()
|
||||
1:
|
||||
%InputActionsTree.reset()
|
@ -0,0 +1 @@
|
||||
uid://eborw7q4b07h
|
@ -0,0 +1,139 @@
|
||||
[gd_scene load_steps=7 format=3 uid="uid://dp3rgqaehb3xu"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://eborw7q4b07h" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/input/input_options_menu.gd" id="1"]
|
||||
[ext_resource type="PackedScene" uid="uid://qoexj4ptqt8a" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/input/input_icon_mapper.tscn" id="2_627ul"]
|
||||
[ext_resource type="Script" uid="uid://1nf36h0gms3q" path="res://addons/maaacks_menus_template/base/scripts/capture_focus.gd" id="2_wft4x"]
|
||||
[ext_resource type="Script" uid="uid://custha7r0uoic" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/input/key_assignment_dialog.gd" id="3_wsh2h"]
|
||||
[ext_resource type="PackedScene" uid="uid://bxp45814v6ydv" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/input/input_actions_list.tscn" id="4_lf2nw"]
|
||||
[ext_resource type="PackedScene" uid="uid://ci6wgl2ngd35n" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/input/input_actions_tree.tscn" id="5_b2whh"]
|
||||
|
||||
[node name="Controls" type="MarginContainer"]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_constants/margin_left = 32
|
||||
theme_override_constants/margin_top = 8
|
||||
theme_override_constants/margin_right = 32
|
||||
theme_override_constants/margin_bottom = 8
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="InputIconMapper" parent="." instance=ExtResource("2_627ul")]
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
size_flags_vertical = 4
|
||||
script = ExtResource("2_wft4x")
|
||||
search_depth = 5
|
||||
|
||||
[node name="InputMappingContainer" type="VBoxContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
alignment = 1
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer/InputMappingContainer"]
|
||||
layout_mode = 2
|
||||
text = "Actions & Inputs"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="InputActionsList" parent="VBoxContainer/InputMappingContainer" node_paths=PackedStringArray("input_icon_mapper") instance=ExtResource("4_lf2nw")]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(560, 480)
|
||||
layout_mode = 2
|
||||
input_icon_mapper = NodePath("../../../InputIconMapper")
|
||||
|
||||
[node name="InputActionsTree" parent="VBoxContainer/InputMappingContainer" node_paths=PackedStringArray("input_icon_mapper") instance=ExtResource("5_b2whh")]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(400, 480)
|
||||
layout_mode = 2
|
||||
input_icon_mapper = NodePath("../../../InputIconMapper")
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/InputMappingContainer"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="ResetButton" type="Button" parent="VBoxContainer/InputMappingContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Reset"
|
||||
|
||||
[node name="KeyAssignmentDialog" type="ConfirmationDialog" parent="."]
|
||||
title = "Assign Key"
|
||||
size = Vector2i(400, 158)
|
||||
dialog_text = "
|
||||
|
||||
|
||||
"
|
||||
script = ExtResource("3_wsh2h")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="KeyAssignmentDialog"]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 8.0
|
||||
offset_top = 8.0
|
||||
offset_right = -8.0
|
||||
offset_bottom = -49.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="InputLabel" type="Label" parent="KeyAssignmentDialog/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "None"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="InputTextEdit" type="TextEdit" parent="KeyAssignmentDialog/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
placeholder_text = "Focus here to assign inputs."
|
||||
context_menu_enabled = false
|
||||
shortcut_keys_enabled = false
|
||||
selecting_enabled = false
|
||||
deselect_on_focus_loss_enabled = false
|
||||
drag_and_drop_selection_enabled = false
|
||||
middle_mouse_paste_enabled = false
|
||||
caret_move_on_right_click = false
|
||||
|
||||
[node name="DelayTimer" type="Timer" parent="KeyAssignmentDialog"]
|
||||
unique_name_in_owner = true
|
||||
wait_time = 0.1
|
||||
one_shot = true
|
||||
|
||||
[node name="KeyDeletionDialog" type="ConfirmationDialog" parent="."]
|
||||
title = "Remove Key"
|
||||
size = Vector2i(419, 100)
|
||||
dialog_text = "Are you sure you want to remove KEY from ACTION?"
|
||||
|
||||
[node name="OneInputMinimumDialog" type="AcceptDialog" parent="."]
|
||||
title = "Cannot Remove"
|
||||
size = Vector2i(398, 100)
|
||||
|
||||
[node name="AlreadyAssignedDialog" type="AcceptDialog" parent="."]
|
||||
title = "Already Assigned"
|
||||
size = Vector2i(398, 100)
|
||||
|
||||
[node name="ResetConfirmationDialog" type="ConfirmationDialog" parent="."]
|
||||
size = Vector2i(486, 100)
|
||||
dialog_text = "Are you sure you want to reset controls back to the defaults?"
|
||||
|
||||
[connection signal="already_assigned" from="VBoxContainer/InputMappingContainer/InputActionsList" to="." method="_on_input_actions_list_already_assigned"]
|
||||
[connection signal="button_clicked" from="VBoxContainer/InputMappingContainer/InputActionsList" to="." method="_on_input_actions_list_button_clicked"]
|
||||
[connection signal="minimum_reached" from="VBoxContainer/InputMappingContainer/InputActionsList" to="." method="_on_input_actions_list_minimum_reached"]
|
||||
[connection signal="add_button_clicked" from="VBoxContainer/InputMappingContainer/InputActionsTree" to="." method="_on_input_actions_tree_add_button_clicked"]
|
||||
[connection signal="already_assigned" from="VBoxContainer/InputMappingContainer/InputActionsTree" to="." method="_on_input_actions_tree_already_assigned"]
|
||||
[connection signal="minimum_reached" from="VBoxContainer/InputMappingContainer/InputActionsTree" to="." method="_on_input_actions_tree_minimum_reached"]
|
||||
[connection signal="remove_button_clicked" from="VBoxContainer/InputMappingContainer/InputActionsTree" to="." method="_on_input_actions_tree_remove_button_clicked"]
|
||||
[connection signal="pressed" from="VBoxContainer/InputMappingContainer/HBoxContainer/ResetButton" to="." method="_on_reset_button_pressed"]
|
||||
[connection signal="confirmed" from="KeyAssignmentDialog" to="." method="_on_key_assignment_dialog_confirmed"]
|
||||
[connection signal="visibility_changed" from="KeyAssignmentDialog" to="KeyAssignmentDialog" method="_on_visibility_changed"]
|
||||
[connection signal="focus_entered" from="KeyAssignmentDialog/VBoxContainer/InputTextEdit" to="KeyAssignmentDialog" method="_on_text_edit_focus_entered"]
|
||||
[connection signal="focus_exited" from="KeyAssignmentDialog/VBoxContainer/InputTextEdit" to="KeyAssignmentDialog" method="_on_input_text_edit_focus_exited"]
|
||||
[connection signal="gui_input" from="KeyAssignmentDialog/VBoxContainer/InputTextEdit" to="KeyAssignmentDialog" method="_on_input_text_edit_gui_input"]
|
||||
[connection signal="confirmed" from="KeyDeletionDialog" to="." method="_on_key_deletion_dialog_confirmed"]
|
||||
[connection signal="confirmed" from="ResetConfirmationDialog" to="." method="_on_reset_confirmation_dialog_confirmed"]
|
@ -0,0 +1,104 @@
|
||||
extends ConfirmationDialog
|
||||
|
||||
const LISTENING_TEXT : String = "Listening for input..."
|
||||
const FOCUS_HERE_TEXT : String = "Focus here to assign inputs."
|
||||
const CONFIRM_INPUT_TEXT : String = "Press again to confirm..."
|
||||
const NO_INPUT_TEXT : String = "None"
|
||||
|
||||
enum InputConfirmation {
|
||||
SINGLE,
|
||||
DOUBLE,
|
||||
OK_BUTTON
|
||||
}
|
||||
@export var input_confirmation : InputConfirmation = InputConfirmation.SINGLE
|
||||
|
||||
var last_input_event : InputEvent
|
||||
var last_input_text : String
|
||||
var listening : bool = false
|
||||
var confirming : bool = false
|
||||
|
||||
func _record_input_event(event : InputEvent) -> void:
|
||||
last_input_text = InputEventHelper.get_text(event)
|
||||
if last_input_text.is_empty():
|
||||
return
|
||||
last_input_event = event
|
||||
%InputLabel.text = last_input_text
|
||||
get_ok_button().disabled = false
|
||||
|
||||
func _is_recordable_input(event : InputEvent) -> bool:
|
||||
return event != null and \
|
||||
(event is InputEventKey or \
|
||||
event is InputEventMouseButton or \
|
||||
event is InputEventJoypadButton or \
|
||||
(event is InputEventJoypadMotion and \
|
||||
abs(event.axis_value) > 0.5)) and \
|
||||
event.is_pressed()
|
||||
|
||||
func _start_listening() -> void:
|
||||
%InputTextEdit.placeholder_text = LISTENING_TEXT
|
||||
listening = true
|
||||
%DelayTimer.start()
|
||||
|
||||
func _stop_listening() -> void:
|
||||
%InputTextEdit.placeholder_text = FOCUS_HERE_TEXT
|
||||
listening = false
|
||||
confirming = false
|
||||
|
||||
func _on_text_edit_focus_entered() -> void:
|
||||
_start_listening.call_deferred()
|
||||
|
||||
func _on_input_text_edit_focus_exited() -> void:
|
||||
_stop_listening()
|
||||
|
||||
func _focus_on_ok() -> void:
|
||||
get_ok_button().grab_focus()
|
||||
|
||||
func _ready() -> void:
|
||||
get_ok_button().focus_neighbor_top = ^"../../%InputTextEdit"
|
||||
get_cancel_button().focus_neighbor_top = ^"../../%InputTextEdit"
|
||||
|
||||
func _input_matches_last(event : InputEvent) -> bool:
|
||||
return last_input_text == InputEventHelper.get_text(event)
|
||||
|
||||
func _is_mouse_input(event : InputEvent) -> bool:
|
||||
return event is InputEventMouse
|
||||
|
||||
func _input_confirms_choice(event : InputEvent) -> bool:
|
||||
return confirming and not _is_mouse_input(event) and _input_matches_last(event)
|
||||
|
||||
func _should_process_input_event(event : InputEvent) -> bool:
|
||||
return listening and _is_recordable_input(event) and %DelayTimer.is_stopped()
|
||||
|
||||
func _should_confirm_input_event(event : InputEvent) -> bool:
|
||||
return not _is_mouse_input(event)
|
||||
|
||||
func _confirm_choice() -> void:
|
||||
confirmed.emit()
|
||||
hide()
|
||||
|
||||
func _process_input_event(event : InputEvent) -> void:
|
||||
if not _should_process_input_event(event):
|
||||
return
|
||||
if _input_confirms_choice(event):
|
||||
confirming = false
|
||||
if input_confirmation == InputConfirmation.DOUBLE:
|
||||
_confirm_choice()
|
||||
else:
|
||||
_focus_on_ok.call_deferred()
|
||||
return
|
||||
_record_input_event(event)
|
||||
if input_confirmation == InputConfirmation.SINGLE:
|
||||
_confirm_choice()
|
||||
if _should_confirm_input_event(event):
|
||||
confirming = true
|
||||
%DelayTimer.start()
|
||||
%InputTextEdit.placeholder_text = CONFIRM_INPUT_TEXT
|
||||
|
||||
func _on_input_text_edit_gui_input(event) -> void:
|
||||
%InputTextEdit.set_deferred("text", "")
|
||||
_process_input_event(event)
|
||||
|
||||
func _on_visibility_changed() -> void:
|
||||
if visible:
|
||||
%InputLabel.text = NO_INPUT_TEXT
|
||||
%InputTextEdit.grab_focus()
|
@ -0,0 +1 @@
|
||||
uid://custha7r0uoic
|
@ -0,0 +1,13 @@
|
||||
class_name MasterOptionsMenu
|
||||
extends Control
|
||||
|
||||
func _unhandled_input(event : InputEvent) -> void:
|
||||
if not is_visible_in_tree():
|
||||
return
|
||||
if event.is_action_pressed("ui_page_down"):
|
||||
$TabContainer.current_tab = ($TabContainer.current_tab+1) % $TabContainer.get_tab_count()
|
||||
elif event.is_action_pressed("ui_page_up"):
|
||||
if $TabContainer.current_tab == 0:
|
||||
$TabContainer.current_tab = $TabContainer.get_tab_count()-1
|
||||
else:
|
||||
$TabContainer.current_tab = $TabContainer.current_tab-1
|
@ -0,0 +1 @@
|
||||
uid://c3mignmhuvvq4
|
@ -0,0 +1,23 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://bvwl11s2p0hd"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c3mignmhuvvq4" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/master_options_menu.gd" id="1_u08d5"]
|
||||
|
||||
[node name="MasterOptionsMenu" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
script = ExtResource("1_u08d5")
|
||||
|
||||
[node name="TabContainer" type="TabContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
tab_alignment = 1
|
@ -0,0 +1,25 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://hmx6o472ropw"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://bvwl11s2p0hd" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/master_options_menu.tscn" id="1_uaidt"]
|
||||
[ext_resource type="PackedScene" uid="uid://dp3rgqaehb3xu" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/input/input_options_menu.tscn" id="2_15wl6"]
|
||||
[ext_resource type="PackedScene" uid="uid://c8vnncjwqcpab" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/audio/audio_options_menu.tscn" id="3_qg4me"]
|
||||
[ext_resource type="PackedScene" uid="uid://b2numvphf2kau" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/video/video_options_menu.tscn" id="4_1t848"]
|
||||
|
||||
[node name="MasterOptionsMenu" instance=ExtResource("1_uaidt")]
|
||||
|
||||
[node name="TabContainer" parent="." index="0"]
|
||||
current_tab = 0
|
||||
|
||||
[node name="Controls" parent="TabContainer" index="1" instance=ExtResource("2_15wl6")]
|
||||
layout_mode = 2
|
||||
metadata/_tab_index = 0
|
||||
|
||||
[node name="Audio" parent="TabContainer" index="2" instance=ExtResource("3_qg4me")]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
metadata/_tab_index = 1
|
||||
|
||||
[node name="Video" parent="TabContainer" index="3" instance=ExtResource("4_1t848")]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
metadata/_tab_index = 2
|
@ -0,0 +1,45 @@
|
||||
class_name MiniOptionsMenu
|
||||
extends Control
|
||||
|
||||
@onready var mute_control = %MuteControl
|
||||
@onready var fullscreen_control = %FullscreenControl
|
||||
|
||||
@export var audio_control_scene : PackedScene
|
||||
@export var hide_busses : Array[String]
|
||||
|
||||
func _on_bus_changed(bus_value : float, bus_iter : int) -> void:
|
||||
AppSettings.set_bus_volume(bus_iter, bus_value)
|
||||
|
||||
func _add_audio_control(bus_name : String, bus_value : float, bus_iter : int) -> void:
|
||||
if audio_control_scene == null or bus_name in hide_busses or bus_name.begins_with(AppSettings.SYSTEM_BUS_NAME_PREFIX):
|
||||
return
|
||||
var audio_control = audio_control_scene.instantiate()
|
||||
%AudioControlContainer.call_deferred("add_child", audio_control)
|
||||
if audio_control is OptionControl:
|
||||
audio_control.option_section = OptionControl.OptionSections.AUDIO
|
||||
audio_control.option_name = bus_name
|
||||
audio_control.value = bus_value
|
||||
audio_control.connect("setting_changed", _on_bus_changed.bind(bus_iter))
|
||||
|
||||
func _add_audio_bus_controls() -> void:
|
||||
for bus_iter in AudioServer.bus_count:
|
||||
var bus_name : String = AppSettings.get_audio_bus_name(bus_iter)
|
||||
var linear : float = AppSettings.get_bus_volume(bus_iter)
|
||||
_add_audio_control(bus_name, linear, bus_iter)
|
||||
|
||||
func _update_ui() -> void:
|
||||
_add_audio_bus_controls()
|
||||
mute_control.value = AppSettings.is_muted()
|
||||
fullscreen_control.value = AppSettings.is_fullscreen(get_window())
|
||||
|
||||
func _sync_with_config() -> void:
|
||||
_update_ui()
|
||||
|
||||
func _ready() -> void:
|
||||
_sync_with_config()
|
||||
|
||||
func _on_mute_control_setting_changed(value : bool) -> void:
|
||||
AppSettings.set_mute(value)
|
||||
|
||||
func _on_fullscreen_control_setting_changed(value : bool) -> void:
|
||||
AppSettings.set_fullscreen_enabled(value, get_window())
|
@ -0,0 +1 @@
|
||||
uid://1c0iyo5djoxj
|
@ -0,0 +1,51 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://vh1ucj2rfbby"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://1c0iyo5djoxj" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/mini_options_menu.gd" id="1_32vm2"]
|
||||
[ext_resource type="PackedScene" uid="uid://cl416gdb1fgwr" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/option_control/slider_option_control.tscn" id="2_kpc65"]
|
||||
[ext_resource type="Script" uid="uid://1nf36h0gms3q" path="res://addons/maaacks_menus_template/base/scripts/capture_focus.gd" id="3_7qt1o"]
|
||||
[ext_resource type="PackedScene" uid="uid://bsxh6v7j0257h" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/option_control/toggle_option_control.tscn" id="4_b20fb"]
|
||||
|
||||
[node name="MiniOptionsMenu" type="VBoxContainer"]
|
||||
custom_minimum_size = Vector2(400, 260)
|
||||
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 = -130.0
|
||||
offset_right = 200.0
|
||||
offset_bottom = 130.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 4
|
||||
theme_override_constants/separation = 8
|
||||
alignment = 1
|
||||
script = ExtResource("1_32vm2")
|
||||
audio_control_scene = ExtResource("2_kpc65")
|
||||
|
||||
[node name="AudioControlContainer" type="VBoxContainer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 8
|
||||
script = ExtResource("3_7qt1o")
|
||||
search_depth = 2
|
||||
|
||||
[node name="MuteControl" parent="." instance=ExtResource("4_b20fb")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
option_name = "Mute"
|
||||
option_section = 2
|
||||
key = "Mute"
|
||||
section = "AudioSettings"
|
||||
|
||||
[node name="FullscreenControl" parent="." instance=ExtResource("4_b20fb")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
option_name = "Fullscreen"
|
||||
option_section = 3
|
||||
key = "FullscreenEnabled"
|
||||
section = "VideoSettings"
|
||||
|
||||
[connection signal="setting_changed" from="MuteControl" to="." method="_on_mute_control_setting_changed"]
|
||||
[connection signal="setting_changed" from="FullscreenControl" to="." method="_on_fullscreen_control_setting_changed"]
|
@ -0,0 +1,81 @@
|
||||
@tool
|
||||
class_name ListOptionControl
|
||||
extends OptionControl
|
||||
|
||||
## Locks Option Titles from auto-updating when editing Option Values.
|
||||
## Intentionally put first for initialization.
|
||||
@export var lock_titles : bool = false
|
||||
## Defines the list of possible values for the variable
|
||||
## this option stores in the config file.
|
||||
@export var option_values : Array :
|
||||
set(value) :
|
||||
option_values = value
|
||||
_on_option_values_changed()
|
||||
|
||||
## Defines the list of options displayed to the user.
|
||||
## Length should match with Option Values.
|
||||
@export var option_titles : Array[String] :
|
||||
set(value):
|
||||
option_titles = value
|
||||
if is_inside_tree():
|
||||
_set_option_list(option_titles)
|
||||
|
||||
var custom_option_values : Array
|
||||
|
||||
func _on_option_values_changed() -> void:
|
||||
if option_values.is_empty(): return
|
||||
custom_option_values = option_values.duplicate()
|
||||
var first_value = custom_option_values.front()
|
||||
property_type = typeof(first_value)
|
||||
_set_titles_from_values()
|
||||
|
||||
func _on_setting_changed(value : Variant) -> void:
|
||||
if value < custom_option_values.size() and value >= 0:
|
||||
super._on_setting_changed(custom_option_values[value])
|
||||
|
||||
func _set_titles_from_values() -> void:
|
||||
if lock_titles: return
|
||||
var mapped_titles : Array[String] = []
|
||||
for option_value in custom_option_values:
|
||||
mapped_titles.append(_value_title_map(option_value))
|
||||
option_titles = mapped_titles
|
||||
|
||||
func _value_title_map(value : Variant) -> String:
|
||||
return "%s" % value
|
||||
|
||||
func _match_value_to_other(value : Variant, other : Variant) -> Variant:
|
||||
# Primarily for when the editor saves floats as ints instead
|
||||
if value is int and other is float:
|
||||
return float(value)
|
||||
if value is float and other is int:
|
||||
return int(round(value))
|
||||
return value
|
||||
|
||||
func _set_value(value : Variant) -> Variant:
|
||||
if option_values.is_empty(): return
|
||||
if value == null:
|
||||
return super._set_value(-1)
|
||||
custom_option_values = option_values.duplicate()
|
||||
value = _match_value_to_other(value, custom_option_values.front())
|
||||
if value not in custom_option_values and typeof(value) == property_type:
|
||||
custom_option_values.append(value)
|
||||
custom_option_values.sort()
|
||||
_set_titles_from_values()
|
||||
if value not in option_values:
|
||||
disable_option(custom_option_values.find(value))
|
||||
value = custom_option_values.find(value)
|
||||
return super._set_value(value)
|
||||
|
||||
func _set_option_list(option_titles_list : Array) -> void:
|
||||
%OptionButton.clear()
|
||||
for option_title in option_titles_list:
|
||||
%OptionButton.add_item(option_title)
|
||||
|
||||
func disable_option(option_index : int, disabled : bool = true) -> void:
|
||||
%OptionButton.set_item_disabled(option_index, disabled)
|
||||
|
||||
func _ready() -> void:
|
||||
lock_titles = lock_titles
|
||||
option_titles = option_titles
|
||||
option_values = option_values
|
||||
super._ready()
|
@ -0,0 +1 @@
|
||||
uid://b8xqufg4re3c2
|
@ -0,0 +1,14 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://b6bl3n5mp3m1e"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://d7te75il06t7" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/option_control/option_control.tscn" id="1_blo3b"]
|
||||
[ext_resource type="Script" uid="uid://b8xqufg4re3c2" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/option_control/list_option_control.gd" id="2_kt4vl"]
|
||||
|
||||
[node name="OptionControl" instance=ExtResource("1_blo3b")]
|
||||
script = ExtResource("2_kt4vl")
|
||||
lock_titles = false
|
||||
option_values = []
|
||||
option_titles = []
|
||||
|
||||
[node name="OptionButton" type="OptionButton" parent="." index="1"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
@ -0,0 +1,140 @@
|
||||
@tool
|
||||
class_name OptionControl
|
||||
extends Control
|
||||
|
||||
signal setting_changed(value)
|
||||
|
||||
enum OptionSections{
|
||||
NONE,
|
||||
INPUT,
|
||||
AUDIO,
|
||||
VIDEO,
|
||||
GAME,
|
||||
APPLICATION,
|
||||
CUSTOM,
|
||||
}
|
||||
|
||||
const OptionSectionNames : Dictionary = {
|
||||
OptionSections.NONE : "",
|
||||
OptionSections.INPUT : AppSettings.INPUT_SECTION,
|
||||
OptionSections.AUDIO : AppSettings.AUDIO_SECTION,
|
||||
OptionSections.VIDEO : AppSettings.VIDEO_SECTION,
|
||||
OptionSections.GAME : AppSettings.GAME_SECTION,
|
||||
OptionSections.APPLICATION : AppSettings.APPLICATION_SECTION,
|
||||
OptionSections.CUSTOM : AppSettings.CUSTOM_SECTION,
|
||||
}
|
||||
|
||||
## Locks config names in case of issues with inherited scenes.
|
||||
## Intentionally put first for initialization.
|
||||
@export var lock_config_names : bool = false
|
||||
## Defines text displayed to the user.
|
||||
@export var option_name : String :
|
||||
set(value):
|
||||
var _update_config : bool = option_name.to_pascal_case() == key and not lock_config_names
|
||||
option_name = value
|
||||
if is_inside_tree():
|
||||
%OptionLabel.text = "%s%s" % [option_name, label_suffix]
|
||||
if _update_config:
|
||||
key = option_name.to_pascal_case()
|
||||
## Defines what section in the config file this option belongs under.
|
||||
@export var option_section : OptionSections :
|
||||
set(value):
|
||||
var _update_config : bool = OptionSectionNames[option_section] == section and not lock_config_names
|
||||
option_section = value
|
||||
if _update_config:
|
||||
section = OptionSectionNames[option_section]
|
||||
|
||||
@export_group("Config Names")
|
||||
## Defines the key for this option variable in the config file.
|
||||
@export var key : String
|
||||
## Defines the section for this option variable in the config file.
|
||||
@export var section : String
|
||||
@export_group("Format")
|
||||
@export var label_suffix : String = " :"
|
||||
@export_group("Properties")
|
||||
## Defines whether the option is editable, or only visible by the user.
|
||||
@export var editable : bool = true : set = set_editable
|
||||
## Defines what kind of variable this option stores in the config file.
|
||||
@export var property_type : Variant.Type = TYPE_BOOL
|
||||
|
||||
## It is advised to use an external editor to set the default value in the scene file.
|
||||
## Godot can experience a bug (caching issue?) that may undo changes.
|
||||
var default_value
|
||||
var _connected_nodes : Array
|
||||
|
||||
func _on_setting_changed(value) -> void:
|
||||
if Engine.is_editor_hint(): return
|
||||
Config.set_config(section, key, value)
|
||||
setting_changed.emit(value)
|
||||
|
||||
func _get_setting(default : Variant = null) -> Variant:
|
||||
return Config.get_config(section, key, default)
|
||||
|
||||
func _connect_option_inputs(node) -> void:
|
||||
if node in _connected_nodes: return
|
||||
if node is Button:
|
||||
if node is OptionButton:
|
||||
node.item_selected.connect(_on_setting_changed)
|
||||
elif node is ColorPickerButton:
|
||||
node.color_changed.connect(_on_setting_changed)
|
||||
else:
|
||||
node.toggled.connect(_on_setting_changed)
|
||||
_connected_nodes.append(node)
|
||||
if node is Range:
|
||||
node.value_changed.connect(_on_setting_changed)
|
||||
_connected_nodes.append(node)
|
||||
if node is LineEdit or node is TextEdit:
|
||||
node.text_changed.connect(_on_setting_changed)
|
||||
_connected_nodes.append(node)
|
||||
|
||||
func _set_value(value : Variant) -> Variant:
|
||||
if value == null:
|
||||
return
|
||||
for node in get_children():
|
||||
if node is Button:
|
||||
if node is OptionButton:
|
||||
node.select(value as int)
|
||||
elif node is ColorPickerButton:
|
||||
node.color = value as Color
|
||||
else:
|
||||
node.button_pressed = value as bool
|
||||
if node is Range:
|
||||
node.value = value as float
|
||||
if node is LineEdit or node is TextEdit:
|
||||
node.text = "%s" % value
|
||||
return value
|
||||
|
||||
func set_value(value : Variant) -> void:
|
||||
value = _set_value(value)
|
||||
_on_setting_changed(value)
|
||||
|
||||
func set_editable(value : bool = true) -> void:
|
||||
editable = value
|
||||
for node in get_children():
|
||||
if node is Button:
|
||||
node.disabled = !editable
|
||||
if node is Slider or node is SpinBox or node is LineEdit or node is TextEdit:
|
||||
node.editable = editable
|
||||
|
||||
func _ready() -> void:
|
||||
lock_config_names = lock_config_names
|
||||
option_section = option_section
|
||||
option_name = option_name
|
||||
property_type = property_type
|
||||
default_value = default_value
|
||||
_set_value(_get_setting(default_value))
|
||||
for child in get_children():
|
||||
_connect_option_inputs(child)
|
||||
child_entered_tree.connect(_connect_option_inputs)
|
||||
|
||||
func _set(property : StringName, value : Variant) -> bool:
|
||||
if property == "value":
|
||||
set_value(value)
|
||||
return true
|
||||
return false
|
||||
|
||||
func _get_property_list() -> Array[Dictionary]:
|
||||
return [
|
||||
{ "name": "value", "type": property_type, "usage": PROPERTY_USAGE_NONE},
|
||||
{ "name": "default_value", "type": property_type}
|
||||
]
|
@ -0,0 +1 @@
|
||||
uid://cafqki2b08kwu
|
@ -0,0 +1,17 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://d7te75il06t7"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cafqki2b08kwu" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/option_control/option_control.gd" id="1_jvl5q"]
|
||||
|
||||
[node name="OptionControl" type="HBoxContainer"]
|
||||
custom_minimum_size = Vector2(0, 40)
|
||||
offset_right = 400.0
|
||||
offset_bottom = 40.0
|
||||
script = ExtResource("1_jvl5q")
|
||||
default_value = false
|
||||
|
||||
[node name="OptionLabel" type="Label" parent="."]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = " :"
|
||||
vertical_alignment = 1
|
@ -0,0 +1,19 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://cl416gdb1fgwr"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://d7te75il06t7" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/option_control/option_control.tscn" id="1_16hlr"]
|
||||
|
||||
[node name="OptionControl" instance=ExtResource("1_16hlr")]
|
||||
custom_minimum_size = Vector2(0, 28)
|
||||
offset_bottom = 28.0
|
||||
property_type = 3
|
||||
default_value = 1.0
|
||||
|
||||
[node name="HSlider" type="HSlider" parent="." index="1"]
|
||||
custom_minimum_size = Vector2(256, 0)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 4
|
||||
max_value = 1.0
|
||||
step = 0.05
|
||||
value = 1.0
|
||||
tick_count = 11
|
||||
ticks_on_borders = true
|
@ -0,0 +1,8 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://bsxh6v7j0257h"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://d7te75il06t7" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/option_control/option_control.tscn" id="1_8rnmo"]
|
||||
|
||||
[node name="OptionControl" instance=ExtResource("1_8rnmo")]
|
||||
|
||||
[node name="CheckButton" type="CheckButton" parent="." index="1"]
|
||||
layout_mode = 2
|
@ -0,0 +1,9 @@
|
||||
@tool
|
||||
class_name Vector2ListOptionControl
|
||||
extends ListOptionControl
|
||||
|
||||
func _value_title_map(value : Variant) -> String:
|
||||
if value is Vector2 or value is Vector2i:
|
||||
return "%d x %d" % [value.x , value.y]
|
||||
else:
|
||||
return super._value_title_map(value)
|
@ -0,0 +1 @@
|
||||
uid://brntdgf3sv0s0
|
@ -0,0 +1,7 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://c01ayjblhcg1t"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://b6bl3n5mp3m1e" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/option_control/list_option_control.tscn" id="1_jqwiw"]
|
||||
[ext_resource type="Script" uid="uid://brntdgf3sv0s0" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/option_control/vector_2_list_option_control.gd" id="2_w33vs"]
|
||||
|
||||
[node name="OptionControl" instance=ExtResource("1_jqwiw")]
|
||||
script = ExtResource("2_w33vs")
|
@ -0,0 +1,38 @@
|
||||
class_name VideoOptionsMenu
|
||||
extends Control
|
||||
|
||||
func _preselect_resolution(window : Window) -> void:
|
||||
%ResolutionControl.value = window.size
|
||||
|
||||
func _update_resolution_options_enabled(window : Window) -> void:
|
||||
if OS.has_feature("web"):
|
||||
%ResolutionControl.editable = false
|
||||
%ResolutionControl.tooltip_text = "Disabled for web"
|
||||
elif AppSettings.is_fullscreen(window):
|
||||
%ResolutionControl.editable = false
|
||||
%ResolutionControl.tooltip_text = "Disabled for fullscreen"
|
||||
else:
|
||||
%ResolutionControl.editable = true
|
||||
%ResolutionControl.tooltip_text = "Select a screen size"
|
||||
|
||||
func _update_ui(window : Window) -> void:
|
||||
%FullscreenControl.value = AppSettings.is_fullscreen(window)
|
||||
_preselect_resolution(window)
|
||||
%VSyncControl.value = AppSettings.get_vsync(window)
|
||||
_update_resolution_options_enabled(window)
|
||||
|
||||
func _ready() -> void:
|
||||
var window : Window = get_window()
|
||||
_update_ui(window)
|
||||
window.connect("size_changed", _preselect_resolution.bind(window))
|
||||
|
||||
func _on_fullscreen_control_setting_changed(value) -> void:
|
||||
var window : Window = get_window()
|
||||
AppSettings.set_fullscreen_enabled(value, window)
|
||||
_update_resolution_options_enabled(window)
|
||||
|
||||
func _on_resolution_control_setting_changed(value) -> void:
|
||||
AppSettings.set_resolution(value, get_window(), false)
|
||||
|
||||
func _on_v_sync_control_setting_changed(value) -> void:
|
||||
AppSettings.set_vsync(value, get_window())
|
@ -0,0 +1 @@
|
||||
uid://cpe5r24151r5n
|
@ -0,0 +1,60 @@
|
||||
[gd_scene load_steps=6 format=3 uid="uid://b2numvphf2kau"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cpe5r24151r5n" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/video/video_options_menu.gd" id="1"]
|
||||
[ext_resource type="Script" uid="uid://1nf36h0gms3q" path="res://addons/maaacks_menus_template/base/scripts/capture_focus.gd" id="2_dgrai"]
|
||||
[ext_resource type="PackedScene" uid="uid://bsxh6v7j0257h" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/option_control/toggle_option_control.tscn" id="3_uded6"]
|
||||
[ext_resource type="PackedScene" uid="uid://c01ayjblhcg1t" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/option_control/vector_2_list_option_control.tscn" id="4_gwtfq"]
|
||||
[ext_resource type="PackedScene" uid="uid://b6bl3n5mp3m1e" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/option_control/list_option_control.tscn" id="5_881de"]
|
||||
|
||||
[node name="Video" type="MarginContainer"]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
theme_override_constants/margin_top = 24
|
||||
theme_override_constants/margin_bottom = 24
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
custom_minimum_size = Vector2(400, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
alignment = 1
|
||||
script = ExtResource("2_dgrai")
|
||||
search_depth = 2
|
||||
|
||||
[node name="FullscreenControl" parent="VBoxContainer" instance=ExtResource("3_uded6")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
option_name = "Fullscreen"
|
||||
option_section = 3
|
||||
key = "FullscreenEnabled"
|
||||
section = "VideoSettings"
|
||||
|
||||
[node name="ResolutionControl" parent="VBoxContainer" instance=ExtResource("4_gwtfq")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
tooltip_text = "Select a screen size"
|
||||
option_values = [Vector2i(640, 360), Vector2i(960, 540), Vector2i(1024, 576), Vector2i(1280, 720), Vector2i(1600, 900), Vector2i(1920, 1080), Vector2i(2048, 1152), Vector2i(2560, 1440), Vector2i(3200, 1800), Vector2i(3840, 2160)]
|
||||
option_titles = Array[String](["640 x 360", "960 x 540", "1024 x 576", "1280 x 720", "1600 x 900", "1920 x 1080", "2048 x 1152", "2560 x 1440", "3200 x 1800", "3840 x 2160"])
|
||||
option_name = "Resolution"
|
||||
option_section = 3
|
||||
key = "ScreenResolution"
|
||||
section = "VideoSettings"
|
||||
property_type = 6
|
||||
|
||||
[node name="VSyncControl" parent="VBoxContainer" instance=ExtResource("5_881de")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
lock_titles = true
|
||||
option_values = [0, 1, 2, 3]
|
||||
option_titles = Array[String](["Disabled", "Enabled", "Adaptive", "Mailbox"])
|
||||
option_name = "V-Sync"
|
||||
option_section = 3
|
||||
key = "V-sync"
|
||||
section = "VideoSettings"
|
||||
property_type = 2
|
||||
default_value = 0
|
||||
|
||||
[connection signal="setting_changed" from="VBoxContainer/FullscreenControl" to="." method="_on_fullscreen_control_setting_changed"]
|
||||
[connection signal="setting_changed" from="VBoxContainer/ResolutionControl" to="." method="_on_resolution_control_setting_changed"]
|
||||
[connection signal="setting_changed" from="VBoxContainer/VSyncControl" to="." method="_on_v_sync_control_setting_changed"]
|
@ -0,0 +1,6 @@
|
||||
[gd_scene format=3 uid="uid://bkcsjsk2ciff"]
|
||||
|
||||
[node name="BackgroundMusicPlayer" type="AudioStreamPlayer"]
|
||||
process_mode = 3
|
||||
autoplay = true
|
||||
bus = &"Music"
|
100
addons/maaacks_menus_template/base/scenes/opening/opening.gd
Normal file
100
addons/maaacks_menus_template/base/scenes/opening/opening.gd
Normal file
@ -0,0 +1,100 @@
|
||||
extends Control
|
||||
|
||||
@export_file("*.tscn") var next_scene : String
|
||||
@export var images : Array[Texture2D]
|
||||
@export_group("Animation")
|
||||
@export var fade_in_time : float = 0.2
|
||||
@export var fade_out_time : float = 0.2
|
||||
@export var visible_time : float = 1.6
|
||||
@export_group("Transition")
|
||||
@export var start_delay : float = 0.5
|
||||
@export var end_delay : float = 0.5
|
||||
@export var show_loading_screen : bool = false
|
||||
|
||||
var tween : Tween
|
||||
var next_image_index : int = 0
|
||||
|
||||
func _load_next_scene() -> void:
|
||||
var status = SceneLoader.get_status()
|
||||
if show_loading_screen or status != ResourceLoader.THREAD_LOAD_LOADED:
|
||||
SceneLoader.change_scene_to_loading_screen()
|
||||
else:
|
||||
SceneLoader.change_scene_to_resource()
|
||||
|
||||
func _add_textures_to_container(textures : Array[Texture2D]) -> void:
|
||||
for texture in textures:
|
||||
var texture_rect : TextureRect = TextureRect.new()
|
||||
texture_rect.texture = texture
|
||||
texture_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
texture_rect.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
texture_rect.modulate.a = 0.0
|
||||
%ImagesContainer.call_deferred("add_child", texture_rect)
|
||||
|
||||
func _event_skips_image(event : InputEvent) -> bool:
|
||||
return event.is_action_released(&"ui_accept") or event.is_action_released(&"ui_select")
|
||||
|
||||
func _event_skips_intro(event : InputEvent) -> bool:
|
||||
return event.is_action_released(&"ui_cancel")
|
||||
|
||||
func _event_is_mouse_button_released(event : InputEvent) -> bool:
|
||||
return event is InputEventMouseButton and not event.is_pressed()
|
||||
|
||||
func _unhandled_input(event : InputEvent) -> void:
|
||||
if _event_skips_intro(event):
|
||||
_load_next_scene()
|
||||
elif _event_skips_image(event):
|
||||
_show_next_image(false)
|
||||
|
||||
func _gui_input(event : InputEvent) -> void:
|
||||
if _event_is_mouse_button_released(event):
|
||||
_show_next_image(false)
|
||||
|
||||
func _transition_out() -> void:
|
||||
await get_tree().create_timer(end_delay).timeout
|
||||
_load_next_scene()
|
||||
|
||||
func _transition_in() -> void:
|
||||
await get_tree().create_timer(start_delay).timeout
|
||||
if next_image_index == 0:
|
||||
_show_next_image()
|
||||
|
||||
func _wait_and_fade_out(texture_rect : TextureRect) -> void:
|
||||
var _compare_next_index = next_image_index
|
||||
await get_tree().create_timer(visible_time, false).timeout
|
||||
if _compare_next_index != next_image_index : return
|
||||
tween = create_tween()
|
||||
tween.tween_property(texture_rect, "modulate:a", 0.0, fade_out_time)
|
||||
await tween.finished
|
||||
_show_next_image.call_deferred()
|
||||
|
||||
func _hide_previous_image() -> void:
|
||||
if tween and tween.is_running():
|
||||
tween.stop()
|
||||
if %ImagesContainer.get_child_count() == 0:
|
||||
return
|
||||
var current_image = %ImagesContainer.get_child(next_image_index - 1)
|
||||
if current_image:
|
||||
current_image.modulate.a = 0.0
|
||||
|
||||
func _show_next_image(animated : bool = true) -> void:
|
||||
_hide_previous_image()
|
||||
if next_image_index >= %ImagesContainer.get_child_count():
|
||||
if animated:
|
||||
_transition_out()
|
||||
else:
|
||||
_load_next_scene()
|
||||
return
|
||||
var texture_rect = %ImagesContainer.get_child(next_image_index)
|
||||
if animated:
|
||||
tween = create_tween()
|
||||
tween.tween_property(texture_rect, "modulate:a", 1.0, fade_in_time)
|
||||
await tween.finished
|
||||
else:
|
||||
texture_rect.modulate.a = 1.0
|
||||
next_image_index += 1
|
||||
_wait_and_fade_out(texture_rect)
|
||||
|
||||
func _ready() -> void:
|
||||
SceneLoader.load_scene(next_scene, true)
|
||||
_add_textures_to_container(images)
|
||||
_transition_in()
|
@ -0,0 +1 @@
|
||||
uid://dtco0s8byckx6
|
@ -0,0 +1,27 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://sikc02ddepyt"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dtco0s8byckx6" path="res://addons/maaacks_menus_template/base/scenes/opening/opening.gd" id="1_fcjph"]
|
||||
|
||||
[node name="Opening" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_fcjph")
|
||||
next_scene = "res://addons/maaacks_menus_template/base/scenes/menus/main_menu/main_menu.tscn"
|
||||
|
||||
[node name="BackgroundMusicPlayer" type="AudioStreamPlayer" parent="."]
|
||||
process_mode = 3
|
||||
autoplay = true
|
||||
bus = &"Music"
|
||||
|
||||
[node name="ImagesContainer" type="MarginContainer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
@ -0,0 +1,10 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://cikf3o5omnunl"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://bqqngki8bm3iq" path="res://addons/maaacks_menus_template/base/scenes/overlaid_menu/overlaid_menu_container.tscn" id="1_kverk"]
|
||||
[ext_resource type="PackedScene" uid="uid://vh1ucj2rfbby" path="res://addons/maaacks_menus_template/base/scenes/menus/options_menu/mini_options_menu.tscn" id="2_ihtu5"]
|
||||
|
||||
[node name="OverlaidMenuContainer" instance=ExtResource("1_kverk")]
|
||||
menu_scene = ExtResource("2_ihtu5")
|
||||
|
||||
[node name="TitleLabel" parent="MenuPanelContainer/MarginContainer/BoxContainer/TitleMargin" index="0"]
|
||||
text = "Options"
|
@ -0,0 +1,81 @@
|
||||
class_name PauseMenu
|
||||
extends OverlaidMenu
|
||||
|
||||
@export var options_packed_scene : PackedScene
|
||||
@export_file("*.tscn") var main_menu_scene : String
|
||||
|
||||
var popup_open : Node
|
||||
|
||||
func close_popup() -> void:
|
||||
if popup_open != null:
|
||||
popup_open.hide()
|
||||
popup_open = null
|
||||
|
||||
func _disable_focus() -> void:
|
||||
for child in %MenuButtons.get_children():
|
||||
if child is Control:
|
||||
child.focus_mode = FOCUS_NONE
|
||||
|
||||
func _enable_focus() -> void:
|
||||
for child in %MenuButtons.get_children():
|
||||
if child is Control:
|
||||
child.focus_mode = FOCUS_ALL
|
||||
|
||||
func _load_scene(scene_path: String) -> void:
|
||||
_scene_tree.paused = false
|
||||
SceneLoader.load_scene(scene_path)
|
||||
|
||||
func open_options_menu() -> void:
|
||||
var options_scene := options_packed_scene.instantiate()
|
||||
add_child(options_scene)
|
||||
_disable_focus.call_deferred()
|
||||
await options_scene.tree_exiting
|
||||
_enable_focus.call_deferred()
|
||||
|
||||
func _handle_cancel_input() -> void:
|
||||
if popup_open != null:
|
||||
close_popup()
|
||||
else:
|
||||
super._handle_cancel_input()
|
||||
|
||||
func _hide_exit_for_web() -> void:
|
||||
if OS.has_feature("web"):
|
||||
%ExitButton.hide()
|
||||
|
||||
func _hide_options_if_unset() -> void:
|
||||
if options_packed_scene == null:
|
||||
%OptionsButton.hide()
|
||||
|
||||
func _hide_main_menu_if_unset() -> void:
|
||||
if main_menu_scene.is_empty():
|
||||
%MainMenuButton.hide()
|
||||
|
||||
func _ready() -> void:
|
||||
_hide_exit_for_web()
|
||||
_hide_options_if_unset()
|
||||
_hide_main_menu_if_unset()
|
||||
|
||||
func _on_restart_button_pressed() -> void:
|
||||
%ConfirmRestart.popup_centered()
|
||||
popup_open = %ConfirmRestart
|
||||
|
||||
func _on_options_button_pressed() -> void:
|
||||
open_options_menu()
|
||||
|
||||
func _on_main_menu_button_pressed() -> void:
|
||||
%ConfirmMainMenu.popup_centered()
|
||||
popup_open = %ConfirmMainMenu
|
||||
|
||||
func _on_exit_button_pressed() -> void:
|
||||
%ConfirmExit.popup_centered()
|
||||
popup_open = %ConfirmExit
|
||||
|
||||
func _on_confirm_restart_confirmed() -> void:
|
||||
SceneLoader.reload_current_scene()
|
||||
close()
|
||||
|
||||
func _on_confirm_main_menu_confirmed() -> void:
|
||||
_load_scene(main_menu_scene)
|
||||
|
||||
func _on_confirm_exit_confirmed() -> void:
|
||||
get_tree().quit()
|
@ -0,0 +1 @@
|
||||
uid://uidwhqh4fyhj
|
@ -0,0 +1,68 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://b5cd6sa8qq4vc"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://wny2d8dvp3ok" path="res://addons/maaacks_menus_template/base/scenes/overlaid_menu/overlaid_menu.tscn" id="1_gm3uv"]
|
||||
[ext_resource type="Script" uid="uid://uidwhqh4fyhj" path="res://addons/maaacks_menus_template/base/scenes/overlaid_menu/menus/pause_menu.gd" id="2_0ln3r"]
|
||||
[ext_resource type="PackedScene" uid="uid://cikf3o5omnunl" path="res://addons/maaacks_menus_template/base/scenes/overlaid_menu/menus/mini_options_overlaid_menu.tscn" id="3_kv70e"]
|
||||
|
||||
[node name="PauseMenu" instance=ExtResource("1_gm3uv")]
|
||||
process_mode = 3
|
||||
script = ExtResource("2_0ln3r")
|
||||
options_packed_scene = ExtResource("3_kv70e")
|
||||
main_menu_scene = "res://addons/maaacks_menus_template/base/scenes/menus/main_menu/main_menu.tscn"
|
||||
pauses_game = true
|
||||
|
||||
[node name="MarginContainer" parent="MenuPanelContainer" index="0"]
|
||||
theme_override_constants/margin_left = 64
|
||||
theme_override_constants/margin_right = 64
|
||||
|
||||
[node name="TitleLabel" parent="MenuPanelContainer/MarginContainer/BoxContainer/TitleMargin" index="0"]
|
||||
text = "Paused"
|
||||
|
||||
[node name="MenuButtonsMargin" parent="MenuPanelContainer/MarginContainer/BoxContainer" index="2"]
|
||||
theme_override_constants/margin_top = 16
|
||||
theme_override_constants/margin_bottom = 16
|
||||
|
||||
[node name="CloseButton" parent="MenuPanelContainer/MarginContainer/BoxContainer/MenuButtonsMargin/MenuButtons" index="0"]
|
||||
text = "Resume"
|
||||
|
||||
[node name="RestartButton" type="Button" parent="MenuPanelContainer/MarginContainer/BoxContainer/MenuButtonsMargin/MenuButtons" index="1"]
|
||||
layout_mode = 2
|
||||
text = "Restart"
|
||||
|
||||
[node name="OptionsButton" type="Button" parent="MenuPanelContainer/MarginContainer/BoxContainer/MenuButtonsMargin/MenuButtons" index="2"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Options"
|
||||
|
||||
[node name="MainMenuButton" type="Button" parent="MenuPanelContainer/MarginContainer/BoxContainer/MenuButtonsMargin/MenuButtons" index="3"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Main Menu"
|
||||
|
||||
[node name="ExitButton" type="Button" parent="MenuPanelContainer/MarginContainer/BoxContainer/MenuButtonsMargin/MenuButtons" index="4"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Exit Game"
|
||||
|
||||
[node name="ConfirmRestart" type="ConfirmationDialog" parent="." index="2"]
|
||||
unique_name_in_owner = true
|
||||
auto_translate_mode = 1
|
||||
dialog_text = "Restart the game?"
|
||||
|
||||
[node name="ConfirmMainMenu" type="ConfirmationDialog" parent="." index="3"]
|
||||
unique_name_in_owner = true
|
||||
auto_translate_mode = 1
|
||||
dialog_text = "Go back to main menu?"
|
||||
|
||||
[node name="ConfirmExit" type="ConfirmationDialog" parent="." index="4"]
|
||||
unique_name_in_owner = true
|
||||
auto_translate_mode = 1
|
||||
dialog_text = "Quit the game?"
|
||||
|
||||
[connection signal="pressed" from="MenuPanelContainer/MarginContainer/BoxContainer/MenuButtonsMargin/MenuButtons/RestartButton" to="." method="_on_restart_button_pressed"]
|
||||
[connection signal="pressed" from="MenuPanelContainer/MarginContainer/BoxContainer/MenuButtonsMargin/MenuButtons/OptionsButton" to="." method="_on_options_button_pressed"]
|
||||
[connection signal="pressed" from="MenuPanelContainer/MarginContainer/BoxContainer/MenuButtonsMargin/MenuButtons/MainMenuButton" to="." method="_on_main_menu_button_pressed"]
|
||||
[connection signal="pressed" from="MenuPanelContainer/MarginContainer/BoxContainer/MenuButtonsMargin/MenuButtons/ExitButton" to="." method="_on_exit_button_pressed"]
|
||||
[connection signal="confirmed" from="ConfirmRestart" to="." method="_on_confirm_restart_confirmed"]
|
||||
[connection signal="confirmed" from="ConfirmMainMenu" to="." method="_on_confirm_main_menu_confirmed"]
|
||||
[connection signal="confirmed" from="ConfirmExit" to="." method="_on_confirm_exit_confirmed"]
|
@ -0,0 +1,49 @@
|
||||
@tool
|
||||
class_name OverlaidMenu
|
||||
extends Control
|
||||
|
||||
@export var pauses_game : bool = false :
|
||||
set(value):
|
||||
pauses_game = value
|
||||
if pauses_game:
|
||||
process_mode = PROCESS_MODE_ALWAYS
|
||||
else:
|
||||
process_mode = PROCESS_MODE_INHERIT
|
||||
@export var makes_mouse_visible : bool = true
|
||||
|
||||
var _initial_pause_state : bool = false
|
||||
var _initial_focus_mode : FocusMode = FOCUS_ALL
|
||||
var _initial_mouse_mode : Input.MouseMode
|
||||
var _initial_focus_control
|
||||
var _scene_tree : SceneTree
|
||||
|
||||
func close() -> void:
|
||||
_scene_tree.paused = _initial_pause_state
|
||||
Input.set_mouse_mode(_initial_mouse_mode)
|
||||
if is_instance_valid(_initial_focus_control) and _initial_focus_control.is_inside_tree():
|
||||
_initial_focus_control.focus_mode = _initial_focus_mode
|
||||
_initial_focus_control.grab_focus()
|
||||
queue_free()
|
||||
|
||||
func _handle_cancel_input() -> void:
|
||||
close()
|
||||
|
||||
func _unhandled_input(event : InputEvent) -> void:
|
||||
if event.is_action_pressed("ui_cancel"):
|
||||
_handle_cancel_input()
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
func _on_close_button_pressed() -> void:
|
||||
close()
|
||||
|
||||
func _enter_tree() -> void:
|
||||
_scene_tree = get_tree()
|
||||
_initial_pause_state = _scene_tree.paused
|
||||
_initial_mouse_mode = Input.get_mouse_mode()
|
||||
_initial_focus_control = get_viewport().gui_get_focus_owner()
|
||||
if _initial_focus_control:
|
||||
_initial_focus_mode = _initial_focus_control.focus_mode
|
||||
if Engine.is_editor_hint(): return
|
||||
_scene_tree.paused = pauses_game or _initial_pause_state
|
||||
if makes_mouse_visible:
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
|
@ -0,0 +1 @@
|
||||
uid://xfugmpspqbcc
|
@ -0,0 +1,88 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://wny2d8dvp3ok"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://xfugmpspqbcc" path="res://addons/maaacks_menus_template/base/scenes/overlaid_menu/overlaid_menu.gd" id="1_euyj1"]
|
||||
[ext_resource type="Script" uid="uid://1nf36h0gms3q" path="res://addons/maaacks_menus_template/base/scripts/capture_focus.gd" id="2_6ani0"]
|
||||
|
||||
[node name="OverlaidMenu" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_euyj1")
|
||||
|
||||
[node name="BackgroundColor" type="ColorRect" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
color = Color(0, 0, 0, 0.12549)
|
||||
|
||||
[node name="MenuPanelContainer" type="PanelContainer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
process_mode = 3
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -16.0
|
||||
offset_top = -16.0
|
||||
offset_right = 16.0
|
||||
offset_bottom = 16.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="MenuPanelContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 16
|
||||
theme_override_constants/margin_top = 16
|
||||
theme_override_constants/margin_right = 16
|
||||
theme_override_constants/margin_bottom = 16
|
||||
|
||||
[node name="BoxContainer" type="BoxContainer" parent="MenuPanelContainer/MarginContainer"]
|
||||
layout_mode = 2
|
||||
vertical = true
|
||||
|
||||
[node name="TitleMargin" type="MarginContainer" parent="MenuPanelContainer/MarginContainer/BoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="TitleLabel" type="Label" parent="MenuPanelContainer/MarginContainer/BoxContainer/TitleMargin"]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 24
|
||||
text = "Menu"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="DescriptionMargin" type="MarginContainer" parent="MenuPanelContainer/MarginContainer/BoxContainer"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="DescriptionLabel" type="RichTextLabel" parent="MenuPanelContainer/MarginContainer/BoxContainer/DescriptionMargin"]
|
||||
layout_mode = 2
|
||||
bbcode_enabled = true
|
||||
|
||||
[node name="MenuButtonsMargin" type="MarginContainer" parent="MenuPanelContainer/MarginContainer/BoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="MenuButtons" type="BoxContainer" parent="MenuPanelContainer/MarginContainer/BoxContainer/MenuButtonsMargin"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(128, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
size_flags_vertical = 3
|
||||
theme_override_constants/separation = 16
|
||||
alignment = 1
|
||||
vertical = true
|
||||
script = ExtResource("2_6ani0")
|
||||
|
||||
[node name="CloseButton" type="Button" parent="MenuPanelContainer/MarginContainer/BoxContainer/MenuButtonsMargin/MenuButtons"]
|
||||
layout_mode = 2
|
||||
text = "Close"
|
||||
|
||||
[connection signal="pressed" from="MenuPanelContainer/MarginContainer/BoxContainer/MenuButtonsMargin/MenuButtons/CloseButton" to="." method="_on_close_button_pressed"]
|
@ -0,0 +1,14 @@
|
||||
@tool
|
||||
class_name OverlaidMenuContainer
|
||||
extends OverlaidMenu
|
||||
|
||||
@export var menu_scene : PackedScene :
|
||||
set(value):
|
||||
var _value_changed = menu_scene != value
|
||||
menu_scene = value
|
||||
if _value_changed:
|
||||
for child in %MenuContainer.get_children():
|
||||
child.queue_free()
|
||||
if menu_scene:
|
||||
var _instance = menu_scene.instantiate()
|
||||
%MenuContainer.add_child(_instance)
|
@ -0,0 +1 @@
|
||||
uid://droejgtv8bu0s
|
@ -0,0 +1,21 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://bqqngki8bm3iq"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://wny2d8dvp3ok" path="res://addons/maaacks_menus_template/base/scenes/overlaid_menu/overlaid_menu.tscn" id="1_xgkve"]
|
||||
[ext_resource type="Script" uid="uid://droejgtv8bu0s" path="res://addons/maaacks_menus_template/base/scenes/overlaid_menu/overlaid_menu_container.gd" id="2_owcue"]
|
||||
|
||||
[node name="OverlaidMenuContainer" instance=ExtResource("1_xgkve")]
|
||||
script = ExtResource("2_owcue")
|
||||
menu_scene = null
|
||||
|
||||
[node name="MenuContainer" type="MarginContainer" parent="MenuPanelContainer/MarginContainer/BoxContainer" index="2"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="MenuButtonsMargin" parent="MenuPanelContainer/MarginContainer/BoxContainer" index="3"]
|
||||
theme_override_constants/margin_top = 16
|
||||
theme_override_constants/margin_bottom = 16
|
||||
|
||||
[node name="CloseButton" parent="MenuPanelContainer/MarginContainer/BoxContainer/MenuButtonsMargin/MenuButtons" index="0"]
|
||||
size_flags_horizontal = 4
|
||||
text = "Back"
|
@ -0,0 +1,155 @@
|
||||
@tool
|
||||
class_name APIClient
|
||||
extends Node
|
||||
|
||||
|
||||
signal response_received(response_body)
|
||||
signal request_failed(error)
|
||||
|
||||
const RESULT_CANT_CONNECT = "Failed to connect"
|
||||
const RESULT_CANT_RESOLVE = "Failed to resolve"
|
||||
const RESULT_CONNECTION_ERROR = "Connection error"
|
||||
const RESULT_TIMEOUT = "Connection timeout"
|
||||
const RESULT_SERVER_ERROR = "Server error"
|
||||
const REQUEST_FAILED = "Error in the request"
|
||||
const REQUEST_TIMEOUT = "Request timed out on the client side"
|
||||
const URL_NOT_SET = "URL parameter is not set"
|
||||
const PARSE_FAILED = "Parsing failed"
|
||||
|
||||
## Location of the API endpoint.
|
||||
@export var api_url : String
|
||||
## HTTP request method to use. Typically GET or POST.
|
||||
@export var request_method : HTTPClient.Method = HTTPClient.METHOD_POST
|
||||
@export_group("Advanced")
|
||||
## Location of an API key file, if authorization is required by the endpoint.
|
||||
@export_file("*.txt") var api_key_file : String
|
||||
## Time in seconds before the request fails due to timeout.
|
||||
@export var request_timeout : float = 0.0
|
||||
@export var _send_request_action : bool = false :
|
||||
set(value):
|
||||
if value and Engine.is_editor_hint():
|
||||
request()
|
||||
# For Godot 4.4
|
||||
# @export_tool_button("Send Request") var _send_request_action = request
|
||||
|
||||
|
||||
@onready var _http_request : HTTPRequest = $HTTPRequest
|
||||
@onready var _timeout_timer : Timer= $TimeoutTimer
|
||||
|
||||
## State flag for whether the connection has timed out on the client-side.
|
||||
var timed_out : bool = false
|
||||
|
||||
func get_http_request() -> HTTPRequest:
|
||||
return _http_request
|
||||
|
||||
func get_api_key() -> String:
|
||||
if api_key_file.is_empty():
|
||||
return ""
|
||||
var file := FileAccess.open(api_key_file, FileAccess.READ)
|
||||
var error := FileAccess.get_open_error()
|
||||
if error != OK:
|
||||
push_error("API Key reading error: %d" % error)
|
||||
return ""
|
||||
var content = file.get_as_text()
|
||||
file.close()
|
||||
return content
|
||||
|
||||
func get_api_url() -> String:
|
||||
return api_url
|
||||
|
||||
func get_api_method() -> int:
|
||||
return request_method
|
||||
|
||||
func mock_empty_body() -> String:
|
||||
var form : Dictionary = {}
|
||||
return JSON.stringify(form)
|
||||
|
||||
func mock_request(body : String):
|
||||
await(get_tree().create_timer(10.0).timeout)
|
||||
_on_request_completed(HTTPRequest.RESULT_SUCCESS, "200", [], body)
|
||||
|
||||
func request(body : String = "", request_headers : Array = []) -> void:
|
||||
var local_http_request : HTTPRequest = get_http_request()
|
||||
var key : String = get_api_key()
|
||||
var url : String = get_api_url()
|
||||
var method : int = get_api_method()
|
||||
if url.is_empty():
|
||||
request_failed.emit(URL_NOT_SET)
|
||||
push_error(URL_NOT_SET)
|
||||
return
|
||||
request_headers.append("Content-Type: application/json")
|
||||
if key:
|
||||
request_headers.append("x-api-key: %s" % key)
|
||||
if request_timeout > 0.0:
|
||||
local_http_request.timeout = request_timeout
|
||||
var error = local_http_request.request(url, request_headers, method, body)
|
||||
if error != OK:
|
||||
request_failed.emit(REQUEST_FAILED)
|
||||
push_error("HTTP Request error: %d" % error)
|
||||
return
|
||||
if request_timeout > 0.0:
|
||||
_timeout_timer.start(request_timeout + 1.0)
|
||||
|
||||
func request_raw(data : PackedByteArray = [], request_headers : Array = []) -> void:
|
||||
var local_http_request : HTTPRequest = get_http_request()
|
||||
var key : String = get_api_key()
|
||||
var url : String = get_api_url()
|
||||
var method : int = get_api_method()
|
||||
if url.is_empty():
|
||||
request_failed.emit(URL_NOT_SET)
|
||||
push_error(URL_NOT_SET)
|
||||
return
|
||||
request_headers.append("Content-Type: application/json")
|
||||
if key:
|
||||
request_headers.append("x-api-key: %s" % key)
|
||||
if request_timeout > 0.0:
|
||||
local_http_request.timeout = request_timeout
|
||||
var error = local_http_request.request_raw(url, request_headers, method, data)
|
||||
if error != OK:
|
||||
request_failed.emit(REQUEST_FAILED)
|
||||
push_error("HTTP Request error: %d" % error)
|
||||
return
|
||||
if request_timeout > 0.0:
|
||||
_timeout_timer.start(request_timeout + 1.0)
|
||||
|
||||
func _on_request_completed(result, response_code, headers, body) -> void:
|
||||
# If already timed out on client-side, then return.
|
||||
if timed_out: return
|
||||
_timeout_timer.stop()
|
||||
if result == HTTPRequest.RESULT_SUCCESS:
|
||||
var body_string : String
|
||||
if body is PackedByteArray:
|
||||
body_string = body.get_string_from_utf8()
|
||||
elif body is String:
|
||||
body_string = body
|
||||
var json := JSON.new()
|
||||
var error = json.parse(body_string)
|
||||
if error != OK:
|
||||
request_failed.emit(PARSE_FAILED)
|
||||
push_error("Parse error: %d" % error)
|
||||
return
|
||||
var parsed_data = json.data
|
||||
response_received.emit(json.data)
|
||||
else:
|
||||
var error_message : String
|
||||
match(result):
|
||||
HTTPRequest.RESULT_CANT_CONNECT:
|
||||
error_message = RESULT_CANT_CONNECT
|
||||
HTTPRequest.RESULT_CANT_RESOLVE:
|
||||
error_message = RESULT_CANT_RESOLVE
|
||||
HTTPRequest.RESULT_CONNECTION_ERROR:
|
||||
error_message = RESULT_CONNECTION_ERROR
|
||||
HTTPRequest.RESULT_TIMEOUT:
|
||||
error_message = RESULT_TIMEOUT
|
||||
_:
|
||||
error_message = RESULT_SERVER_ERROR
|
||||
request_failed.emit(error_message)
|
||||
push_error("HTTP Result error: %d" % result)
|
||||
|
||||
func _on_http_request_request_completed(result, response_code, headers, body) -> void:
|
||||
_on_request_completed(result, response_code, headers, body)
|
||||
|
||||
func _on_timeout_timer_timeout() -> void:
|
||||
timed_out = true
|
||||
request_failed.emit(REQUEST_TIMEOUT)
|
||||
push_warning(REQUEST_TIMEOUT)
|
@ -0,0 +1 @@
|
||||
uid://s0j82xowl675
|
@ -0,0 +1,13 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://drhhakm62vjsy"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://s0j82xowl675" path="res://addons/maaacks_menus_template/base/scenes/utilities/api_client.gd" id="1_c5ofg"]
|
||||
|
||||
[node name="APIClient" type="Node"]
|
||||
script = ExtResource("1_c5ofg")
|
||||
|
||||
[node name="HTTPRequest" type="HTTPRequest" parent="."]
|
||||
|
||||
[node name="TimeoutTimer" type="Timer" parent="."]
|
||||
|
||||
[connection signal="request_completed" from="HTTPRequest" to="." method="_on_http_request_request_completed"]
|
||||
[connection signal="timeout" from="TimeoutTimer" to="." method="_on_timeout_timer_timeout"]
|
@ -0,0 +1,285 @@
|
||||
@tool
|
||||
## Utility node for downloading and unzipping a file from a URL to an extraction destination.
|
||||
class_name DownloadAndExtract
|
||||
extends Node
|
||||
|
||||
## Sent when the run has completed.
|
||||
signal run_completed
|
||||
## Sent when a response is received from the server.
|
||||
signal response_received(response_body)
|
||||
## Sent when the run has failed or exited early for any reason.
|
||||
signal run_failed(error : String)
|
||||
## Sent when the zip file has finished saving.
|
||||
signal zip_saved
|
||||
|
||||
const TEMPORARY_ZIP_PATH = "res://temp.zip"
|
||||
const RESULT_CANT_CONNECT = "Failed to connect"
|
||||
const RESULT_CANT_RESOLVE = "Failed to resolve"
|
||||
const RESULT_CONNECTION_ERROR = "Connection error"
|
||||
const RESULT_TIMEOUT = "Connection timeout"
|
||||
const RESULT_SERVER_ERROR = "Server error"
|
||||
const REQUEST_FAILED = "Error in the request"
|
||||
const REQUEST_TIMEOUT = "Request timed out on the client side"
|
||||
const DOWNLOAD_IN_PROGRESS = "Download already in progress"
|
||||
const EXTRACT_IN_PROGRESS = "Extract already in progress"
|
||||
const DELETE_IN_PROGRESS = "Delete already in progress"
|
||||
const FAILED_TO_SAVE_ZIP_FILE = "Failed to save the zip file"
|
||||
const FAILED_TO_MAKE_EXTRACT_DIR = "Failed to make extract directory"
|
||||
const FAILED_TO_READ_ZIP_FILE = "Failed to read the zip file"
|
||||
const DOWNLOADED_ZIP_FILE_DOESNT_EXIST = "The downloaded ZIP file doesn't exist"
|
||||
const URL_NOT_SET = "URL parameter is not set"
|
||||
|
||||
enum Stage{
|
||||
NONE,
|
||||
DOWNLOAD,
|
||||
SAVE,
|
||||
EXTRACT,
|
||||
DELETE,
|
||||
}
|
||||
|
||||
## Location of the zip file to be downloaded.
|
||||
@export var zip_url : String
|
||||
## Path where the zipped files are to be extracted.
|
||||
@export_dir var extract_path : String
|
||||
@export_group("Advanced")
|
||||
## If not empty, zipped file paths that do not contain a match to the string will be ignored.
|
||||
@export var path_match_string : String = ""
|
||||
## Assuming zip file contains a single base directory, the flag copies all of the contents,
|
||||
## as if they were at the base of the zip file. It never makes the base directory locally.
|
||||
@export var skip_base_zip_dir : bool = false
|
||||
## Forces a download and extraction even if the files already exist.
|
||||
@export var force : bool = false
|
||||
## Path where the zip file will be stored.
|
||||
@export var zip_file_path : String = TEMPORARY_ZIP_PATH
|
||||
## Flag to delete a downloaded zip file after the contents are extracted.
|
||||
@export var delete_zip_file : bool = true
|
||||
## Ratio of processing time that should be spent on extracting files.
|
||||
@export_range(0.0, 1.0) var process_time_ratio : float = 0.75
|
||||
## Seconds of delay added between saving the zip file and extracting it.
|
||||
@export_range(0.0, 3.0) var extraction_delay : float = 0.25
|
||||
## Duration to wait before the request times out.
|
||||
@export var request_timeout : float = 0.0
|
||||
@export var _start_run_action : bool = false :
|
||||
set(value):
|
||||
if value and Engine.is_editor_hint():
|
||||
run()
|
||||
# For Godot 4.4
|
||||
# @export_tool_button("Download & Extract") var _start_run_action = run
|
||||
|
||||
|
||||
@onready var _http_request : HTTPRequest = $HTTPRequest
|
||||
@onready var _timeout_timer : Timer= $TimeoutTimer
|
||||
|
||||
## State flag for whether the connection has timed out on the client-side.
|
||||
var timed_out : bool = false
|
||||
## Current stage of the download and extract process.
|
||||
var stage : Stage = Stage.NONE
|
||||
var zip_reader : ZIPReader = ZIPReader.new()
|
||||
var zipped_file_paths : PackedStringArray = []
|
||||
var extracted_file_paths : Array[String] = []
|
||||
var skipped_file_paths : Array[String] = []
|
||||
var downloaded_zip_file : bool = false
|
||||
var base_zip_path : String = ""
|
||||
var _save_progress : float = 0.0
|
||||
|
||||
func get_http_request() -> HTTPRequest:
|
||||
return _http_request
|
||||
|
||||
func get_zip_url() -> String:
|
||||
return zip_url
|
||||
|
||||
func _zip_exists() -> bool:
|
||||
return FileAccess.file_exists(zip_file_path)
|
||||
|
||||
func get_request_method() -> int:
|
||||
return HTTPClient.METHOD_GET
|
||||
|
||||
## Sends the request to download the target zip file, and then extracts the contents.
|
||||
func run(request_headers : Array = []) -> void:
|
||||
if stage == Stage.DOWNLOAD:
|
||||
run_failed.emit(DOWNLOAD_IN_PROGRESS)
|
||||
push_warning(DOWNLOAD_IN_PROGRESS)
|
||||
return
|
||||
if _zip_exists() and not force:
|
||||
_extract_files.call_deferred()
|
||||
return
|
||||
var local_http_request : HTTPRequest = get_http_request()
|
||||
var url : String = get_zip_url()
|
||||
var method : int = get_request_method()
|
||||
if url.is_empty():
|
||||
run_failed.emit(URL_NOT_SET)
|
||||
push_error(URL_NOT_SET)
|
||||
return
|
||||
if request_timeout > 0.0:
|
||||
local_http_request.timeout = request_timeout
|
||||
var error = local_http_request.request(url, request_headers, method)
|
||||
if error != OK:
|
||||
run_failed.emit(REQUEST_FAILED)
|
||||
push_error("HTTP Request error: %d" % error)
|
||||
return
|
||||
if request_timeout > 0.0:
|
||||
_timeout_timer.start(request_timeout + 1.0)
|
||||
stage = Stage.DOWNLOAD
|
||||
|
||||
func _delete_zip_file() -> void:
|
||||
if not delete_zip_file or not downloaded_zip_file: return
|
||||
if stage == Stage.DELETE:
|
||||
run_failed.emit(DELETE_IN_PROGRESS)
|
||||
push_warning(DELETE_IN_PROGRESS)
|
||||
return
|
||||
stage = Stage.DELETE
|
||||
DirAccess.remove_absolute(zip_file_path)
|
||||
downloaded_zip_file = false
|
||||
|
||||
func _save_zip_file(body : PackedByteArray) -> void:
|
||||
stage = Stage.SAVE
|
||||
var file = FileAccess.open(zip_file_path, FileAccess.WRITE)
|
||||
if not file:
|
||||
run_failed.emit(FAILED_TO_SAVE_ZIP_FILE)
|
||||
push_error(FAILED_TO_SAVE_ZIP_FILE)
|
||||
return
|
||||
file.store_buffer(body)
|
||||
file.close()
|
||||
downloaded_zip_file = true
|
||||
zip_saved.emit()
|
||||
|
||||
func extract_path_exists() -> bool:
|
||||
return DirAccess.dir_exists_absolute(extract_path)
|
||||
|
||||
func _make_extract_path() -> void:
|
||||
var err := DirAccess.make_dir_recursive_absolute(extract_path)
|
||||
if err != OK:
|
||||
run_failed.emit(FAILED_TO_MAKE_EXTRACT_DIR)
|
||||
push_error(FAILED_TO_MAKE_EXTRACT_DIR)
|
||||
|
||||
func _extract_files() -> void:
|
||||
if stage == Stage.EXTRACT:
|
||||
run_failed.emit(EXTRACT_IN_PROGRESS)
|
||||
push_warning(EXTRACT_IN_PROGRESS)
|
||||
return
|
||||
stage = Stage.EXTRACT
|
||||
if not _zip_exists():
|
||||
run_failed.emit(DOWNLOADED_ZIP_FILE_DOESNT_EXIST)
|
||||
push_error(DOWNLOADED_ZIP_FILE_DOESNT_EXIST)
|
||||
return
|
||||
if not extract_path_exists(): _make_extract_path()
|
||||
var error = zip_reader.open(zip_file_path)
|
||||
if error != OK:
|
||||
run_failed.emit(FAILED_TO_READ_ZIP_FILE)
|
||||
push_error("ZIP Reader error: %d" % error)
|
||||
return
|
||||
zipped_file_paths = zip_reader.get_files()
|
||||
if skip_base_zip_dir:
|
||||
base_zip_path = zipped_file_paths[0]
|
||||
if not base_zip_path.ends_with("/"):
|
||||
push_warning("Skipping extracting base path, but it is not a directory.")
|
||||
zipped_file_paths.remove_at(0)
|
||||
|
||||
func _on_request_completed(result, response_code, headers, body) -> void:
|
||||
# If already timed out on client-side, then return.
|
||||
if timed_out: return
|
||||
_timeout_timer.stop()
|
||||
if _zip_exists(): _delete_zip_file()
|
||||
if result == HTTPRequest.RESULT_SUCCESS:
|
||||
if body is PackedByteArray:
|
||||
response_received.emit(body)
|
||||
_save_zip_file(body)
|
||||
_save_progress = 0.0
|
||||
var tween = create_tween()
|
||||
tween.tween_property(self, "_save_progress", 1.0, extraction_delay)
|
||||
await tween.finished
|
||||
_extract_files.call_deferred()
|
||||
else:
|
||||
var error_message : String
|
||||
match(result):
|
||||
HTTPRequest.RESULT_CANT_CONNECT:
|
||||
error_message = RESULT_CANT_CONNECT
|
||||
HTTPRequest.RESULT_CANT_RESOLVE:
|
||||
error_message = RESULT_CANT_RESOLVE
|
||||
HTTPRequest.RESULT_CONNECTION_ERROR:
|
||||
error_message = RESULT_CONNECTION_ERROR
|
||||
HTTPRequest.RESULT_TIMEOUT:
|
||||
error_message = RESULT_TIMEOUT
|
||||
_:
|
||||
error_message = RESULT_SERVER_ERROR
|
||||
run_failed.emit(error_message)
|
||||
push_error("HTTP Result error: %d" % result)
|
||||
|
||||
func _on_http_request_request_completed(result, response_code, headers, body) -> void:
|
||||
_on_request_completed(result, response_code, headers, body)
|
||||
|
||||
func _on_timeout_timer_timeout() -> void:
|
||||
timed_out = true
|
||||
run_failed.emit(REQUEST_TIMEOUT)
|
||||
push_warning(REQUEST_TIMEOUT)
|
||||
|
||||
func get_progress() -> float:
|
||||
if stage == Stage.DOWNLOAD:
|
||||
return get_download_progress()
|
||||
elif stage == Stage.SAVE:
|
||||
return get_save_progress()
|
||||
elif stage == Stage.EXTRACT:
|
||||
return get_extraction_progress()
|
||||
return 0.0
|
||||
|
||||
func get_save_progress() -> float:
|
||||
return _save_progress
|
||||
|
||||
func get_extraction_progress() -> float:
|
||||
if zipped_file_paths.size() == 0:
|
||||
return 0.0
|
||||
return float(extracted_file_paths.size()) / float(zipped_file_paths.size())
|
||||
|
||||
func get_download_progress() -> float:
|
||||
var body_size := _http_request.get_body_size()
|
||||
if body_size < 1: return 0.0
|
||||
return float(_http_request.get_downloaded_bytes()) / float(body_size)
|
||||
|
||||
func _zipped_files_remaining() -> int:
|
||||
return zipped_file_paths.size() - (extracted_file_paths.size() + skipped_file_paths.size())
|
||||
|
||||
func _extract_next_zipped_file() -> void:
|
||||
var path_index = extracted_file_paths.size() + skipped_file_paths.size()
|
||||
var zipped_file_path := zipped_file_paths.get(path_index)
|
||||
if path_match_string and not zipped_file_path.contains(path_match_string):
|
||||
skipped_file_paths.append(zipped_file_path)
|
||||
return
|
||||
var extract_path_dir := extract_path
|
||||
if not extract_path_dir.ends_with("/"):
|
||||
extract_path_dir += "/"
|
||||
var full_path := extract_path_dir
|
||||
if skip_base_zip_dir:
|
||||
full_path += zipped_file_path.replace(base_zip_path, "")
|
||||
else:
|
||||
full_path += zipped_file_path
|
||||
if full_path.ends_with("/"):
|
||||
if not DirAccess.dir_exists_absolute(full_path):
|
||||
DirAccess.make_dir_recursive_absolute(full_path)
|
||||
else:
|
||||
if not FileAccess.file_exists(full_path) or force:
|
||||
var file_access := FileAccess.open(full_path, FileAccess.WRITE)
|
||||
if file_access == null:
|
||||
skipped_file_paths.append(zipped_file_path)
|
||||
push_error("Failed to open file: %s" % full_path)
|
||||
return
|
||||
var file_contents = zip_reader.read_file(zipped_file_path)
|
||||
file_access.store_buffer(file_contents)
|
||||
file_access.close()
|
||||
extracted_file_paths.append(full_path)
|
||||
|
||||
func _finish_extraction() -> void:
|
||||
zip_reader.close()
|
||||
_delete_zip_file()
|
||||
stage = Stage.NONE
|
||||
run_completed.emit()
|
||||
|
||||
func _process(delta : float) -> void:
|
||||
if stage == Stage.EXTRACT:
|
||||
var frame_start_time : float = Time.get_unix_time_from_system()
|
||||
var frame_time : float = 0.0
|
||||
while (frame_time < delta * process_time_ratio):
|
||||
if _zipped_files_remaining() == 0:
|
||||
_finish_extraction()
|
||||
break
|
||||
_extract_next_zipped_file()
|
||||
frame_time = Time.get_unix_time_from_system() - frame_start_time
|
@ -0,0 +1 @@
|
||||
uid://bqu3bc0tttrfk
|
@ -0,0 +1,14 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://dlkmofxhavh10"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bqu3bc0tttrfk" path="res://addons/maaacks_menus_template/base/scenes/utilities/download_and_extract.gd" id="1_1few7"]
|
||||
|
||||
[node name="DownloadAndExtract" type="Node"]
|
||||
script = ExtResource("1_1few7")
|
||||
|
||||
[node name="HTTPRequest" type="HTTPRequest" parent="."]
|
||||
|
||||
[node name="TimeoutTimer" type="Timer" parent="."]
|
||||
one_shot = true
|
||||
|
||||
[connection signal="request_completed" from="HTTPRequest" to="." method="_on_http_request_request_completed"]
|
||||
[connection signal="timeout" from="TimeoutTimer" to="." method="_on_timeout_timer_timeout"]
|
176
addons/maaacks_menus_template/base/scripts/app_settings.gd
Normal file
176
addons/maaacks_menus_template/base/scripts/app_settings.gd
Normal file
@ -0,0 +1,176 @@
|
||||
class_name AppSettings
|
||||
extends Node
|
||||
## Interface to read/write general application settings through [Config].
|
||||
|
||||
const INPUT_SECTION = &'InputSettings'
|
||||
const AUDIO_SECTION = &'AudioSettings'
|
||||
const VIDEO_SECTION = &'VideoSettings'
|
||||
const GAME_SECTION = &'GameSettings'
|
||||
const APPLICATION_SECTION = &'ApplicationSettings'
|
||||
const CUSTOM_SECTION = &'CustomSettings'
|
||||
|
||||
const FULLSCREEN_ENABLED = &'FullscreenEnabled'
|
||||
const SCREEN_RESOLUTION = &'ScreenResolution'
|
||||
const MUTE_SETTING = &'Mute'
|
||||
const MASTER_BUS_INDEX = 0
|
||||
const SYSTEM_BUS_NAME_PREFIX = "_"
|
||||
|
||||
# Input
|
||||
static var default_action_events : Dictionary
|
||||
static var initial_bus_volumes : Array
|
||||
|
||||
static func get_config_input_events(action_name : String, default = null) -> Array:
|
||||
return Config.get_config(INPUT_SECTION, action_name, default)
|
||||
|
||||
static func set_config_input_events(action_name : String, inputs : Array) -> void:
|
||||
Config.set_config(INPUT_SECTION, action_name, inputs)
|
||||
|
||||
static func _clear_config_input_events() -> void:
|
||||
Config.erase_section(INPUT_SECTION)
|
||||
|
||||
static func remove_action_input_event(action_name : String, input_event : InputEvent) -> void:
|
||||
InputMap.action_erase_event(action_name, input_event)
|
||||
var action_events : Array[InputEvent] = InputMap.action_get_events(action_name)
|
||||
var config_events : Array = get_config_input_events(action_name, action_events)
|
||||
config_events.erase(input_event)
|
||||
set_config_input_events(action_name, config_events)
|
||||
|
||||
static func set_input_from_config(action_name : String) -> void:
|
||||
var action_events : Array[InputEvent] = InputMap.action_get_events(action_name)
|
||||
var config_events = get_config_input_events(action_name, action_events)
|
||||
if config_events == action_events:
|
||||
return
|
||||
if config_events.is_empty():
|
||||
Config.erase_section_key(INPUT_SECTION, action_name)
|
||||
return
|
||||
InputMap.action_erase_events(action_name)
|
||||
for config_event in config_events:
|
||||
if config_event not in action_events:
|
||||
InputMap.action_add_event(action_name, config_event)
|
||||
|
||||
static func _get_action_names() -> Array[StringName]:
|
||||
return InputMap.get_actions()
|
||||
|
||||
static func _get_custom_action_names() -> Array[StringName]:
|
||||
var callable_filter := func(action_name): return not (action_name.begins_with("ui_") or action_name.begins_with("spatial_editor"))
|
||||
var action_list := _get_action_names()
|
||||
return action_list.filter(callable_filter)
|
||||
|
||||
static func get_action_names(built_in_actions : bool = false) -> Array[StringName]:
|
||||
if built_in_actions:
|
||||
return _get_action_names()
|
||||
else:
|
||||
return _get_custom_action_names()
|
||||
|
||||
static func reset_to_default_inputs() -> void:
|
||||
_clear_config_input_events()
|
||||
for action_name in default_action_events:
|
||||
InputMap.action_erase_events(action_name)
|
||||
var input_events = default_action_events[action_name]
|
||||
for input_event in input_events:
|
||||
InputMap.action_add_event(action_name, input_event)
|
||||
|
||||
static func set_default_inputs() -> void:
|
||||
var action_list : Array[StringName] = _get_action_names()
|
||||
for action_name in action_list:
|
||||
default_action_events[action_name] = InputMap.action_get_events(action_name)
|
||||
|
||||
static func set_inputs_from_config() -> void:
|
||||
var action_list : Array[StringName] = _get_action_names()
|
||||
for action_name in action_list:
|
||||
set_input_from_config(action_name)
|
||||
|
||||
# Audio
|
||||
|
||||
static func get_bus_volume(bus_index : int) -> float:
|
||||
var initial_linear = 1.0
|
||||
if initial_bus_volumes.size() > bus_index:
|
||||
initial_linear = initial_bus_volumes[bus_index]
|
||||
var linear = db_to_linear(AudioServer.get_bus_volume_db(bus_index))
|
||||
linear /= initial_linear
|
||||
return linear
|
||||
|
||||
static func set_bus_volume(bus_index : int, linear : float) -> void:
|
||||
var initial_linear = 1.0
|
||||
if initial_bus_volumes.size() > bus_index:
|
||||
initial_linear = initial_bus_volumes[bus_index]
|
||||
linear *= initial_linear
|
||||
AudioServer.set_bus_volume_db(bus_index, linear_to_db(linear))
|
||||
|
||||
static func is_muted() -> bool:
|
||||
return AudioServer.is_bus_mute(MASTER_BUS_INDEX)
|
||||
|
||||
static func set_mute(mute_flag : bool) -> void:
|
||||
AudioServer.set_bus_mute(MASTER_BUS_INDEX, mute_flag)
|
||||
|
||||
static func get_audio_bus_name(bus_iter : int) -> String:
|
||||
return AudioServer.get_bus_name(bus_iter)
|
||||
|
||||
static func set_audio_from_config() -> void:
|
||||
for bus_iter in AudioServer.bus_count:
|
||||
var bus_key : String = get_audio_bus_name(bus_iter).to_pascal_case()
|
||||
var bus_volume : float = get_bus_volume(bus_iter)
|
||||
initial_bus_volumes.append(bus_volume)
|
||||
bus_volume = Config.get_config(AUDIO_SECTION, bus_key, bus_volume)
|
||||
if is_nan(bus_volume):
|
||||
bus_volume = 1.0
|
||||
Config.set_config(AUDIO_SECTION, bus_key, bus_volume)
|
||||
set_bus_volume(bus_iter, bus_volume)
|
||||
var mute_audio_flag : bool = is_muted()
|
||||
mute_audio_flag = Config.get_config(AUDIO_SECTION, MUTE_SETTING, mute_audio_flag)
|
||||
set_mute(mute_audio_flag)
|
||||
|
||||
# Video
|
||||
|
||||
static func set_fullscreen_enabled(value : bool, window : Window) -> void:
|
||||
window.mode = Window.MODE_EXCLUSIVE_FULLSCREEN if (value) else Window.MODE_WINDOWED
|
||||
|
||||
static func set_resolution(value : Vector2i, window : Window, update_config : bool = true) -> void:
|
||||
if value.x == 0 or value.y == 0:
|
||||
return
|
||||
window.size = value
|
||||
if update_config:
|
||||
Config.set_config(VIDEO_SECTION, SCREEN_RESOLUTION, value)
|
||||
|
||||
static func is_fullscreen(window : Window) -> bool:
|
||||
return (window.mode == Window.MODE_EXCLUSIVE_FULLSCREEN) or (window.mode == Window.MODE_FULLSCREEN)
|
||||
|
||||
static func get_resolution(window : Window) -> Vector2i:
|
||||
var current_resolution : Vector2i = window.size
|
||||
return Config.get_config(VIDEO_SECTION, SCREEN_RESOLUTION, current_resolution)
|
||||
|
||||
static func _on_window_size_changed(window: Window) -> void:
|
||||
Config.set_config(VIDEO_SECTION, SCREEN_RESOLUTION, window.size)
|
||||
|
||||
static func set_video_from_config(window : Window) -> void:
|
||||
window.size_changed.connect(_on_window_size_changed.bind(window))
|
||||
var fullscreen_enabled : bool = is_fullscreen(window)
|
||||
fullscreen_enabled = Config.get_config(VIDEO_SECTION, FULLSCREEN_ENABLED, fullscreen_enabled)
|
||||
set_fullscreen_enabled(fullscreen_enabled, window)
|
||||
if not (fullscreen_enabled or OS.has_feature("web")):
|
||||
var current_resolution : Vector2i = get_resolution(window)
|
||||
set_resolution(current_resolution, window)
|
||||
|
||||
static func set_vsync(vsync_mode : DisplayServer.VSyncMode, window : Window = null) -> void:
|
||||
var window_id : int = 0
|
||||
if window:
|
||||
window_id = window.get_window_id()
|
||||
DisplayServer.window_set_vsync_mode(vsync_mode, window_id)
|
||||
|
||||
static func get_vsync(window : Window = null) -> DisplayServer.VSyncMode:
|
||||
var window_id : int = 0
|
||||
if window:
|
||||
window_id = window.get_window_id()
|
||||
var vsync_mode = DisplayServer.window_get_vsync_mode(window_id)
|
||||
return vsync_mode
|
||||
|
||||
# All
|
||||
|
||||
static func set_from_config() -> void:
|
||||
set_default_inputs()
|
||||
set_inputs_from_config()
|
||||
set_audio_from_config()
|
||||
|
||||
static func set_from_config_and_window(window : Window) -> void:
|
||||
set_from_config()
|
||||
set_video_from_config(window)
|
@ -0,0 +1 @@
|
||||
uid://dwflyh7g2rjxt
|
65
addons/maaacks_menus_template/base/scripts/capture_focus.gd
Normal file
65
addons/maaacks_menus_template/base/scripts/capture_focus.gd
Normal file
@ -0,0 +1,65 @@
|
||||
class_name CaptureFocus
|
||||
extends Control
|
||||
## Node that captures UI focus for games with a hidden mouse or joypad enabled.
|
||||
##
|
||||
## This script assists with capturing UI focus when
|
||||
## opening, closing, or switching between menus.
|
||||
## When attached to a node, it will check if it was changed to visible
|
||||
## and if it should grab focus. If both are true, it will capture focus
|
||||
## on the first eligible node in its scene tree.
|
||||
|
||||
## Hierarchical depth to search in the scene tree.
|
||||
@export var search_depth : int = 1
|
||||
@export var enabled : bool = false
|
||||
@export var null_focus_enabled : bool = true
|
||||
@export var joypad_enabled : bool = true
|
||||
@export var mouse_hidden_enabled : bool = true
|
||||
|
||||
## Locks focus
|
||||
@export var lock : bool = false :
|
||||
set(value):
|
||||
var value_changed : bool = lock != value
|
||||
lock = value
|
||||
if value_changed and not lock:
|
||||
update_focus()
|
||||
|
||||
func _focus_first_search(control_node : Control, levels : int = 1) -> bool:
|
||||
if control_node == null or !control_node.is_visible_in_tree():
|
||||
return false
|
||||
if control_node.focus_mode == FOCUS_ALL:
|
||||
control_node.grab_focus()
|
||||
if control_node is ItemList:
|
||||
control_node.select(0)
|
||||
return true
|
||||
if levels < 1:
|
||||
return false
|
||||
var children = control_node.get_children()
|
||||
for child in children:
|
||||
if _focus_first_search(child, levels - 1):
|
||||
return true
|
||||
return false
|
||||
|
||||
func focus_first() -> void:
|
||||
_focus_first_search(self, search_depth)
|
||||
|
||||
func update_focus() -> void:
|
||||
if lock : return
|
||||
if _is_visible_and_should_capture():
|
||||
focus_first()
|
||||
|
||||
func _should_capture_focus() -> bool:
|
||||
return enabled or \
|
||||
(get_viewport().gui_get_focus_owner() == null and null_focus_enabled) or \
|
||||
(Input.get_connected_joypads().size() > 0 and joypad_enabled) or \
|
||||
(Input.mouse_mode not in [Input.MOUSE_MODE_VISIBLE, Input.MOUSE_MODE_CONFINED] and mouse_hidden_enabled)
|
||||
|
||||
func _is_visible_and_should_capture() -> bool:
|
||||
return is_visible_in_tree() and _should_capture_focus()
|
||||
|
||||
func _on_visibility_changed() -> void:
|
||||
call_deferred("update_focus")
|
||||
|
||||
func _ready() -> void:
|
||||
if is_inside_tree():
|
||||
update_focus()
|
||||
connect("visibility_changed", _on_visibility_changed)
|
@ -0,0 +1 @@
|
||||
uid://1nf36h0gms3q
|
59
addons/maaacks_menus_template/base/scripts/config.gd
Normal file
59
addons/maaacks_menus_template/base/scripts/config.gd
Normal file
@ -0,0 +1,59 @@
|
||||
class_name Config
|
||||
extends Object
|
||||
|
||||
## Interface for a single configuration file through [ConfigFile].
|
||||
|
||||
const CONFIG_FILE_LOCATION := "user://config.cfg"
|
||||
|
||||
static var config_file : ConfigFile
|
||||
|
||||
static func _init() -> void:
|
||||
load_config_file()
|
||||
|
||||
static func _save_config_file() -> void:
|
||||
var save_error : int = config_file.save(CONFIG_FILE_LOCATION)
|
||||
if save_error:
|
||||
push_error("save config file failed with error %d" % save_error)
|
||||
|
||||
static func load_config_file() -> void:
|
||||
if config_file != null:
|
||||
return
|
||||
config_file = ConfigFile.new()
|
||||
var load_error : int = config_file.load(CONFIG_FILE_LOCATION)
|
||||
if load_error:
|
||||
var save_error : int = config_file.save(CONFIG_FILE_LOCATION)
|
||||
if save_error:
|
||||
push_error("save config file failed with error %d" % save_error)
|
||||
|
||||
static func set_config(section: String, key: String, value) -> void:
|
||||
load_config_file()
|
||||
config_file.set_value(section, key, value)
|
||||
_save_config_file()
|
||||
|
||||
static func get_config(section: String, key: String, default = null) -> Variant:
|
||||
load_config_file()
|
||||
return config_file.get_value(section, key, default)
|
||||
|
||||
static func has_section(section: String) -> bool:
|
||||
load_config_file()
|
||||
return config_file.has_section(section)
|
||||
|
||||
static func has_section_key(section: String, key: String) -> bool:
|
||||
load_config_file()
|
||||
return config_file.has_section_key(section, key)
|
||||
|
||||
static func erase_section(section: String) -> void:
|
||||
if has_section(section):
|
||||
config_file.erase_section(section)
|
||||
_save_config_file()
|
||||
|
||||
static func erase_section_key(section: String, key: String) -> void:
|
||||
if has_section_key(section, key):
|
||||
config_file.erase_section_key(section, key)
|
||||
_save_config_file()
|
||||
|
||||
static func get_section_keys(section: String) -> PackedStringArray:
|
||||
load_config_file()
|
||||
if config_file.has_section(section):
|
||||
return config_file.get_section_keys(section)
|
||||
return PackedStringArray()
|
1
addons/maaacks_menus_template/base/scripts/config.gd.uid
Normal file
1
addons/maaacks_menus_template/base/scripts/config.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://dxjk8pgi7yhtq
|
51
addons/maaacks_menus_template/base/scripts/file_lister.gd
Normal file
51
addons/maaacks_menus_template/base/scripts/file_lister.gd
Normal file
@ -0,0 +1,51 @@
|
||||
@tool
|
||||
extends Node
|
||||
class_name FileLister
|
||||
## Helper class for listing all the scenes in a directory.
|
||||
|
||||
## List of paths to scene files.
|
||||
@export var _refresh_files_action : bool = false :
|
||||
set(value):
|
||||
if value and Engine.is_editor_hint():
|
||||
_refresh_files()
|
||||
# For Godot 4.4
|
||||
# @export_tool_button("Refresh Files") var _refresh_files_action = _refresh_files
|
||||
## Filled in the editor by selecting a directory.
|
||||
@export var files : Array[String]
|
||||
## Fills files with those discovered in directories, and matching constraints.
|
||||
@export_dir var directories : Array[String] :
|
||||
set(value):
|
||||
directories = value
|
||||
_refresh_files()
|
||||
|
||||
@export_group("Constraints")
|
||||
@export var search : String
|
||||
@export var filter : String
|
||||
|
||||
@export_subgroup("Advanced Search")
|
||||
@export var begins_with : String
|
||||
@export var ends_with : String
|
||||
@export var not_begins_with : String
|
||||
@export var not_ends_with : String
|
||||
|
||||
|
||||
func _refresh_files():
|
||||
if not is_inside_tree(): return
|
||||
files.clear()
|
||||
for directory in directories:
|
||||
var dir_access = DirAccess.open(directory)
|
||||
if dir_access:
|
||||
for file in dir_access.get_files():
|
||||
if (not search.is_empty()) and (not file.contains(search)):
|
||||
continue
|
||||
if (not filter.is_empty()) and (file.contains(filter)):
|
||||
continue
|
||||
if (not begins_with.is_empty()) and (not file.begins_with(begins_with)):
|
||||
continue
|
||||
if (not ends_with.is_empty()) and (not file.ends_with(ends_with)):
|
||||
continue
|
||||
if (not not_begins_with.is_empty()) and (file.begins_with(not_begins_with)):
|
||||
continue
|
||||
if (not not_ends_with.is_empty()) and (file.ends_with(not_ends_with)):
|
||||
continue
|
||||
files.append(directory + "/" + file)
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user