use std::collections::HashMap;
use bevy::prelude::*;
use noesis_runtime::view::FrameworkElement;
use crate::render::{NoesisRenderState, NoesisSet};
#[derive(Debug, Clone, PartialEq)]
pub enum DpValue {
F32(f32),
F64(f64),
I32(i32),
Bool(bool),
Str(String),
}
impl DpValue {
#[must_use]
pub fn write_to(&self, element: &mut FrameworkElement, property: &str) -> bool {
match self {
Self::F32(v) => element.set_f32(property, *v),
Self::F64(v) => element.set_f64(property, *v),
Self::I32(v) => element.set_i32(property, *v),
Self::Bool(v) => element.set_bool(property, *v),
Self::Str(v) => element.set_string(property, v),
}
}
#[must_use]
pub fn to_boxed(&self) -> noesis_runtime::binding::Boxed {
use noesis_runtime::binding::{box_bool, box_f32, box_f64, box_i32, box_string};
match self {
Self::F32(v) => box_f32(*v),
Self::F64(v) => box_f64(*v),
Self::I32(v) => box_i32(*v),
Self::Bool(v) => box_bool(*v),
Self::Str(v) => box_string(v),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DpKind {
F32,
F64,
I32,
Bool,
Str,
}
impl DpKind {
#[must_use]
pub fn read_from(self, element: &FrameworkElement, property: &str) -> Option<DpValue> {
match self {
Self::F32 => element.get_f32(property).map(DpValue::F32),
Self::F64 => element.get_f64(property).map(DpValue::F64),
Self::I32 => element.get_i32(property).map(DpValue::I32),
Self::Bool => element.get_bool(property).map(DpValue::Bool),
Self::Str => element.get_string(property).map(DpValue::Str),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DpWatch {
pub name: String,
pub property: String,
pub kind: DpKind,
}
impl DpWatch {
pub fn new(name: impl Into<String>, property: impl Into<String>, kind: DpKind) -> Self {
Self {
name: name.into(),
property: property.into(),
kind,
}
}
}
#[derive(Component, Clone, Default, Debug)]
pub struct NoesisDp {
pub set: HashMap<(String, String), DpValue>,
pub watch: Vec<DpWatch>,
}
impl NoesisDp {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn set_f32(self, name: impl Into<String>, property: impl Into<String>, value: f32) -> Self {
self.insert(name, property, DpValue::F32(value))
}
#[must_use]
pub fn set_f64(self, name: impl Into<String>, property: impl Into<String>, value: f64) -> Self {
self.insert(name, property, DpValue::F64(value))
}
#[must_use]
pub fn set_i32(self, name: impl Into<String>, property: impl Into<String>, value: i32) -> Self {
self.insert(name, property, DpValue::I32(value))
}
#[must_use]
pub fn set_bool(
self,
name: impl Into<String>,
property: impl Into<String>,
value: bool,
) -> Self {
self.insert(name, property, DpValue::Bool(value))
}
#[must_use]
pub fn set_string(
self,
name: impl Into<String>,
property: impl Into<String>,
value: impl Into<String>,
) -> Self {
self.insert(name, property, DpValue::Str(value.into()))
}
#[must_use]
pub fn watch(
mut self,
name: impl Into<String>,
property: impl Into<String>,
kind: DpKind,
) -> Self {
self.watch.push(DpWatch::new(name, property, kind));
self
}
pub fn write_f32(&mut self, name: impl Into<String>, property: impl Into<String>, value: f32) {
self.write(name, property, DpValue::F32(value));
}
pub fn write_f64(&mut self, name: impl Into<String>, property: impl Into<String>, value: f64) {
self.write(name, property, DpValue::F64(value));
}
pub fn write_i32(&mut self, name: impl Into<String>, property: impl Into<String>, value: i32) {
self.write(name, property, DpValue::I32(value));
}
pub fn write_bool(
&mut self,
name: impl Into<String>,
property: impl Into<String>,
value: bool,
) {
self.write(name, property, DpValue::Bool(value));
}
pub fn write_string(
&mut self,
name: impl Into<String>,
property: impl Into<String>,
value: impl Into<String>,
) {
self.write(name, property, DpValue::Str(value.into()));
}
pub fn observe(&mut self, name: impl Into<String>, property: impl Into<String>, kind: DpKind) {
self.watch.push(DpWatch::new(name, property, kind));
}
fn insert(
mut self,
name: impl Into<String>,
property: impl Into<String>,
value: DpValue,
) -> Self {
self.set.insert((name.into(), property.into()), value);
self
}
fn write(&mut self, name: impl Into<String>, property: impl Into<String>, value: DpValue) {
self.set.insert((name.into(), property.into()), value);
}
}
#[derive(Message, Debug, Clone)]
pub struct NoesisDpChanged {
pub view: Entity,
pub name: String,
pub property: String,
pub value: DpValue,
}
#[allow(clippy::needless_pass_by_value)]
pub(crate) fn sync_dp_bridge(
views: Query<(Entity, Ref<NoesisDp>)>,
state: Option<NonSendMut<NoesisRenderState>>,
mut changed: MessageWriter<NoesisDpChanged>,
) {
let Some(mut state) = state else {
return;
};
for (entity, dp) in &views {
if dp.is_changed() || state.scene_rebuilt_this_frame(entity) {
state.apply_dp_for(entity, &dp.set);
}
for (name, property, value) in state.poll_dp_reads_for(entity, &dp.watch) {
changed.write(NoesisDpChanged {
view: entity,
name,
property,
value,
});
}
}
}
pub struct NoesisDpPlugin;
impl Plugin for NoesisDpPlugin {
fn build(&self, app: &mut App) {
app.add_message::<NoesisDpChanged>()
.add_systems(PostUpdate, sync_dp_bridge.in_set(NoesisSet::Apply));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builder_collects_set_and_watch() {
let dp = NoesisDp::new()
.set_f32("S", "Value", 0.5)
.set_f64("S", "Double", 1.5)
.set_i32("C", "SelectedIndex", 2)
.set_bool("B", "IsEnabled", false)
.set_string("T", "Text", "hi")
.watch("S", "Value", DpKind::F32)
.watch("C", "SelectedIndex", DpKind::I32);
assert_eq!(
dp.set.get(&("S".into(), "Value".into())),
Some(&DpValue::F32(0.5)),
);
assert_eq!(
dp.set.get(&("S".into(), "Double".into())),
Some(&DpValue::F64(1.5)),
);
assert_eq!(
dp.set.get(&("C".into(), "SelectedIndex".into())),
Some(&DpValue::I32(2)),
);
assert_eq!(
dp.set.get(&("B".into(), "IsEnabled".into())),
Some(&DpValue::Bool(false)),
);
assert_eq!(
dp.set.get(&("T".into(), "Text".into())),
Some(&DpValue::Str("hi".into())),
);
assert_eq!(dp.watch.len(), 2);
assert_eq!(dp.watch[0], DpWatch::new("S", "Value", DpKind::F32));
assert_eq!(dp.watch[1].kind, DpKind::I32);
}
}