diff --git a/scenes/prefabs/sqore_options_menu/gameplay.gd b/scenes/prefabs/sqore_options_menu/gameplay.gd new file mode 100644 index 0000000..8f815d9 --- /dev/null +++ b/scenes/prefabs/sqore_options_menu/gameplay.gd @@ -0,0 +1,4 @@ +extends CollapsingVBoxContainer + +func _ready() -> void: + pass diff --git a/scenes/prefabs/sqore_options_menu/sqore_options_menu.gd b/scenes/prefabs/sqore_options_menu/sqore_options_menu.gd new file mode 100644 index 0000000..f9b58d3 --- /dev/null +++ b/scenes/prefabs/sqore_options_menu/sqore_options_menu.gd @@ -0,0 +1,12 @@ +extends VBoxContainer +""" +Relatively system agnostic menu for use of quickly bootstrapping an options menu for your game. If you want to create a custom menu this may serve as a template and example of how to interface with Sqore's various system settings +""" + +## When entering the tree, forces Sqore global settings to be loaded from disk (in cast that hasn't happened yet) +func _enter_tree() -> void: + Sqore.reload_globals() + +## When exiting the tree, force Sqore to save to disk any changes that have been made. This helps to prevent any dissonance between runtime globals and disk. +func _exit_tree() -> void: + Sqore.save_globals() diff --git a/scenes/prefabs/sqore_options_menu/sqore_options_menu.tscn b/scenes/prefabs/sqore_options_menu/sqore_options_menu.tscn new file mode 100644 index 0000000..7773bcc --- /dev/null +++ b/scenes/prefabs/sqore_options_menu/sqore_options_menu.tscn @@ -0,0 +1,100 @@ +[gd_scene load_steps=3 format=3 uid="uid://dnbbxlomwl1pr"] + +[ext_resource type="Script" path="res://addons/sqore/scenes/prefabs/sqore_options_menu/sqore_options_menu.gd" id="1_6met4"] +[ext_resource type="Script" path="res://addons/sqore/scenes/prefabs/sqore_options_menu/gameplay.gd" id="2_n73id"] + +[node name="SqoreOptionsMenu" type="VBoxContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_6met4") + +[node name="Gameplay" type="CollapsingVBoxContainer" parent="."] +heading_text = "Gameplay" +layout_mode = 2 +script = ExtResource("2_n73id") + +[node name="HFlowContainer" type="HFlowContainer" parent="Gameplay"] +visible = false +layout_mode = 2 + +[node name="CheckSSAO" type="CheckButton" parent="Gameplay/HFlowContainer"] +layout_mode = 2 +text = "Use SSAO" + +[node name="CheckBloom" type="CheckButton" parent="Gameplay/HFlowContainer"] +layout_mode = 2 + +[node name="CheckSDFGI" type="CheckButton" parent="Gameplay/HFlowContainer"] +layout_mode = 2 + +[node name="CheckSSIL" type="CheckButton" parent="Gameplay/HFlowContainer"] +layout_mode = 2 + +[node name="CheckSSR" type="CheckButton" parent="Gameplay/HFlowContainer"] +layout_mode = 2 + +[node name="Graphics" type="CollapsingVBoxContainer" parent="."] +heading_text = "Graphics" +layout_mode = 2 + +[node name="Controls" type="CollapsingVBoxContainer" parent="."] +heading_text = "Controls" +layout_mode = 2 + +[node name="Audio" type="CollapsingVBoxContainer" parent="."] +heading_text = "Audio" +layout_mode = 2 + +[node name="HSlider" type="HSlider" parent="."] +custom_minimum_size = Vector2(0, 64) +layout_mode = 2 + +[node name="Title" type="Label" parent="HSlider"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +text = "asdasdasd" + +[node name="Min" type="Label" parent="HSlider"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +text = "0" +vertical_alignment = 2 + +[node name="Max" type="Label" parent="HSlider"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +text = "1" +horizontal_alignment = 2 +vertical_alignment = 2 + +[node name="Current" type="Label" parent="HSlider"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +text = "0.55556" +horizontal_alignment = 1 +vertical_alignment = 2 + +[node name="LabelledHSlider" type="LabelledHSlider" parent="."] +text = "This is a cool slider" +custom_minimum_size = Vector2(0, 64) +layout_mode = 2 +max_value = 89.46 diff --git a/sqore.gdextension b/sqore.gdextension index 4f4b0bc..c0c42a5 100644 --- a/sqore.gdextension +++ b/sqore.gdextension @@ -1,6 +1,8 @@ [configuration] entry_symbol = "gdext_rust_init" compatibility_minimum = 4.2 + +;; set to false for better performance. Set to true for development reloadable = false diff --git a/src/scene/gui/collapsable_container.rs b/src/scene/gui/collapsable_container.rs new file mode 100644 index 0000000..3e2f4be --- /dev/null +++ b/src/scene/gui/collapsable_container.rs @@ -0,0 +1,73 @@ +use godot::engine::Button; +use godot::engine::Control; +use godot::engine::IVBoxContainer; +use godot::engine::VBoxContainer; +use godot::prelude::*; + +#[derive(GodotClass)] +#[class(tool, init, base=VBoxContainer)] +pub struct CollapsingVBoxContainer { + #[export] + #[var(get,set = set_heading_text)] + heading_text: GString, + + #[export] + default_visibility: bool, + + heading: Option>, + base: Base, +} + +#[godot_api] +impl IVBoxContainer for CollapsingVBoxContainer { + fn enter_tree(&mut self) { + let mut heading_button = Button::new_alloc(); + self.heading = Some(heading_button.clone()); + heading_button.set_text(self.heading_text.clone()); + heading_button.set_toggle_mode(true); + heading_button.set_pressed(self.default_visibility); + self.base_mut().add_child(heading_button.clone().upcast()); + self.base_mut() + .move_child(heading_button.clone().upcast(), 0); + heading_button.connect( + StringName::from("toggled"), + Callable::from_object_method(&self.to_gd(), "on_heading_toggle"), + ); + self.on_heading_toggle(self.default_visibility); + } + + fn ready(&mut self) { + // is anything needed here?? + } +} + +#[godot_api] +impl CollapsingVBoxContainer { + #[func] + fn on_heading_toggle(&mut self, is_toggled: bool) { + let mut children: Vec> = self.base_mut().get_children().iter_shared().collect(); + if let Some(btn) = &self.heading { + let btn_base = &btn.clone().upcast::(); + children = children.into_iter().filter(|p| p != btn_base).collect(); + } + for child in children { + if let Ok(control) = &mut child.clone().try_cast::() { + control.set_visible(is_toggled); + } + if let Ok(node2d) = &mut child.clone().try_cast::() { + node2d.set_visible(is_toggled); + } + if let Ok(node3d) = &mut child.clone().try_cast::() { + node3d.set_visible(is_toggled); + } + } + } + + #[func] + fn set_heading_text(&mut self, n_text: GString) { + self.heading_text = n_text; + if let Some(btn) = &mut self.heading { + btn.set_text(self.heading_text.clone()); + } + } +} diff --git a/src/scene/gui/labelled_hslider.rs b/src/scene/gui/labelled_hslider.rs new file mode 100644 index 0000000..03bbaf8 --- /dev/null +++ b/src/scene/gui/labelled_hslider.rs @@ -0,0 +1,100 @@ +use godot::{ + engine::{ + control::LayoutPreset, + global::{HorizontalAlignment, VerticalAlignment}, + HSlider, IHSlider, Label, + }, + obj::WithBaseField, + prelude::*, +}; + +#[derive(GodotClass)] +#[class(tool, init, base=HSlider)] +pub struct LabelledHSlider { + #[export] + #[var(get, set=set_text)] + text: GString, + + label_title: Option>, + label_min: Option>, + label_max: Option>, + label_current: Option>, + + base: Base, +} + +#[godot_api] +impl IHSlider for LabelledHSlider { + fn enter_tree(&mut self) { + //pass + let mut l_title = Label::new_alloc(); + let mut l_min = Label::new_alloc(); + let mut l_max = Label::new_alloc(); + let mut l_cur = Label::new_alloc(); + l_title.set_text(self.text.clone()); + l_min.set_text(Self::as_text(self.base().get_min())); + l_max.set_text(Self::as_text(self.base().get_max())); + l_cur.set_text(Self::as_text(self.base().get_value())); + + let call_range = Callable::from_object_method(&self.to_gd(), "update_range"); + let call_value = Callable::from_object_method(&self.to_gd(), "update_value"); + self.base_mut().add_child(l_title.clone().upcast()); + self.base_mut().add_child(l_min.clone().upcast()); + self.base_mut().add_child(l_max.clone().upcast()); + self.base_mut().add_child(l_cur.clone().upcast()); + self.base_mut() + .connect(StringName::from("changed"), call_range); + self.base_mut() + .connect(StringName::from("value_changed"), call_value); + + l_title.set_anchors_and_offsets_preset(LayoutPreset::FULL_RECT); + l_title.set_vertical_alignment(VerticalAlignment::TOP); + l_title.set_horizontal_alignment(HorizontalAlignment::LEFT); + + l_min.set_anchors_and_offsets_preset(LayoutPreset::FULL_RECT); + l_min.set_vertical_alignment(VerticalAlignment::BOTTOM); + l_min.set_horizontal_alignment(HorizontalAlignment::LEFT); + + l_max.set_anchors_and_offsets_preset(LayoutPreset::FULL_RECT); + l_max.set_vertical_alignment(VerticalAlignment::BOTTOM); + l_max.set_horizontal_alignment(HorizontalAlignment::RIGHT); + + l_cur.set_anchors_and_offsets_preset(LayoutPreset::FULL_RECT); + l_cur.set_vertical_alignment(VerticalAlignment::BOTTOM); + l_cur.set_horizontal_alignment(HorizontalAlignment::CENTER); + } +} + +#[godot_api] +impl LabelledHSlider { + #[func] + fn set_text(&mut self, n_text: GString) { + self.text = n_text.clone(); + if let Some(label) = &mut self.label_title { + label.set_text(n_text); + } + } + #[func] + fn update_range(&mut self) { + let min = self.base().get_min(); + let max = self.base().get_max(); + if let Some(l_min) = &mut self.label_min { + l_min.set_text(Self::as_text(min)); + } + if let Some(l_max) = &mut self.label_max { + l_max.set_text(Self::as_text(max)); + } + } + + #[func] + fn update_value(&mut self, value: f64) { + let Some(label) = &mut self.label_current else { + return; + }; + label.set_text(Self::as_text(value)); + } + + fn as_text(num: f64) -> GString { + format!("{}", num).to_godot() + } +} diff --git a/src/scene/gui/mod.rs b/src/scene/gui/mod.rs new file mode 100644 index 0000000..d25baac --- /dev/null +++ b/src/scene/gui/mod.rs @@ -0,0 +1,2 @@ +pub mod collapsable_container; +pub mod labelled_hslider; \ No newline at end of file diff --git a/src/scene/mod.rs b/src/scene/mod.rs index f8c8d14..390e04b 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -13,6 +13,7 @@ pub mod signals; pub mod state_machine; pub mod utility_nodes; pub mod vfx_stack; +pub mod gui; pub fn register_singletons() { game_globals::register_singleton();