use bevy::prelude::*;
use noesis_runtime::binding::{Binding, set_binding};
use noesis_runtime::converters::Converter;
use noesis_runtime::multi_binding::{MultiBinding, MultiConverter};
use noesis_runtime::view::FrameworkElement;
use crate::render::{NoesisRenderState, NoesisSet};
pub use noesis_runtime::binding::BindingMode;
pub use noesis_runtime::converters::{ConvertArg, Converted, ValueConverter};
pub use noesis_runtime::multi_binding::MultiValueConverter;
#[derive(Clone, Debug)]
enum BindingSource {
DataContext,
ElementName(String),
Own,
}
#[derive(Clone, Debug)]
pub struct SourceSpec {
path: String,
source: BindingSource,
}
impl SourceSpec {
#[must_use]
pub fn data_context(path: impl Into<String>) -> Self {
Self {
path: path.into(),
source: BindingSource::DataContext,
}
}
#[must_use]
pub fn element(name: impl Into<String>, path: impl Into<String>) -> Self {
Self {
path: path.into(),
source: BindingSource::ElementName(name.into()),
}
}
#[must_use]
pub fn own(path: impl Into<String>) -> Self {
Self {
path: path.into(),
source: BindingSource::Own,
}
}
fn build(&self) -> Binding {
let binding = Binding::new(&self.path);
match &self.source {
BindingSource::DataContext => binding,
BindingSource::ElementName(name) => binding.element_name(name),
BindingSource::Own => binding.relative_source_self(),
}
}
}
type BoxedConverter = Box<dyn ValueConverter + Sync>;
type BoxedMultiConverter = Box<dyn MultiValueConverter + Sync>;
struct DynConverter(BoxedConverter);
impl ValueConverter for DynConverter {
fn convert(&self, value: &ConvertArg, param: &ConvertArg) -> Option<Converted> {
self.0.convert(value, param)
}
fn convert_back(&self, value: &ConvertArg, param: &ConvertArg) -> Option<Converted> {
self.0.convert_back(value, param)
}
}
struct DynMultiConverter(BoxedMultiConverter);
impl MultiValueConverter for DynMultiConverter {
fn convert(&self, values: &[ConvertArg], param: &ConvertArg) -> Option<Converted> {
self.0.convert(values, param)
}
}
enum BindSpec {
Converted {
source: SourceSpec,
converter: Option<BoxedConverter>,
mode: BindingMode,
},
Multi {
sources: Vec<SourceSpec>,
converter: Option<BoxedMultiConverter>,
mode: BindingMode,
},
}
impl BindSpec {
fn mode_mut(&mut self) -> &mut BindingMode {
match self {
BindSpec::Converted { mode, .. } | BindSpec::Multi { mode, .. } => mode,
}
}
fn take_built(&mut self) -> Option<BuiltBinding> {
match self {
BindSpec::Converted {
source,
converter,
mode,
} => {
let boxed = converter.take()?;
let conv = Converter::new(DynConverter(boxed));
let binding = source.build().mode(*mode).converter(&conv);
Some(BuiltBinding::Single {
binding,
_converter: conv,
})
}
BindSpec::Multi {
sources,
converter,
mode,
} => {
let boxed = converter.take()?;
let conv = MultiConverter::new(DynMultiConverter(boxed));
let mut binding = MultiBinding::new().converter(&conv).mode(*mode);
for source in sources.iter() {
binding = binding.add_binding(source.build());
}
Some(BuiltBinding::Multi {
binding,
_converter: conv,
})
}
}
}
}
struct BindTarget {
element: String,
property: String,
spec: BindSpec,
}
#[derive(Component, Default)]
pub struct NoesisBinding {
targets: Vec<BindTarget>,
}
impl NoesisBinding {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn converted<C: ValueConverter + Sync>(
mut self,
element: impl Into<String>,
property: impl Into<String>,
source: SourceSpec,
converter: C,
) -> Self {
self.targets.push(BindTarget {
element: element.into(),
property: property.into(),
spec: BindSpec::Converted {
source,
converter: Some(Box::new(converter)),
mode: BindingMode::OneWay,
},
});
self
}
#[must_use]
pub fn multi<C: MultiValueConverter + Sync>(
mut self,
element: impl Into<String>,
property: impl Into<String>,
sources: impl IntoIterator<Item = SourceSpec>,
converter: C,
) -> Self {
self.targets.push(BindTarget {
element: element.into(),
property: property.into(),
spec: BindSpec::Multi {
sources: sources.into_iter().collect(),
converter: Some(Box::new(converter)),
mode: BindingMode::OneWay,
},
});
self
}
#[must_use]
pub fn mode(mut self, mode: BindingMode) -> Self {
if let Some(target) = self.targets.last_mut() {
*target.spec.mode_mut() = mode;
}
self
}
}
pub(crate) enum BuiltBinding {
Single {
binding: Binding,
_converter: Converter,
},
Multi {
binding: MultiBinding,
_converter: MultiConverter,
},
}
pub struct BindingEntry {
built: BuiltBinding,
bound_for_uri: Option<String>,
}
impl BindingEntry {
pub(crate) fn new(built: BuiltBinding) -> Self {
Self {
built,
bound_for_uri: None,
}
}
pub(crate) fn needs_bind(&self, uri: &str) -> bool {
self.bound_for_uri.as_deref() != Some(uri)
}
pub(crate) fn mark_bound(&mut self, uri: &str) {
self.bound_for_uri = Some(uri.to_owned());
}
pub(crate) fn reset_bind(&mut self) {
self.bound_for_uri = None;
}
pub(crate) fn bind_onto(&self, element: &FrameworkElement, property: &str) -> bool {
match &self.built {
BuiltBinding::Single { binding, .. } => set_binding(element, property, binding),
BuiltBinding::Multi { binding, .. } => binding.set_on(element, property),
}
}
}
#[allow(clippy::needless_pass_by_value)]
pub(crate) fn sync_binding_bridge(
mut views: Query<(Entity, &mut NoesisBinding)>,
state: Option<NonSendMut<NoesisRenderState>>,
) {
let Some(mut state) = state else {
return;
};
for (entity, mut comp) in &mut views {
let comp = comp.bypass_change_detection();
for target in &mut comp.targets {
if state.has_binding(entity, &target.element, &target.property) {
continue;
}
let Some(built) = target.spec.take_built() else {
continue;
};
state.insert_binding(
entity,
target.element.clone(),
target.property.clone(),
built,
);
}
state.bind_pending_for(entity);
}
}
pub struct NoesisBindingPlugin;
impl Plugin for NoesisBindingPlugin {
fn build(&self, app: &mut App) {
app.add_systems(PostUpdate, sync_binding_bridge.in_set(NoesisSet::Apply));
}
}