Add settings that persist

This commit is contained in:
Sofia 2026-07-23 21:39:51 +03:00
parent 60c0c6e71e
commit 798dcbc1bd
10 changed files with 399 additions and 0 deletions

77
Cargo.lock generated
View File

@ -41,6 +41,12 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]] [[package]]
name = "gdextension-api" name = "gdextension-api"
version = "0.5.1" version = "0.5.1"
@ -138,12 +144,28 @@ dependencies = [
"zerocopy", "zerocopy",
] ]
[[package]]
name = "hashbrown"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.5.0" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "indexmap"
version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.186" version = "0.2.186"
@ -181,6 +203,7 @@ dependencies = [
"godot", "godot",
"serde", "serde",
"teanet", "teanet",
"toml",
] ]
[[package]] [[package]]
@ -222,6 +245,15 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "serde_spanned"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
dependencies = [
"serde_core",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.119" version = "2.0.119"
@ -262,6 +294,45 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "toml"
version = "1.1.3+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53c96ecdfa941c8fc4fcaed14f99ada8ebed502eef533015095a07e3301d4c3c"
dependencies = [
"indexmap",
"serde_core",
"serde_spanned",
"toml_datetime",
"toml_parser",
"toml_writer",
"winnow",
]
[[package]]
name = "toml_datetime"
version = "1.1.1+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
dependencies = [
"serde_core",
]
[[package]]
name = "toml_parser"
version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
dependencies = [
"winnow",
]
[[package]]
name = "toml_writer"
version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.24" version = "1.0.24"
@ -278,6 +349,12 @@ dependencies = [
"quote", "quote",
] ]
[[package]]
name = "winnow"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.8.54" version = "0.8.54"

View File

@ -1,6 +1,7 @@
[gd_scene format=3 uid="uid://lahirbqfppvw"] [gd_scene format=3 uid="uid://lahirbqfppvw"]
[ext_resource type="PackedScene" uid="uid://bbmc2gmy7g8ag" path="res://scenes/misc/menu_player.tscn" id="1_o5qli"] [ext_resource type="PackedScene" uid="uid://bbmc2gmy7g8ag" path="res://scenes/misc/menu_player.tscn" id="1_o5qli"]
[ext_resource type="PackedScene" uid="uid://n3u0jr4wiib1" path="res://scenes/ui/settings.tscn" id="2_0wfyh"]
[node name="menu" type="MainMenu" unique_id=2036763876 node_paths=PackedStringArray("camera", "main_position", "host_position", "main_panel", "host_panel", "join_panel", "host_port", "join_addr", "popup_panel", "popup_title", "popup_text", "popup_button")] [node name="menu" type="MainMenu" unique_id=2036763876 node_paths=PackedStringArray("camera", "main_position", "host_position", "main_panel", "host_panel", "join_panel", "host_port", "join_addr", "popup_panel", "popup_title", "popup_text", "popup_button")]
camera = NodePath("camera_3d") camera = NodePath("camera_3d")
@ -175,6 +176,9 @@ text = "Join"
layout_mode = 2 layout_mode = 2
text = "Back" text = "Back"
[node name="settings" parent="." unique_id=999912383 instance=ExtResource("2_0wfyh")]
visible = false
[node name="main_pos" type="Node3D" parent="." unique_id=98241074] [node name="main_pos" type="Node3D" parent="." unique_id=98241074]
transform = Transform3D(0.98629165, -0.031186387, 0.16203767, -1.4054484e-08, 0.981978, 0.1889952, -0.16501155, -0.18640435, 0.9685167, 1.0974054, 1.7166122, 1.4909104) transform = Transform3D(0.98629165, -0.031186387, 0.16203767, -1.4054484e-08, 0.981978, 0.1889952, -0.16501155, -0.18640435, 0.9685167, 1.0974054, 1.7166122, 1.4909104)
@ -232,6 +236,7 @@ text = "Ok"
[connection signal="pressed" from="main_panel/v_box_container/host" to="." method="go_to_host_game"] [connection signal="pressed" from="main_panel/v_box_container/host" to="." method="go_to_host_game"]
[connection signal="pressed" from="main_panel/v_box_container/join" to="." method="go_to_join_game"] [connection signal="pressed" from="main_panel/v_box_container/join" to="." method="go_to_join_game"]
[connection signal="pressed" from="main_panel/v_box_container/settings" to="settings" method="open"]
[connection signal="pressed" from="main_panel/v_box_container/quit" to="." method="quit_game"] [connection signal="pressed" from="main_panel/v_box_container/quit" to="." method="quit_game"]
[connection signal="pressed" from="host_panel/v_box_container/host" to="." method="host_game"] [connection signal="pressed" from="host_panel/v_box_container/host" to="." method="host_game"]
[connection signal="pressed" from="host_panel/v_box_container/back" to="." method="go_to_main_menu"] [connection signal="pressed" from="host_panel/v_box_container/back" to="." method="go_to_main_menu"]

