-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
1 parent
1daf474
commit ba441d1
Showing
14 changed files
with
764 additions
and
56 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
use godot::{engine::*, prelude::*}; | ||
use once_cell::sync::Lazy; | ||
|
||
use crate::game_globals::CoreGlobals; | ||
|
||
pub static SINGLETON_CORE_GLOBALS: Lazy<StringName> = Lazy::new(|| StringName::from("CoreGlobals")); | ||
|
||
pub fn register_engine_elements() { | ||
Engine::singleton().register_singleton( | ||
SINGLETON_CORE_GLOBALS.clone(), | ||
CoreGlobals::alloc_gd().upcast(), | ||
); | ||
} | ||
|
||
pub fn unregister_engine_elements() { | ||
Engine::singleton().unregister_singleton(SINGLETON_CORE_GLOBALS.clone()); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
use godot::{ | ||
builtin::meta::{ConvertError, GodotConvert}, | ||
engine::*, | ||
prelude::*, | ||
}; | ||
|
||
use crate::{ | ||
editor_plugin::SINGLETON_CORE_GLOBALS, game_settings::SquigglesCoreConfig, | ||
serialization::SquigglesSerialized, | ||
}; | ||
|
||
const PROJECT_SETTINGS_NAMESPACE: &str = "addons/squiggles_core/"; | ||
const S_LOADERS: &str = "loaders"; | ||
const S_GAME_SETTINGS: &str = "game_settings"; | ||
|
||
fn get_setting_name(name: &str) -> GString { | ||
(String::from(PROJECT_SETTINGS_NAMESPACE) + name).to_godot() | ||
} | ||
|
||
#[derive(GodotClass)] | ||
#[class(tool, base=Object)] | ||
pub struct CoreGlobals { | ||
#[var] | ||
config: Gd<SquigglesCoreConfig>, | ||
#[base] | ||
base: Base<Object>, | ||
} | ||
|
||
#[godot_api] | ||
impl IObject for CoreGlobals { | ||
fn init(base: Base<Self::Base>) -> Self { | ||
// let mut zelf = Self { config: None, base }; | ||
let mut possible_config: Option<Gd<SquigglesCoreConfig>> = None; | ||
match Self::get_or_init_default(S_LOADERS, PackedStringArray::new()) { | ||
Err(err) => godot_warn!("Conversion Error: {}", err.to_string()), | ||
Ok(loaders) => { | ||
for item in loaders.as_slice().iter() { | ||
godot_print!("Found loader entry: {}", item); | ||
} | ||
} | ||
} | ||
// try load configuration file | ||
if let Ok(config_path) = | ||
Self::get_or_init_default(S_GAME_SETTINGS, "res://squiggles_config.tres".to_godot()) | ||
{ | ||
if let Some(config_resource) = ResourceLoader::singleton().load(config_path.clone()) { | ||
let opt_res: Result<Gd<SquigglesCoreConfig>, Gd<Resource>> = | ||
config_resource.try_cast(); | ||
if let Ok(valid_resource) = opt_res { | ||
possible_config = Some(valid_resource); | ||
} | ||
} else { | ||
let msg = format!("Expected an instance of `SquigglesCoreConfig` resource to be at path: \"{}\". Either create the resource at that location, or update the `{}` setting in your project settings.", config_path, S_GAME_SETTINGS); | ||
godot_error!("{}", msg); | ||
godot_print!("{}", msg); | ||
} | ||
} | ||
let mut zelf = Self { | ||
config: possible_config.unwrap_or(SquigglesCoreConfig::new_gd()), | ||
base, | ||
}; | ||
zelf.reload_globals(); | ||
|
||
zelf | ||
} | ||
} | ||
|
||
#[godot_api] | ||
impl CoreGlobals { | ||
#[signal] | ||
fn global_serialize() {} | ||
|
||
#[signal] | ||
fn global_deserialize() {} | ||
|
||
#[func] | ||
fn get_setting(&self, name: String, default_value: Variant) -> Variant { | ||
let result = Self::get_or_init_default(name.as_str(), default_value); | ||
match result { | ||
Ok(value) => value, | ||
Err(_) => Variant::nil(), | ||
} | ||
} | ||
#[func] | ||
fn save_globals(&mut self) { | ||
self.serialize(); | ||
} | ||
|
||
#[func] | ||
fn reload_globals(&mut self) { | ||
self.deserialize(); | ||
} | ||
|
||
// internal specialized functions | ||
|
||
pub fn get_or_init_default<T: GodotConvert + FromGodot + ToGodot>( | ||
name: &str, | ||
default: T, | ||
) -> Result<T, ConvertError> { | ||
let mut project = ProjectSettings::singleton(); | ||
let value_volatile = project.get_setting(get_setting_name(name)); | ||
|
||
if value_volatile.is_nil() || value_volatile.get_type() != default.to_variant().get_type() { | ||
project.set_setting(get_setting_name(name), default.to_variant()); | ||
Ok(default) | ||
} else { | ||
// no longer volatile | ||
T::try_from_variant(&value_volatile) | ||
} | ||
} | ||
|
||
pub fn singleton() -> Gd<CoreGlobals> { | ||
let Some(vol) = Engine::singleton().get_singleton(SINGLETON_CORE_GLOBALS.clone()) else { | ||
panic!("Failed to find engine singleton for CoreGlobals. You must access this after it is registered!"); | ||
}; | ||
let res_core: Result<Gd<CoreGlobals>, Gd<_>> = vol.try_cast(); | ||
let Ok(core) = res_core else { | ||
panic!("Failed to cast engine singleton for CoreGlobals. This should never happen!"); | ||
}; | ||
core | ||
} | ||
} | ||
|
||
impl SquigglesSerialized for CoreGlobals { | ||
fn serialize(&mut self) { | ||
// I'm comfy using unwrap because this struct should never be constructed outside of the init function, which assigns the | ||
self.config.bind_mut().serialize(); | ||
} | ||
|
||
fn deserialize(&mut self) { | ||
self.config.bind_mut().deserialize(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
use godot::{engine::display_server::WindowMode, prelude::*}; | ||
|
||
use crate::serialization::{SaveDataBuilder, SquigglesSerialized}; | ||
|
||
#[derive(GodotClass)] | ||
#[class(tool, base=Resource)] | ||
pub struct GameGraphicsSettings { | ||
#[export] | ||
use_ssao: bool, | ||
#[export] | ||
use_bloom: bool, | ||
#[export] | ||
use_sdfgi: bool, | ||
#[export] | ||
use_ssil: bool, | ||
#[export] | ||
use_ssr: bool, | ||
#[export] | ||
value_brightness: f32, | ||
#[export] | ||
value_contrast: f32, | ||
#[export] | ||
value_saturation: f32, | ||
#[export] | ||
value_exposure: f32, | ||
#[export(enum=(Windowed=0, Minimized=1, Maximized=2, Fullscreen=3, ExclusiveFullscreen=4))] | ||
window_fullscreen_mode: i32, | ||
#[base] | ||
base: Base<Resource>, | ||
} | ||
|
||
#[godot_api] | ||
impl IResource for GameGraphicsSettings { | ||
fn init(base: Base<Self::Base>) -> Self { | ||
Self { | ||
use_ssao: true, | ||
base, | ||
use_bloom: true, | ||
use_sdfgi: true, | ||
use_ssil: false, | ||
use_ssr: false, | ||
value_brightness: 1.0, | ||
value_contrast: 1.0, | ||
value_saturation: 1.0, | ||
value_exposure: 1.0, | ||
window_fullscreen_mode: WindowMode::WINDOW_MODE_MAXIMIZED.ord(), | ||
} | ||
} | ||
} | ||
|
||
#[godot_api] | ||
impl GameGraphicsSettings {} | ||
|
||
const GRAPHICS_SAVE_PATH: &str = "user://core/graphics.json"; | ||
|
||
impl SquigglesSerialized for GameGraphicsSettings { | ||
fn serialize(&mut self) { | ||
let mut save = SaveDataBuilder::alloc_gd(); | ||
let mut bind = save.bind_mut(); | ||
bind.set_value("use_ssao".to_godot(), self.use_ssao.to_variant()); | ||
bind.set_value("use_bloom".to_godot(), self.use_bloom.to_variant()); | ||
bind.set_value("use_sdfgi".to_godot(), self.use_sdfgi.to_variant()); | ||
bind.set_value("use_ssil".to_godot(), self.use_ssil.to_variant()); | ||
bind.set_value("use_ssr".to_godot(), self.use_ssr.to_variant()); | ||
bind.set_value( | ||
"value_brightness".to_godot(), | ||
self.value_brightness.to_variant(), | ||
); | ||
bind.set_value( | ||
"value_contrast".to_godot(), | ||
self.value_contrast.to_variant(), | ||
); | ||
bind.set_value( | ||
"value_saturation".to_godot(), | ||
self.value_saturation.to_variant(), | ||
); | ||
bind.set_value( | ||
"value_exposure".to_godot(), | ||
self.value_exposure.to_variant(), | ||
); | ||
bind.set_value( | ||
"window_fullscreen_mode".to_godot(), | ||
self.window_fullscreen_mode.to_variant(), | ||
); | ||
bind.save(GRAPHICS_SAVE_PATH.into_godot()); | ||
} | ||
|
||
fn deserialize(&mut self) { | ||
let Some(mut load) = SaveDataBuilder::try_load_file(GRAPHICS_SAVE_PATH.into_godot()) else { | ||
return; | ||
}; | ||
// use_ssao: bool, | ||
// use_bloom: bool, | ||
// use_sdfgi: bool, | ||
// use_ssil: bool, | ||
// use_ssr: bool, | ||
// value_brightness: f32, | ||
// value_contrast: f32, | ||
// value_saturation: f32, | ||
// value_exposure: f32, | ||
// window_fullscreen_mode: i32, | ||
let mut bind = load.bind_mut(); | ||
self.use_ssao = bind.internal_get_value("use_ssao".to_godot(), self.use_ssao.to_godot()); | ||
self.use_bloom = bind.internal_get_value("use_bloom".to_godot(), self.use_bloom.to_godot()); | ||
self.use_sdfgi = bind.internal_get_value("use_sdfgi".to_godot(), self.use_sdfgi.to_godot()); | ||
self.use_ssil = bind.internal_get_value("use_ssil".to_godot(), self.use_ssil.to_godot()); | ||
self.use_ssr = bind.internal_get_value("use_ssr".to_godot(), self.use_ssr.to_godot()); | ||
self.value_brightness = | ||
bind.internal_get_value("value_brightness".to_godot(), self.value_brightness); | ||
self.value_contrast = | ||
bind.internal_get_value("value_contrast".to_godot(), self.value_contrast); | ||
self.value_saturation = | ||
bind.internal_get_value("value_saturation".to_godot(), self.value_saturation); | ||
self.value_exposure = | ||
bind.internal_get_value("value_exposure".to_godot(), self.value_exposure); | ||
self.window_fullscreen_mode = bind.internal_get_value( | ||
"window_fullscreen_mode".to_godot(), | ||
self.window_fullscreen_mode, | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
use godot::prelude::*; | ||
|
||
use crate::serialization::SquigglesSerialized; | ||
|
||
use self::graphics::GameGraphicsSettings; | ||
|
||
pub mod graphics; | ||
|
||
#[derive(GodotClass)] | ||
#[class(tool, base=Resource)] | ||
pub struct SquigglesCoreConfig { | ||
#[export] | ||
graphics: Gd<graphics::GameGraphicsSettings>, | ||
|
||
#[base] | ||
base: Base<Resource>, | ||
} | ||
#[godot_api] | ||
impl IResource for SquigglesCoreConfig { | ||
fn init(base: Base<Self::Base>) -> Self { | ||
Self { | ||
base, | ||
graphics: GameGraphicsSettings::new_gd(), | ||
} | ||
} | ||
} | ||
|
||
#[godot_api] | ||
impl SquigglesCoreConfig {} | ||
|
||
impl SquigglesSerialized for SquigglesCoreConfig { | ||
fn serialize(&mut self) { | ||
self.graphics.bind_mut().serialize(); | ||
} | ||
|
||
fn deserialize(&mut self) { | ||
self.graphics.bind_mut().deserialize(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
use godot::{ | ||
engine::{Environment, IWorldEnvironment, WorldEnvironment}, | ||
prelude::*, | ||
}; | ||
|
||
use crate::game_globals::CoreGlobals; | ||
|
||
#[derive(GodotClass)] | ||
#[class(init, base=WorldEnvironment)] | ||
struct WorldEnvironmentSettingsCompliant { | ||
#[export] | ||
force_override: bool, | ||
#[base] | ||
base: Base<WorldEnvironment>, | ||
} | ||
|
||
#[godot_api] | ||
impl IWorldEnvironment for WorldEnvironmentSettingsCompliant { | ||
fn ready(&mut self) { | ||
// TODO: omfg if <unwrap> else {...}; is so lovely on the eyes. I wanna convert some of the more deeply nested fuctions into this pattern if possible. | ||
let option_env = self.base.get_environment(); | ||
let mut env = Environment::new(); | ||
if option_env.is_some() && !self.force_override { | ||
// if there is an existing environment and we are not forcing an override, let that be the environment | ||
return; | ||
} | ||
if let Some(n_env) = option_env { | ||
env = n_env; | ||
} | ||
|
||
let gd_gfx = CoreGlobals::singleton() | ||
.bind() | ||
.get_config() | ||
.bind() | ||
.get_graphics(); | ||
let gfx = gd_gfx.bind(); | ||
env.set_glow_enabled(gfx.get_use_bloom()); | ||
|
||
self.base.set_environment(env); | ||
} | ||
} | ||
|
||
#[godot_api] | ||
impl WorldEnvironmentSettingsCompliant {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
pub mod custom_world_environment; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,34 @@ | ||
// crate-wide warnings alterations | ||
#![allow(dead_code)] // a lot of elements are considered unused because Godot grabs it over FFI. | ||
|
||
use godot::prelude::*; | ||
|
||
// module specifications | ||
pub mod camera; | ||
pub mod editor_plugin; | ||
pub mod error_handling; | ||
pub mod game_globals; | ||
pub mod game_settings; | ||
pub mod godot_replacements; | ||
pub mod interaction; | ||
pub mod serialization; | ||
pub mod state_machine; | ||
pub mod utility_nodes; | ||
|
||
// extension loading | ||
struct SquigglesCore; | ||
|
||
#[gdextension] | ||
unsafe impl ExtensionLibrary for SquigglesCore {} | ||
unsafe impl ExtensionLibrary for SquigglesCore { | ||
fn on_level_init(level: InitLevel) { | ||
if level == InitLevel::Scene { | ||
editor_plugin::register_engine_elements(); | ||
} | ||
} | ||
|
||
fn on_level_deinit(level: InitLevel) { | ||
if level == InitLevel::Scene { | ||
editor_plugin::unregister_engine_elements(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
use std::collections::HashMap; | ||
|
||
use godot::{ | ||
engine::{file_access::ModeFlags, DirAccess, FileAccess, Json, ProjectSettings}, | ||
prelude::*, | ||
}; | ||
|
||
const INTERNAL_PREFIX: &str = "__internal__"; | ||
|
||
#[derive(GodotClass)] | ||
#[class(base=Object)] | ||
pub struct SaveDataBuilder { | ||
data: Dictionary, | ||
child_builders: HashMap<GString, Gd<SaveDataBuilder>>, | ||
base: Base<Object>, | ||
} | ||
|
||
#[godot_api] | ||
impl IObject for SaveDataBuilder { | ||
fn init(base: Base<Self::Base>) -> Self { | ||
Self { | ||
data: Dictionary::new(), | ||
child_builders: HashMap::new(), | ||
base, | ||
} | ||
} | ||
} | ||
#[godot_api] | ||
pub impl SaveDataBuilder { | ||
#[func] | ||
pub fn set_value(&mut self, key: GString, value: Variant) { | ||
self.data.set(key, value); | ||
} | ||
|
||
#[func] | ||
pub fn get_value(&mut self, key: GString) -> Variant { | ||
self.data.get_or_nil(key) | ||
} | ||
|
||
#[func] | ||
pub fn get_value_or_default(&mut self, key: GString, default: Variant) -> Variant { | ||
if let Some(value) = self.data.get(key) { | ||
return value; | ||
} | ||
default | ||
} | ||
|
||
#[func] | ||
pub fn get_child_builder(&mut self, key: GString) -> Gd<SaveDataBuilder> { | ||
let mut n_builder = SaveDataBuilder::alloc_gd(); | ||
if let Some(child) = self.child_builders.get_mut(&key) { | ||
n_builder = child.clone(); | ||
} else { | ||
self.child_builders.insert(key, n_builder.clone()); | ||
} | ||
n_builder | ||
} | ||
|
||
#[func] | ||
pub fn save(&mut self, file_path: GString) -> bool { | ||
let abs_path = ProjectSettings::singleton().globalize_path(file_path.clone()); | ||
if let Some(base_dir) = std::path::Path::new(abs_path.to_string().as_str()).parent() { | ||
if let Some(valid_base_dir) = base_dir.to_str() { | ||
if !DirAccess::dir_exists_absolute(GString::from(valid_base_dir)) { | ||
DirAccess::make_dir_recursive_absolute(GString::from(valid_base_dir)); | ||
} | ||
} | ||
} | ||
let Some(mut file) = FileAccess::open(file_path.clone(), ModeFlags::WRITE) else { | ||
godot_warn!("Failed to access file {}", file_path); | ||
return false; | ||
}; | ||
let text = Json::stringify(self.get_as_dict().to_variant()); | ||
file.store_string(text); | ||
true | ||
} | ||
|
||
#[func] | ||
pub fn load(&mut self, file_path: GString) -> bool { | ||
let Some(file) = FileAccess::open(file_path.clone(), ModeFlags::READ) else { | ||
godot_warn!("Failed to access file {}", file_path); | ||
return false; | ||
}; | ||
let opt_cast = Json::parse_string(file.get_as_text()); | ||
if let Ok(data) = Dictionary::try_from_variant(&opt_cast) { | ||
for entry in data.iter_shared() { | ||
let skey = GString::from_variant(&entry.0); | ||
if skey.to_string().starts_with(INTERNAL_PREFIX) { | ||
} else { | ||
self.data.set(skey, entry.1); | ||
} | ||
} | ||
}; | ||
|
||
true | ||
} | ||
|
||
#[func] | ||
pub fn load_from(dict: Dictionary) -> Gd<SaveDataBuilder> { | ||
let mut data = SaveDataBuilder::alloc_gd(); | ||
for entry in dict.iter_shared() { | ||
let skey = GString::from_variant(&entry.0); | ||
if skey.to_string().starts_with(INTERNAL_PREFIX) { | ||
// attempts to construct an internal save data builder (which is effecitively a sub-layer in the JSON) | ||
if let Ok(dict) = Dictionary::try_from_variant(&entry.1) { | ||
let child = SaveDataBuilder::load_from(dict); | ||
let i_key = skey.to_string().replace(INTERNAL_PREFIX, ""); | ||
data.bind_mut() | ||
.child_builders | ||
.insert(i_key.to_godot(), child); | ||
} else { | ||
godot_warn!("Found SaveDataBuilder entry that is corrupted. Please ensure this JSON data is correct. Key=\"{}\"; expected dictionary value. Found: {}", skey, entry.1); | ||
} | ||
} else { | ||
// loads a simple data value | ||
data.bind_mut().data.set(skey, entry.1); | ||
} | ||
} | ||
data | ||
} | ||
|
||
pub fn try_load_file(file_path: GString) -> Option<Gd<SaveDataBuilder>> { | ||
let mut result = SaveDataBuilder::alloc_gd(); | ||
if !result.bind_mut().load(file_path) { | ||
None | ||
} else { | ||
Some(result) | ||
} | ||
} | ||
|
||
#[func] | ||
pub fn get_as_dict(&mut self) -> Dictionary { | ||
let mut dict = Dictionary::new(); | ||
for entry in self.data.iter_shared() { | ||
dict.insert(entry.0, entry.1); | ||
} | ||
for entry in self.child_builders.iter_mut() { | ||
let n_key: GString = | ||
(String::from(INTERNAL_PREFIX) + &entry.0.to_string().to_owned()).to_godot(); | ||
dict.insert(n_key, entry.1.bind_mut().get_as_dict()); | ||
} | ||
dict | ||
} | ||
|
||
pub fn internal_get_value<T: FromGodot + ToGodot>( | ||
&mut self, | ||
key: GString, | ||
default_value: T, | ||
) -> T { | ||
let value = self.get_value(key); | ||
if value.is_nil() { | ||
return default_value; | ||
} | ||
let Ok(valid) = T::try_from_variant(&value) else { | ||
return default_value; | ||
}; | ||
valid | ||
} | ||
} | ||
|
||
/// Unfortunately this only can be used internally, but it grants access to serialization functions for all serializable functions | ||
pub trait SquigglesSerialized { | ||
fn serialize(&mut self); | ||
fn deserialize(&mut self); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
use godot::{ | ||
engine::{AudioStream, Control}, | ||
prelude::*, | ||
}; | ||
|
||
type Sfx = Option<Gd<AudioStream>>; | ||
#[derive(GodotClass)] | ||
#[class(init, base=Node)] | ||
struct GuiInteract { | ||
#[export] | ||
auto_focus: bool, | ||
#[export] | ||
hover_sfx: Sfx, | ||
#[export] | ||
interact_sfx: Sfx, | ||
#[export] | ||
appear_sfx: Sfx, | ||
#[export] | ||
disappear_sfx: Sfx, | ||
|
||
audio_player: Option<Gd<AudioStreamPlayer>>, | ||
#[base] | ||
base: Base<Node>, | ||
} | ||
|
||
#[godot_api] | ||
impl INode for GuiInteract { | ||
fn ready(&mut self) { | ||
// load audio stream player | ||
let player = AudioStreamPlayer::new_alloc(); | ||
self.audio_player = Some(player.clone()); | ||
self.base.add_child(player.upcast()); | ||
// TODO: use a global setting to assign the audio bus and volume | ||
|
||
// get parent | ||
let Some(node_parent) = self.base.get_parent() else { | ||
return; | ||
}; | ||
let rcast: Result<Gd<Control>, _> = node_parent.try_cast(); | ||
let Ok(mut control_parent) = rcast else { | ||
return; | ||
}; | ||
|
||
// grab focus | ||
if self.auto_focus { | ||
control_parent.grab_focus(); | ||
} | ||
|
||
// connect signals | ||
self.try_connect(&mut control_parent, "pressed", "on_interact"); | ||
self.try_connect( | ||
&mut control_parent, | ||
"visibility_changed", | ||
"on_visiblity_changed", | ||
); | ||
self.try_connect(&mut control_parent, "focus_entered", "on_hover"); | ||
self.try_connect(&mut control_parent, "mouse_entered", "on_mouse_enter"); | ||
} | ||
} | ||
|
||
#[godot_api] | ||
impl GuiInteract { | ||
#[func] | ||
fn on_visiblity_changed(&mut self) { | ||
if let Some(node_parent) = self.base.get_parent() { | ||
let rcast: Result<Gd<Control>, _> = node_parent.try_cast(); | ||
if let Ok(parent) = rcast { | ||
if parent.is_visible() { | ||
if let Some(sfx) = &self.appear_sfx { | ||
self.try_play_sfx(sfx.clone()); | ||
} | ||
} else if let Some(sfx) = &self.disappear_sfx { | ||
self.try_play_sfx(sfx.clone()); | ||
} | ||
} | ||
} | ||
} | ||
|
||
#[func] | ||
fn on_mouse_enter(&mut self) { | ||
// get parent | ||
let Some(node_parent) = self.base.get_parent() else { | ||
return; | ||
}; | ||
let rcast: Result<Gd<Control>, _> = node_parent.try_cast(); | ||
let Ok(mut control_parent) = rcast else { | ||
return; | ||
}; | ||
|
||
// grab focus | ||
if self.auto_focus { | ||
control_parent.grab_focus(); | ||
} | ||
} | ||
|
||
#[func] | ||
fn on_hover(&mut self) { | ||
if let Some(sfx) = &self.hover_sfx { | ||
self.try_play_sfx(sfx.clone()); | ||
} | ||
} | ||
#[func] | ||
fn on_interact(&mut self) { | ||
if let Some(sfx) = &self.interact_sfx { | ||
self.try_play_sfx(sfx.clone()); | ||
} | ||
} | ||
|
||
fn try_play_sfx(&mut self, stream: Gd<AudioStream>) { | ||
if let Some(player) = &mut self.audio_player { | ||
player.stop(); | ||
player.set_stream(stream); | ||
player.play(); | ||
} | ||
} | ||
fn try_connect(&self, node: &mut Gd<Control>, signal_name: &str, callable: &str) -> bool { | ||
let signal = StringName::from(signal_name); | ||
if !node.has_signal(signal.clone()) { | ||
return false; | ||
} | ||
node.connect( | ||
signal, | ||
Callable::from_object_method(&self.to_gd(), callable), | ||
); | ||
|
||
true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
pub mod gui_interact; |