use std::{borrow::Cow, collections::BTreeSet, marker::PhantomData};
use crate::Str;
pub use mogwai_macros::{ViewChild, ViewProperties, rsx};
pub trait ViewText {
fn new(text: impl AsRef<str>) -> Self;
fn set_text(&self, text: impl AsRef<str>);
fn get_text(&self) -> Str;
}
pub trait ViewTextExt {
fn into_text<V: View>(self) -> V::Text;
}
impl<T: AsRef<str>> ViewTextExt for T {
fn into_text<V: View>(self) -> V::Text {
ViewText::new(self)
}
}
pub struct AppendArg<V: View, I> {
pub iter: I,
_phantom: PhantomData<V>,
}
impl<V: View, C: ViewChild<V>, T: Iterator<Item = C>> From<T> for AppendArg<V, T> {
fn from(iter: T) -> Self {
AppendArg {
iter,
_phantom: PhantomData,
}
}
}
impl<V: View, T> From<T> for AppendArg<V, Option<T>> {
fn from(value: T) -> Self {
AppendArg {
iter: Some(value),
_phantom: PhantomData,
}
}
}
impl<V: View, I> AppendArg<V, I> {
pub fn new(iter: I) -> Self {
AppendArg {
iter,
_phantom: PhantomData,
}
}
}
impl<V: View, I: Iterator> Iterator for AppendArg<V, I> {
type Item = I::Item;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next()
}
}
impl<V: View, I> AppendArg<V, I> {
pub fn map_iter<T>(self, f: impl FnOnce(I) -> T) -> AppendArg<V, T> {
AppendArg {
iter: f(self.iter),
_phantom: PhantomData,
}
}
}
pub trait ViewParent<V: View> {
fn append_node(&self, node: Cow<'_, V::Node>);
fn remove_node(&self, node: Cow<'_, V::Node>);
fn replace_node(&self, new_node: Cow<'_, V::Node>, old_node: Cow<'_, V::Node>);
fn insert_node_before(&self, new_node: Cow<'_, V::Node>, before_node: Option<Cow<'_, V::Node>>);
fn append_child(&self, child: impl ViewChild<V>) {
for node in child.as_append_arg() {
self.append_node(node);
}
}
fn remove_child(&self, child: impl ViewChild<V>) {
for node in child.as_append_arg() {
self.remove_node(node);
}
}
fn replace_child(&self, new_child: impl ViewChild<V>, old_child: impl ViewChild<V>) {
let new_nodes = new_child.as_append_arg();
let old_nodes = old_child.as_append_arg();
for (new_node, old_node) in new_nodes.zip(old_nodes) {
self.replace_node(new_node, old_node);
}
}
fn insert_child_before(
&self,
child: impl ViewChild<V>,
before_child: Option<impl ViewChild<V>>,
) {
if let Some(before_child) = before_child {
let mut before_nodes = before_child.as_append_arg();
for new_node in child.as_append_arg() {
self.insert_node_before(new_node, before_nodes.next());
}
} else {
self.append_child(child);
}
}
}
pub trait ViewChild<V: View> {
fn as_append_arg(&self) -> AppendArg<V, impl Iterator<Item = Cow<'_, V::Node>>>;
fn as_boxed_append_arg(&self) -> AppendArg<V, Box<dyn Iterator<Item = Cow<'_, V::Node>> + '_>> {
AppendArg::new(Box::new(self.as_append_arg()))
}
}
impl<V: View, T: ViewChild<V>> ViewChild<V> for &T {
fn as_append_arg(&self) -> AppendArg<V, impl Iterator<Item = Cow<'_, V::Node>>> {
T::as_append_arg(self)
}
}
impl<V: View, T: ViewChild<V> + 'static> ViewChild<V> for &mut T {
fn as_append_arg(&self) -> AppendArg<V, impl Iterator<Item = Cow<'_, V::Node>>> {
T::as_append_arg(self)
}
}
impl<V: View, T: ViewChild<V>> ViewChild<V> for Vec<T> {
fn as_append_arg(&self) -> AppendArg<V, impl Iterator<Item = Cow<'_, V::Node>>> {
AppendArg::new(self.iter().flat_map(|t| t.as_append_arg()))
}
}
impl<V: View, T: ViewChild<V>> ViewChild<V> for Option<T> {
fn as_append_arg(&self) -> AppendArg<V, impl Iterator<Item = Cow<'_, V::Node>>> {
AppendArg::new(self.iter().flat_map(|t| t.as_append_arg()))
}
}
impl<V: View> ViewChild<V> for String {
fn as_append_arg(&self) -> AppendArg<V, impl Iterator<Item = Cow<'_, V::Node>>> {
let text = self.into_text::<V>();
let mut arg = text.as_append_arg();
let node: V::Node = arg.next().unwrap().into_owned();
AppendArg::new(std::iter::once(Cow::Owned(node)))
}
}
pub trait ViewProperties {
fn has_property(&self, property: impl AsRef<str>) -> bool;
fn get_property(&self, property: impl AsRef<str>) -> Option<Str>;
fn set_property(&self, property: impl AsRef<str>, value: impl AsRef<str>);
fn remove_property(&self, property: impl AsRef<str>);
fn set_style(&self, key: impl AsRef<str>, value: impl AsRef<str>);
fn remove_style(&self, key: impl AsRef<str>);
fn add_class(&self, class: impl AsRef<str>) {
if let Some(classes) = self.get_property("class") {
let mut set = classes.split_whitespace().collect::<BTreeSet<_>>();
set.insert(class.as_ref());
let classes = set.into_iter().collect::<Vec<_>>().join(" ");
self.set_property("class", classes);
}
}
fn remove_class(&self, class: impl AsRef<str>) {
if let Some(classes) = self.get_property("class") {
let mut set = classes.split_whitespace().collect::<BTreeSet<_>>();
set.remove(class.as_ref());
let classes = set.into_iter().collect::<Vec<_>>().join(" ");
self.set_property("class", classes);
}
}
}
pub trait ViewEventListener<V: View> {
fn next(&self) -> impl Future<Output = V::Event>;
fn on_window(event_name: impl Into<Cow<'static, str>>) -> V::EventListener;
fn on_document(event_name: impl Into<Cow<'static, str>>) -> V::EventListener;
}
pub trait ViewEventTarget<V: View> {
fn listen(&self, event_name: impl Into<Cow<'static, str>>) -> V::EventListener;
}
pub trait ViewElement {
type View: View<Element = Self>;
fn new(name: impl AsRef<str>) -> Self;
fn new_namespace(name: impl AsRef<str>, ns: impl AsRef<str>) -> Self
where
Self: ViewProperties + Sized,
{
let el = Self::new(name);
el.set_property("xmlns", ns);
el
}
fn when_element<V: View, T>(&self, f: impl FnOnce(&V::Element) -> T) -> Option<T> {
let el = try_cast_el::<Self::View, V>(self)?;
let t = f(el);
Some(t)
}
}
pub trait ViewEvent {
type View: View<Event = Self>;
fn when_event<V: View, T>(&self, f: impl FnOnce(&V::Event) -> T) -> Option<T> {
let el = try_cast_ev::<Self::View, V>(self)?;
let t = f(el);
Some(t)
}
}
pub trait View: Sized + 'static {
type Node: Clone;
type Element: ViewElement
+ ViewParent<Self>
+ ViewChild<Self>
+ ViewProperties
+ ViewEventTarget<Self>
+ Clone
+ 'static;
type Text: ViewText + ViewChild<Self> + ViewEventTarget<Self> + Clone + 'static;
type EventListener: ViewEventListener<Self>;
type Event: ViewEvent;
fn is_view<W: View>() -> bool {
std::any::TypeId::of::<W>() == std::any::TypeId::of::<Self>()
}
}
fn try_cast_el<V: View, W: View>(element: &V::Element) -> Option<&W::Element> {
if V::is_view::<W>() {
Some(unsafe { &*(element as *const V::Element as *const W::Element) })
} else {
None
}
}
fn try_cast_ev<V: View, W: View>(event: &V::Event) -> Option<&W::Event> {
if V::is_view::<W>() {
Some(unsafe { &*(event as *const V::Event as *const W::Event) })
} else {
None
}
}