View File

@ -0,0 +1,54 @@
[gd_scene format=3 uid="uid://n3u0jr4wiib1"]
[ext_resource type="PackedScene" uid="uid://cqiwdajl71ujv" path="res://scenes/ui/slider.tscn" id="1_8wfqe"]
[node name="settings" type="SettingsPanel" unique_id=999912383 node_paths=PackedStringArray("settings_grid")]
settings_grid = NodePath("grid")
slider_scene = ExtResource("1_8wfqe")
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -207.0
offset_top = -112.0
offset_right = 207.0
offset_bottom = 112.0
grow_horizontal = 2
grow_vertical = 2
[node name="label" type="Label" parent="." unique_id=2036746921]
layout_mode = 0
offset_left = 57.0
offset_right = 357.0
offset_bottom = 23.0
text = "Settings"
horizontal_alignment = 1
[node name="grid" type="GridContainer" parent="." unique_id=833231169]
custom_minimum_size = Vector2(400, 0)
custom_maximum_size = Vector2(400, -1)
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -145.0
offset_top = -74.0
offset_right = 155.0
offset_bottom = 67.0
grow_horizontal = 2
grow_vertical = 2
columns = 2
[node name="button" type="Button" parent="." unique_id=1301469030]
layout_mode = 0
offset_left = 182.0
offset_top = 192.0
offset_right = 232.0
offset_bottom = 223.0
size_flags_horizontal = 4
text = "Close"
[connection signal="pressed" from="button" to="." method="close"]

View File

@ -0,0 +1,19 @@
[gd_scene format=3 uid="uid://cqiwdajl71ujv"]
[node name="slider" type="SliderContainer" unique_id=2000698366 node_paths=PackedStringArray("label", "slider")]
label = NodePath("label")
slider = NodePath("h_slider")
value = 50.0
max = 100.0
offset_right = 214.0
offset_bottom = 23.0
[node name="label" type="Label" parent="." unique_id=2059967949]
layout_mode = 2
text = "50"
[node name="h_slider" type="HSlider" parent="." unique_id=1564648919]
custom_minimum_size = Vector2(200, 0)
layout_mode = 2
size_flags_vertical = 4
value = 50.0

View File

@ -10,3 +10,4 @@ crate-type = ["cdylib"] # Compile this crate to a dynamic C library.
godot = "0.5.4" godot = "0.5.4"
serde = "1.0.228" serde = "1.0.228"
teanet = { path = "../teanet-rust" } teanet = { path = "../teanet-rust" }
toml = "1.1.3"

47
rust/src/game_settings.rs Normal file
View File

@ -0,0 +1,47 @@
use godot::{
classes::{FileAccess, file_access::ModeFlags},
prelude::*,
};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GameSettings {
pub fov: f64,
}
impl Default for GameSettings {
fn default() -> Self {
Self { fov: 90. }
}
}
#[derive(GodotClass)]
#[class(base=Node, singleton)]
pub struct GameSettingsManager {
pub settings: GameSettings,
base: Base<Node>,
}
#[godot_api]
impl INode for GameSettingsManager {
fn init(base: Base<Node>) -> GameSettingsManager {
let mut settings = GameSettings::default();
if let Some(file) = FileAccess::open("user://config.toml", ModeFlags::READ) {
if let Ok(deserialized) = toml::from_str(&file.get_as_text().to_string()) {
settings = deserialized;
}
}
GameSettingsManager { settings, base }
}
}
impl GameSettingsManager {
pub fn update_settings(&mut self, settings: GameSettings) {
self.settings = settings;
if let Some(mut file) = FileAccess::open("user://config.toml", ModeFlags::WRITE) {
if let Ok(serialized) = toml::to_string(&self.settings) {
file.store_string(&serialized);
}
}
}
}

View File

@ -3,6 +3,7 @@ use godot::prelude::*;
pub mod ball; pub mod ball;
pub mod cli_parser; pub mod cli_parser;
pub mod game_manager; pub mod game_manager;
pub mod game_settings;
pub mod goal; pub mod goal;
pub mod killbox; pub mod killbox;
pub mod map; pub mod map;

View File

@ -6,4 +6,6 @@ pub mod main_menu;
pub mod menu_player; pub mod menu_player;
pub mod net_stats; pub mod net_stats;
pub mod player_listing; pub mod player_listing;
pub mod settings;
pub mod simple_score_view; pub mod simple_score_view;
pub mod slider;

