use crate::metadata::PseudoClassesAttribute;
use crate::primitives::TemplatedControlImpl;
use crate::templates::IDataTemplate;
use crate::{ContentControl, ContentControlImpl, Control, ControlImpl};
use ferroui_base::data::{
AggregateError, AggregateException, BindingChainException, BindingError, DataValidationException,
};
use ferroui_base::input::InputElementImpl;
use ferroui_base::interactivity::InteractiveImpl;
use ferroui_base::layout::LayoutableImpl;
use ferroui_base::{
ferro_class, ferro_impl_classes, ferro_property, instantiate, AttachedProperty, BoxedValue, DirectProperty,
ElementRef, FerroObject, FerroObjectImpl, FerroProperty, FerroPropertyChangedEventArgs, Ref,
StyledElement, StyledElementImpl, StyledProperty, VisualImpl,
};
use std::cell::RefCell;
use std::fmt;
use std::rc::Rc;
#[derive(Clone)]
pub struct ErrorConverter(Rc<dyn Fn(&BoxedValue) -> Option<BoxedValue>>);
impl ErrorConverter {
pub fn new(convert: impl Fn(&BoxedValue) -> Option<BoxedValue> + 'static) -> Self {
Self(Rc::new(convert))
}
pub fn convert(&self, error: &BoxedValue) -> Option<BoxedValue> {
(self.0)(error)
}
}
impl PartialEq for ErrorConverter {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.0, &other.0)
}
}
impl fmt::Debug for ErrorConverter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("ErrorConverter")
}
}
#[repr(C)]
pub struct DataValidationErrors {
base: ContentControl,
owner: RefCell<Option<ElementRef<Control>>>,
}
ferro_class!(DataValidationErrors: ContentControl);
ferroui_base::ferro_class_info!(DataValidationErrors { new: DataValidationErrors::new });
ferro_impl_classes!(
DataValidationErrors: StyledElementImpl,
VisualImpl,
LayoutableImpl,
InteractiveImpl,
InputElementImpl,
ControlImpl,
TemplatedControlImpl,
ContentControlImpl
);
ferroui_base::ferro_impl_classes!(DataValidationErrors: FerroObjectImpl);
impl DataValidationErrors {
pub const PSEUDO_CLASSES: PseudoClassesAttribute = PseudoClassesAttribute::new(&[":error"]);
}
ferroui_base::ferro_properties! { impl DataValidationErrors {
ferro_property!(
pub fn errors_property() -> AttachedProperty<Option<Vec<BoxedValue>>> {
FerroProperty::register_attached::<DataValidationErrors, Control, _>("Errors", None)
}
);
ferro_property!(
pub fn has_errors_property() -> AttachedProperty<bool> {
FerroProperty::register_attached::<DataValidationErrors, Control, _>("HasErrors", false)
}
);
ferro_property!(
pub fn error_converter_property() -> AttachedProperty<Option<ErrorConverter>> {
FerroProperty::register_attached::<DataValidationErrors, Control, _>("ErrorConverter", None)
}
);
ferro_property!(
pub fn error_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
FerroProperty::register::<DataValidationErrors, _>("ErrorTemplate", None)
}
);
ferro_property!(
fn original_errors_property() -> AttachedProperty<Option<Vec<BoxedValue>>> {
FerroProperty::register_attached::<DataValidationErrors, Control, _>("OriginalErrors", None)
}
);
ferro_property!(
fn overriding_errors_internally_property() -> AttachedProperty<bool> {
FerroProperty::register_attached::<DataValidationErrors, Control, _>("OverridingErrorsInternally", false)
}
);
ferro_property!(
pub fn owner_property() -> DirectProperty<DataValidationErrors, Option<ElementRef<Control>>> {
ferroui_base::data::core::ValueTypes::register_element_ref::<Control>();
FerroProperty::register_direct::<DataValidationErrors, _>(
"Owner",
|o| o.owner.borrow().clone(),
Some(|o, v| o.set_owner(ElementRef::resolve(&v))),
None,
)
}
);
} }
impl DataValidationErrors {
fn static_constructor() {
Self::errors_property().changed().subscribe(Self::errors_changed);
Self::has_errors_property().changed().subscribe(Self::has_errors_changed);
StyledElement::templated_parent_property()
.changed()
.add_class_handler::<DataValidationErrors>(|x, e| x.on_templated_parent_change(e));
Self::error_converter_property().changed().subscribe(Self::on_error_converter_changed);
}
pub fn construct() -> Self {
Self { base: ContentControl::construct(), owner: RefCell::new(None) }
}
pub fn new() -> Ref<Self> {
instantiate(Self::construct())
}
pub fn owner(&self) -> Option<Ref<Control>> {
ElementRef::resolve(&self.owner.borrow())
}
pub fn set_owner(&self, value: Option<Ref<Control>>) {
self.set_and_raise(Self::owner_property(), &self.owner, ElementRef::from_nullable(value));
}
fn sender_control(e: &FerroPropertyChangedEventArgs<'_>) -> Ref<Control> {
match e.sender().downcast_ref::<Control>() {
Some(control) => control.to_ref(),
None => panic!(
"Unable to cast object of type '{}' to type 'Control'.",
e.sender().get_type().name()
),
}
}
fn on_error_converter_changed(e: &FerroPropertyChangedEventArgs<'_>) {
Self::on_errors_or_converter_changed(&Self::sender_control(e));
}
fn on_templated_parent_change(&self, e: &FerroPropertyChangedEventArgs<'_>) {
if self.owner().is_none() {
let new_value = e.get_new_value::<Option<Ref<FerroObject>>>();
self.set_owner(new_value.and_then(|value| value.cast::<Control>()));
}
}
pub fn error_template(&self) -> Option<Rc<dyn IDataTemplate>> {
self.get_value(Self::error_template_property())
}
pub fn set_error_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
self.set_value(Self::error_template_property(), value)
}
fn errors_changed(e: &FerroPropertyChangedEventArgs<'_>) {
let control = Self::sender_control(e);
if control.get_value(Self::overriding_errors_internally_property()) {
return;
}
let errors = e.get_new_value::<Option<Vec<BoxedValue>>>();
control.set_value(Self::original_errors_property(), errors);
Self::on_errors_or_converter_changed(&control);
}
fn has_errors_changed(e: &FerroPropertyChangedEventArgs<'_>) {
let control = Self::sender_control(e);
control.pseudo_classes().set(":error", e.get_new_value::<bool>());
}
pub fn get_errors(control: &Control) -> Option<Vec<BoxedValue>> {
control.get_value(Self::errors_property())
}
pub fn set_errors(control: &Control, errors: Option<Vec<BoxedValue>>) {
control.set_value(Self::errors_property(), errors);
}
pub fn set_error(control: &Control, error: Option<&BindingError>) {
Self::set_errors(
control,
Self::unpack_exception(error)
.map(|errors| errors.iter().filter_map(Self::unpack_data_validation_exception).collect()),
);
}
fn on_errors_or_converter_changed(control: &Control) {
let converter = Self::get_error_converter(control);
let original_errors = control.get_value(Self::original_errors_property());
let new_errors: Option<Vec<BoxedValue>> = match converter {
None => original_errors,
Some(converter) => {
original_errors.map(|errors| errors.iter().filter_map(|error| converter.convert(error)).collect())
}
};
let has_errors = new_errors.as_ref().is_some_and(|errors| !errors.is_empty());
control.set_current_value(Self::overriding_errors_internally_property(), true);
{
struct Restore<'a>(&'a Control);
impl Drop for Restore<'_> {
fn drop(&mut self) {
self.0.set_current_value(DataValidationErrors::overriding_errors_internally_property(), false);
}
}
let _restore = Restore(control);
control.set_current_value(Self::errors_property(), new_errors);
}
control.set_value(Self::has_errors_property(), has_errors);
}
pub fn clear_errors(control: &Control) {
Self::set_errors(control, None);
}
pub fn get_has_errors(control: &Control) -> bool {
control.get_value(Self::has_errors_property())
}
pub fn get_error_converter(control: &Control) -> Option<ErrorConverter> {
control.get_value(Self::error_converter_property())
}
pub fn set_error_converter(control: &Control, converter: Option<ErrorConverter>) {
control.set_value(Self::error_converter_property(), converter);
}
fn unpack_exception(exception: Option<&BindingError>) -> Option<Vec<BindingError>> {
let exception = exception?;
let inner = exception.inner();
let exceptions: Vec<BindingError> = if let Some(aggregate) = inner.downcast_ref::<AggregateError>() {
aggregate.inner_errors().to_vec()
} else if let Some(aggregate) = inner.downcast_ref::<AggregateException>() {
aggregate.inner_exceptions().iter().cloned().map(BindingError::new).collect()
} else {
vec![exception.clone()]
};
Some(exceptions.into_iter().filter(|x| !x.inner().is::<BindingChainException>()).collect())
}
fn unpack_data_validation_exception(exception: &BindingError) -> Option<BoxedValue> {
if let Some(data_validation_exception) = exception.inner().downcast_ref::<DataValidationException>() {
return data_validation_exception.error_data().cloned();
}
let error: BoxedValue = Rc::new(exception.clone());
Some(error)
}
}