use crate::element::{Attach, ControlElement, Element, ElementClass, IntoChild, IntoChildren, IntoElement, PropEntry, PropValue};
use crate::reactive::{Memo, Signal, TwoWay};
use ferroui_base::data::BindingBase;
use ferroui_base::interactivity::{IRoutedEventArgs, Interactive, RoutedEvent};
use ferroui_base::{AttachedProperty, BoxedValue, DirectProperty, FerroObject, FerroProperty, ObjectType, PropertyValue, Ref, StyledProperty, Upcast};
use ferroui_controls::Control;
use std::any::Any;
use std::cell::RefCell;
use std::marker::PhantomData;
use std::rc::Rc;
pub trait ViewClass: ObjectType + Upcast<Control> {
fn create() -> Ref<Self>;
}
pub trait Builder {
type Output;
fn builder() -> Self::Output;
}
impl<T: ViewClass> Builder for T {
type Output = El<T>;
fn builder() -> El<T> {
El::new()
}
}
pub trait TypedProperty: 'static {
type Value: PropertyValue;
fn untyped(&'static self) -> &'static FerroProperty;
}
impl<T: PropertyValue> TypedProperty for StyledProperty<T> {
type Value = T;
fn untyped(&'static self) -> &'static FerroProperty {
self.as_property()
}
}
impl<T: PropertyValue> TypedProperty for AttachedProperty<T> {
type Value = T;
fn untyped(&'static self) -> &'static FerroProperty {
let styled: &'static StyledProperty<T> = self;
styled.as_property()
}
}
impl<O: ObjectType, T: PropertyValue> TypedProperty for DirectProperty<O, T> {
type Value = T;
fn untyped(&'static self) -> &'static FerroProperty {
let base: &'static ferroui_base::DirectPropertyBase<T> = self;
base.as_property()
}
}
pub trait IntoValue<T> {
fn into_value(self) -> T;
}
impl<T> IntoValue<T> for T {
fn into_value(self) -> T {
self
}
}
pub trait FromValue<T>: Sized {
fn from_value(value: &T) -> Option<Self>;
}
impl<T: Clone> FromValue<T> for T {
fn from_value(value: &T) -> Option<Self> {
Some(value.clone())
}
}
pub mod markers {
pub struct Value;
pub struct FromSignal<V>(std::marker::PhantomData<V>);
pub struct FromMemo<V>(std::marker::PhantomData<V>);
pub struct FromClosure<V>(std::marker::PhantomData<V>);
pub struct FromTwoWay<V>(std::marker::PhantomData<V>);
pub struct FromElement;
pub struct FromBinding;
}
pub trait IntoProp<T, M> {
fn into_prop(self) -> PropValue;
}
fn boxed<T: PropertyValue>(value: T) -> BoxedValue {
Rc::new(value)
}
impl<T: PropertyValue, V: IntoValue<T>> IntoProp<T, markers::Value> for V {
fn into_prop(self) -> PropValue {
PropValue::Value(boxed(self.into_value()))
}
}
impl<T: PropertyValue, V: IntoValue<T> + Clone + 'static> IntoProp<T, markers::FromSignal<V>> for Signal<V> {
fn into_prop(self) -> PropValue {
PropValue::Dynamic(Rc::new(move || boxed(self.get().into_value())))
}
}
impl<T: PropertyValue, V: IntoValue<T> + Clone + 'static> IntoProp<T, markers::FromMemo<V>> for Memo<V> {
fn into_prop(self) -> PropValue {
PropValue::Dynamic(Rc::new(move || boxed(self.get().into_value())))
}
}
impl<T: PropertyValue, V: IntoValue<T>, F: Fn() -> V + 'static> IntoProp<T, markers::FromClosure<V>> for F {
fn into_prop(self) -> PropValue {
PropValue::Dynamic(Rc::new(move || boxed(self().into_value())))
}
}
impl<T: PropertyValue, V: IntoValue<T> + FromValue<T> + Clone + PartialEq + 'static> IntoProp<T, markers::FromTwoWay<V>>
for TwoWay<V>
{
fn into_prop(self) -> PropValue {
let signal = self.0;
PropValue::TwoWay {
get: Rc::new(move || boxed(signal.get().into_value())),
set: Rc::new(move |value: &dyn Any| {
if let Some(value) = value.downcast_ref::<T>().and_then(V::from_value) {
if signal.is_alive() {
signal.set_if_changed(value);
}
}
}),
}
}
}
impl<E: IntoElement> IntoProp<Option<BoxedValue>, markers::FromElement> for E {
fn into_prop(self) -> PropValue {
PropValue::Element(Rc::new(RefCell::new(Some(self.into_element()))))
}
}
pub struct Bind<B: BindingBase + 'static>(pub B);
impl<T: PropertyValue, B: BindingBase + 'static> IntoProp<T, markers::FromBinding> for Bind<B> {
fn into_prop(self) -> PropValue {
PropValue::Binding(Rc::new(self.0))
}
}
pub struct El<T> {
element: ControlElement,
_marker: PhantomData<fn() -> T>,
}
impl<T: ViewClass> El<T> {
pub fn new() -> Self {
fn create<T: ViewClass>() -> Ref<Control> {
T::create().upcast()
}
let class = ElementClass {
name: std::any::type_name::<T>(),
type_id: std::any::TypeId::of::<T>,
create: create::<T>,
};
Self { element: ControlElement::new(class), _marker: PhantomData }
}
}
impl<T: ViewClass> Default for El<T> {
fn default() -> Self {
Self::new()
}
}
pub trait ElementBuilder: Sized {
fn element_mut(&mut self) -> &mut ControlElement;
fn prop<P: TypedProperty + ?Sized, M>(mut self, property: &'static P, value: impl IntoProp<P::Value, M>) -> Self {
let property = property.untyped();
let value = value.into_prop();
let props = &mut self.element_mut().props;
props.retain(|entry| !std::ptr::eq(entry.property, property));
props.push(PropEntry { property, value });
self
}
fn routed<A: IRoutedEventArgs + 'static>(
mut self,
event: &'static RoutedEvent<A>,
handler: impl Fn(&Interactive, &A) + 'static,
) -> Self {
let handler = Rc::new(handler);
let attach: Attach = Rc::new(move |control: &Ref<Control>| {
let handler = handler.clone();
control.add_handler(event, move |sender, e| handler(sender, e));
});
self.element_mut().handlers.push(attach);
self
}
}
impl<T> ElementBuilder for El<T> {
fn element_mut(&mut self) -> &mut ControlElement {
&mut self.element
}
}
impl<T> El<T> {
pub fn key(mut self, key: impl Into<crate::Key>) -> Self {
self.element.key = Some(key.into());
self
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.element.name = Some(name.into());
self
}
pub fn class(mut self, classes: &str) -> Self {
self.element.classes.extend(classes.split_whitespace().map(str::to_string));
self
}
pub fn classes(self, classes: &str) -> Self {
self.class(classes)
}
pub fn set<P: TypedProperty + ?Sized, M>(self, property: &'static P, value: impl IntoProp<P::Value, M>) -> Self {
self.prop(property, value)
}
pub fn bind<P: TypedProperty + ?Sized>(mut self, property: &'static P, binding: impl BindingBase + 'static) -> Self {
let property = property.untyped();
self.element.props.push(PropEntry { property, value: PropValue::Binding(Rc::new(binding)) });
self
}
pub fn on<A: IRoutedEventArgs + 'static>(
self,
event: &'static RoutedEvent<A>,
handler: impl Fn(&Interactive, &A) + 'static,
) -> Self {
self.routed(event, handler)
}
pub fn on_changed<P: TypedProperty + ?Sized>(mut self, property: &'static P, handler: impl Fn(&P::Value) + 'static) -> Self {
let untyped = property.untyped();
let handler = Rc::new(handler);
let attach: Attach = Rc::new(move |control: &Ref<Control>| {
let handler = handler.clone();
let subscription = control.property_changed(move |e| {
if std::ptr::eq(e.property(), untyped) {
if let Some(value) = e.new_value().downcast_ref::<P::Value>() {
handler(value);
}
}
});
crate::reactive::on_cleanup(move || subscription.dispose());
});
self.element.handlers.push(attach);
self
}
pub fn with(mut self, f: impl Fn(&Ref<T>) + 'static) -> Self
where
T: ObjectType,
{
let attach: Attach = Rc::new(move |control: &Ref<Control>| {
if let Some(control) = control.cast::<T>() {
f(&control);
}
});
self.element.on_mount.push(attach);
self
}
pub fn node_ref(self, node_ref: crate::NodeRef<T>) -> Self
where
T: ObjectType,
{
self.with(move |control| node_ref.set(control.clone()))
}
pub fn child(mut self, child: impl IntoChild) -> Self {
self.element.children.push(child.into_child());
self
}
pub fn children(mut self, children: impl IntoChildren) -> Self {
self.element.children.extend(children.into_children());
self
}
pub fn build(self) -> Element {
Element::Control(Box::new(self.element))
}
}
impl<T> IntoElement for El<T> {
fn into_element(self) -> Element {
self.build()
}
}
impl<T> From<El<T>> for Element {
fn from(builder: El<T>) -> Self {
builder.build()
}
}
#[doc(hidden)]
pub fn object_of(control: &Ref<Control>) -> &FerroObject {
control
}