137
rust/src/ui/settings.rs Normal file
View File

@ -0,0 +1,137 @@
use std::collections::HashMap;
use godot::{
classes::{Control, GridContainer, IPanel, Label, Panel, control::SizeFlags},
prelude::*,
};
use crate::{
game_settings::{GameSettings, GameSettingsManager},
ui::slider::SliderContainer,
};
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Hash)]
pub enum Setting {
FoV,
}
impl ToString for Setting {
fn to_string(&self) -> String {
match self {
Setting::FoV => "Field of View",
}
.to_string()
}
}
#[derive(Clone, Copy)]
pub enum SettingValue {
SliderNumber(SliderValue),
}
#[derive(Clone, Copy)]
pub struct SliderValue {
min: f64,
max: f64,
value: f64,
}
#[derive(GodotClass)]
#[class(base=Panel, init)]
pub struct SettingsPanel {
#[export]
settings_grid: Option<Gd<GridContainer>>,
#[export]
slider_scene: Option<Gd<PackedScene>>,
settings: HashMap<Setting, SettingValue>,
base: Base<Panel>,
}
#[godot_api]
impl IPanel for SettingsPanel {
fn ready(&mut self) {
let singleton = GameSettingsManager::singleton();
let settings = &singleton.bind().settings;
self.settings.insert(
Setting::FoV,
SettingValue::SliderNumber(SliderValue {
min: 60.,
max: 120.,
value: settings.fov,
}),
);
if let Some(mut grid) = self.settings_grid.clone() {
for (setting, value) in &self.settings {
let mut label = Label::new_alloc();
label.set_text(&setting.to_string());
label.set_h_size_flags(SizeFlags::EXPAND | SizeFlags::SHRINK_BEGIN);
grid.add_child(&label);
if let Some(scene) = self.slider_scene.clone() {
let node = match value {
SettingValue::SliderNumber(slider_value) => {
let mut slider = scene.instantiate_as::<SliderContainer>();
slider.bind_mut().min = slider_value.min;
slider.bind_mut().max = slider_value.max;
slider.bind_mut().value = slider_value.value;
let setting_clone = setting.clone();
let value_clone = slider_value.clone();
slider.bind_mut().signals().on_change().connect_other(
self,
move |s, new_value| {
s.run_deferred(move |s| {
s.on_change(
setting_clone,
SettingValue::SliderNumber(SliderValue {
value: new_value,
..value_clone
}),
)
})
},
);
slider.upcast::<Control>()
}
};
grid.add_child(&node);
}
}
}
}
}
#[godot_api]
impl SettingsPanel {
pub fn on_change(&mut self, setting: Setting, new_value: SettingValue) {
self.settings.insert(setting, new_value);
let mut new_settings = GameSettingsManager::singleton().bind().settings.clone();
for (setting, value) in &self.settings {
match (setting, value) {
(Setting::FoV, SettingValue::SliderNumber(value)) => {
new_settings.fov = value.value;
}
}
}
GameSettingsManager::singleton()
.bind_mut()
.update_settings(new_settings);
}
#[func]
pub fn open(&mut self) {
self.base_mut().set_visible(true);
}
#[func]
pub fn close(&mut self) {
self.base_mut().set_visible(false);
}
}

56
rust/src/ui/slider.rs Normal file
View File

@ -0,0 +1,56 @@
use godot::{
classes::{HBoxContainer, IHBoxContainer, Label, Slider},
prelude::*,
};
#[derive(GodotClass)]
#[class(base=HBoxContainer, init, tool)]
pub struct SliderContainer {
#[export]
label: Option<Gd<Label>>,
#[export]
slider: Option<Gd<Slider>>,
#[export]
pub value: f64,
#[export]
pub min: f64,
#[export]
pub max: f64,
last_value: f64,
base: Base<HBoxContainer>,
}
#[godot_api]
impl IHBoxContainer for SliderContainer {
fn ready(&mut self) {}
fn process(&mut self, _delta: f64) {
if let Some(slider) = &mut self.slider {
slider.set_min(self.min);
slider.set_max(self.max);
if self.last_value != self.value {
slider.set_value(self.value);
}
self.value = slider.get_value();
if self.last_value != self.value {
let value = self.value;
self.signals().on_change().emit(value);
}
self.last_value = self.value;
}
if let Some(label) = &mut self.label
&& let Some(slider) = &self.slider
{
label.set_text(&format!("{}", slider.get_value()));
}
}
}
#[godot_api]
impl SliderContainer {
#[signal]
pub fn on_change(value: f64);
}