diff --git a/.gitignore b/.gitignore index 6936990..98e5fcf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -/target +target **/*.rs.bk Cargo.lock diff --git a/maple-core/.gitignore b/maple-core/.gitignore new file mode 100644 index 0000000..6936990 --- /dev/null +++ b/maple-core/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/Cargo.toml b/maple-core/Cargo.toml similarity index 61% rename from Cargo.toml rename to maple-core/Cargo.toml index d1c12d4..4e2d357 100644 --- a/Cargo.toml +++ b/maple-core/Cargo.toml @@ -1,6 +1,9 @@ +cargo-features = ["edition"] + [package] -name = "vdom_lib" +name = "maple-core" version = "0.1.0" authors = ["Andrey Tkachenko "] +edition = "2018" [dependencies] diff --git a/src/callback.rs b/maple-core/src/callback.rs similarity index 94% rename from src/callback.rs rename to maple-core/src/callback.rs index dba661b..62e8d96 100644 --- a/src/callback.rs +++ b/maple-core/src/callback.rs @@ -1,5 +1,7 @@ use std::marker::PhantomData; +use crate::component::ComponentMsg; + pub trait Callback { type Arg; diff --git a/src/children.rs b/maple-core/src/children.rs similarity index 61% rename from src/children.rs rename to maple-core/src/children.rs index 9adf8ed..2372b07 100644 --- a/src/children.rs +++ b/maple-core/src/children.rs @@ -1,3 +1,7 @@ +use crate::engine::Engine; +use crate::renderable::Renderable; +use std::marker::PhantomData; + #[macro_export] macro_rules! children { ($w:expr) => ($w); @@ -6,8 +10,8 @@ macro_rules! children { pub struct Children2 where E: Engine, - CH0: Renderable, - CH1: Renderable + CH0: Renderable, + CH1: Renderable { _e: PhantomData, @@ -17,8 +21,8 @@ pub struct Children2 impl Children2 where E: Engine, - CH0: Renderable, - CH1: Renderable + CH0: Renderable, + CH1: Renderable { pub fn new(child0: CH0, child1: CH1) -> Self { Self { @@ -29,14 +33,12 @@ impl Children2 } } -impl Renderable for Children2 +impl Renderable for Children2 where E: Engine, - CH0: Renderable, - CH1: Renderable + CH0: Renderable, + CH1: Renderable { - type Engine = E; - - fn render(&self, eng: &Self::Engine) { + fn render(&self, eng: &E) { self.child0.render(eng); self.child1.render(eng); } diff --git a/src/component.rs b/maple-core/src/component.rs similarity index 93% rename from src/component.rs rename to maple-core/src/component.rs index 01c7c2a..fed51f9 100644 --- a/src/component.rs +++ b/maple-core/src/component.rs @@ -1,4 +1,6 @@ +use std::default::Default; + pub trait Component { type Props: Default; type Msg = !; @@ -10,6 +12,11 @@ pub trait Component { } } + +pub trait ComponentMsg { + type Component: Component; +} + impl Component for ! { type Props = (); @@ -18,10 +25,6 @@ impl Component for ! { } } -pub trait ComponentMsg { - type Component: Component; -} - impl ComponentMsg for () { type Component = !; } diff --git a/src/context.rs b/maple-core/src/context.rs similarity index 100% rename from src/context.rs rename to maple-core/src/context.rs diff --git a/src/convert.rs b/maple-core/src/convert.rs similarity index 100% rename from src/convert.rs rename to maple-core/src/convert.rs diff --git a/src/engine.rs b/maple-core/src/engine.rs similarity index 100% rename from src/engine.rs rename to maple-core/src/engine.rs diff --git a/maple-core/src/lib.rs b/maple-core/src/lib.rs new file mode 100644 index 0000000..e0eeb4e --- /dev/null +++ b/maple-core/src/lib.rs @@ -0,0 +1,25 @@ +#![feature(associated_type_defaults, never_type, unsize, specialization)] + +pub mod convert; +pub mod engine; +pub mod context; +pub mod callback; +pub mod component; + +#[macro_use] +pub mod children; +pub mod renderable; +pub mod node; +pub mod view; + +pub mod prelude { + pub use crate::convert::{MyFrom, MyInto}; + pub use crate::engine::Engine; + pub use crate::context::{Context, DefaultContext}; + pub use crate::callback::{Callback, Callback1}; + pub use crate::component::{Component, ComponentMsg}; + pub use crate::children::{Children2}; + pub use crate::renderable::{Renderable, RenderImplementation, Stub}; + pub use crate::node::Node; + pub use crate::view::View; +} \ No newline at end of file diff --git a/maple-core/src/node.rs b/maple-core/src/node.rs new file mode 100644 index 0000000..88aa825 --- /dev/null +++ b/maple-core/src/node.rs @@ -0,0 +1,46 @@ +use crate::engine::Engine; +use crate::renderable::{Renderable, RenderImplementation}; +use std::marker::PhantomData; +/// +/// +/// +/// +pub struct Node + where + E: Engine, + CHE: Engine, + I: RenderImplementation, + CH: Renderable +{ + item: I, + children: CH, + _m: PhantomData<(E, CHE)> +} + +impl Node + where + E: Engine, + CHE: Engine, + I: RenderImplementation, + CH: Renderable +{ + pub fn new(item: I, children: CH) -> Self { + Self { + item, + children, + _m: Default::default() + } + } +} + +impl Renderable for Node + where + E: Engine, + CHE: Engine, + I: RenderImplementation, + CH: Renderable +{ + fn render(&self, eng: &E) { + self.item.render_impl(eng, &self.children) + } +} diff --git a/maple-core/src/renderable.rs b/maple-core/src/renderable.rs new file mode 100644 index 0000000..6a8c247 --- /dev/null +++ b/maple-core/src/renderable.rs @@ -0,0 +1,76 @@ +use std::marker::PhantomData; + +use crate::engine::Engine; + +/// defines implementation of how the children elements binds to the component +/// in dom backend it actually attaches/detaches elements to each other +/// +/// +pub trait RenderImplementation + where E: Engine, + CE: Engine, +{ + fn render_impl>(&self, eng: &E, children: &C); +} + +/// Trait Renderable +/// defines implementation of how the component should be rendered itself +/// +/// +/// +pub trait Renderable + where E: Engine, +{ + fn render(&self, eng: &E); +} + +/// +/// for nodes without children +/// +/// +/// +#[derive(Default)] +pub struct Stub { + _e: PhantomData +} + +impl Renderable for Stub { + fn render(&self, _eng: &E) {} +} + +// default impl Renderable for T +// where E: Engine, +// T: RenderImplementation, +// { +// fn render(&self, eng: &E) { +// self.render_impl(eng, &Stub::default()) +// } +// } + +impl Renderable for Box> { + fn render(&self, eng: &E) { + (**self).render(eng); + } +} + +impl Renderable for Vec + where E: Engine, + T: Renderable +{ + fn render(&self, eng: &E) { + for child in self.iter() { + child.render(eng); + } + } +} + +impl Renderable for Option + where E: Engine, + T: Renderable +{ + fn render(&self, eng: &E) { + if let Some(inner) = self { + inner.render(eng); + } + } +} \ No newline at end of file diff --git a/src/view.rs b/maple-core/src/view.rs similarity index 63% rename from src/view.rs rename to maple-core/src/view.rs index 2dd0016..df3faa1 100644 --- a/src/view.rs +++ b/maple-core/src/view.rs @@ -1,21 +1,21 @@ +use crate::engine::Engine; +use crate::renderable::Renderable; +use crate::context::Context; /// /// /// /// /// -pub trait View { +pub trait View + where E: Engine, + CE: Engine, +{ type InputContext: Context; type OutputContext: Context; + type Renderable: Renderable; fn receive_context(&mut self, ctx: Self::InputContext) -> Self::OutputContext; -} - -pub trait ViewX { - type Engine: Engine; - type ChildrenEngine: Engine = Self::Engine; - type Renderable: Renderable; - - fn build + 'static>(self, children: Option) -> Self::Renderable; + fn build + 'static>(self, children: Option) -> Self::Renderable; } // It will have sense when `specialization` will be ready to use diff --git a/maple-examples/.gitignore b/maple-examples/.gitignore new file mode 100644 index 0000000..6936990 --- /dev/null +++ b/maple-examples/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/maple-examples/Cargo.toml b/maple-examples/Cargo.toml new file mode 100644 index 0000000..b497803 --- /dev/null +++ b/maple-examples/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "maple-examples" +version = "0.1.0" +authors = ["Andrey Tkachenko "] + +[dependencies] +maple = {path = "../maple"} +maple-stdweb-dom = {path = "../maple-stdweb-dom"} \ No newline at end of file diff --git a/maple-examples/examples/simple.rs b/maple-examples/examples/simple.rs new file mode 100644 index 0000000..1ebb8a7 --- /dev/null +++ b/maple-examples/examples/simple.rs @@ -0,0 +1,82 @@ +#![feature(proc_macro_non_items)] +#![allow(dead_code)] + +#[macro_use] +extern crate maple; +extern crate maple_stdweb_dom; + +macro_rules! children { + ($w:expr) => ($w); + ($w1:expr, $($rest:tt)*) => (Children2::new($w1, children!($($rest)*))); +} + +use maple::prelude::*; + +use std::default::Default; +use maple::prelude::tabs::*; +use maple::prelude::prelude::Panel; + +use maple_stdweb_dom::*; + +// fn get_rest(count: usize) -> impl Renderable { +// let mut vec = Vec::new(); + +// for i in 0..count { +// vec.push(view! { "Number " {format!("{}", i + 1)} }); +// } + +// return vec; +// } + + +fn main() { + let x = { + let ctx_default = DefaultContext; + + let mut p0_props =
::Props::default(); + let mut comp_p0 = Div::create(p0_props); + + // let ctx_p0 = comp_p0.receive_context(ctx_default.clone()); + // let p0 = comp_p0.build::>(None); + + + // p0 + 0 + }; + + // let x = view! { + //
+ //
+ // + // + // + // + //
+ + // + // + //
+ // + // + // "Tab 1 Content" + // + + // + // + // + + // + // { ... get_rest(5) } + // + // + // + // + //
+ // }; + + let eng = HtmlEngine::new(); + + // x.render(&eng); + + println!("{}", eng.to_string()); +} diff --git a/maple-examples/src/lib.rs b/maple-examples/src/lib.rs new file mode 100644 index 0000000..31e1bb2 --- /dev/null +++ b/maple-examples/src/lib.rs @@ -0,0 +1,7 @@ +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} diff --git a/maple-macro/.gitignore b/maple-macro/.gitignore new file mode 100644 index 0000000..088ba6b --- /dev/null +++ b/maple-macro/.gitignore @@ -0,0 +1,10 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk diff --git a/maple-macro/Cargo.toml b/maple-macro/Cargo.toml new file mode 100644 index 0000000..273c9c4 --- /dev/null +++ b/maple-macro/Cargo.toml @@ -0,0 +1,16 @@ +cargo-features = ["edition"] + +[package] +name = "maple-macro" +version = "0.1.1" +authors = ["Andrey Tkachenko "] +edition = "2018" + +[dependencies] +syn = {version = "*", features=["full", "extra-traits"]} +quote = "*" +proc-macro2 = {version = "*", features=["nightly", "proc-macro"]} +maple-core = { path="../maple-core" } + +[lib] +proc-macro = true \ No newline at end of file diff --git a/maple-macro/README.md b/maple-macro/README.md new file mode 100644 index 0000000..eb29e03 --- /dev/null +++ b/maple-macro/README.md @@ -0,0 +1 @@ +# maple-macro diff --git a/maple-macro/src/attribute.rs b/maple-macro/src/attribute.rs new file mode 100644 index 0000000..767176f --- /dev/null +++ b/maple-macro/src/attribute.rs @@ -0,0 +1,46 @@ +use syn::{Expr, ExprLit, Ident}; +use syn::synom::Synom; +use quote::ToTokens; + +#[derive(Debug)] +crate struct Attribute { + pub name: Ident, + pub value: AttributeValue +} + +impl Synom for Attribute { + named!(parse -> Self, do_parse!( + name: syn!(Ident) >> + punct!(=) >> + value: syn!(AttributeValue) >> + + (Attribute { name, value }) + )); +} + +#[derive(Debug)] +crate enum AttributeValue { + Expr(Expr), + Literal(ExprLit) +} + +impl Synom for AttributeValue { + named!(parse -> Self, do_parse!( + value: alt!( + syn!(ExprLit) => { |d| AttributeValue::Literal(d) } + | + braces!(syn!(Expr)) => { |d| AttributeValue::Expr(d.1) } + ) >> + + (value) + )); +} + +impl ToTokens for AttributeValue { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + match self { + AttributeValue::Literal(s) => s.to_tokens(tokens), + AttributeValue::Expr(e) => e.to_tokens(tokens) + }; + } +} \ No newline at end of file diff --git a/maple-macro/src/lib.rs b/maple-macro/src/lib.rs new file mode 100644 index 0000000..c9d15fd --- /dev/null +++ b/maple-macro/src/lib.rs @@ -0,0 +1,134 @@ + +mod attribute; +mod node; +mod tag; + +#[macro_use] +extern crate syn; + +#[macro_use] +extern crate quote; + +use proc_macro::{TokenStream}; +use syn::synom::Synom; +use syn::{TypePath, Ident, Expr}; +use syn::buffer::{TokenBuffer}; +// use syn::parsers::{named, do_parse, punct, syn, call, alt, braces, many0}; +use quote::*; + +use self::node::Node; +use self::attribute::{Attribute, AttributeValue}; + +fn visit_attribute(ident: &Ident, attr: &Attribute, _ctx: &TypePath) -> impl ToTokens { + let attr_name = &attr.name; + let attr_value = &attr.value; + + match attr_value { + AttributeValue::Expr(Expr::Closure(closure)) => quote! { + #ident.#attr_name = Callback1::new({#closure}).my_into(); + }, + _ => quote! { #ident.#attr_name = {#attr_value}.my_into(); } + } +} + +fn visit_node(parent: &str, idx: &mut u32, node: &Node, stream: &mut proc_macro2::TokenStream, thread: &mut proc_macro2::TokenStream) { + let ii_name = &("p".to_string() + &idx.to_string()); + let ii = Ident::new(ii_name, proc_macro2::Span::call_site()); + let ctx_ii = Ident::new(&("ctx_".to_string() + ii_name), proc_macro2::Span::call_site()); + let comp_ii = Ident::new(&("comp_".to_string() + ii_name), proc_macro2::Span::call_site()); + let pp = Ident::new(&("ctx_".to_string() + parent), proc_macro2::Span::call_site()); + + match node { + Node::Text(txt) => { + let toks = quote!{ + let #ii = Text::create(::Props::new(#txt)); + }; + toks.to_tokens(stream); + }, + + Node::Expr(exr) => { + let toks = quote!{ + let #ii = Text::create(::Props::new(format!("{:?}", #exr))); + }; + toks.to_tokens(stream); + }, + + Node::Embed(exr) => { + let toks = quote!{ + let #ii = #exr; + }; + toks.to_tokens(stream); + }, + + Node::Tag(tag) => { + let iip = Ident::new(&(ii_name.to_string() + "_props"), proc_macro2::Span::call_site()); + let path = &tag.path; + + #[allow(unused_mut)] + let mut children = Vec::new(); + + let attributes = tag.attributes + .iter() + .map(|attr| visit_attribute(&iip, &attr, &path)); + + let toks = quote!{ + #[allow(unused_mut)] + let mut #iip = <#path as Component>::Props::default(); + #(#attributes;)* + let mut #comp_ii = #path::create(#iip); + #[allow(unused_variables)] + let #ctx_ii = #comp_ii.receive_context(#pp.clone()); + }; + + toks.to_tokens(stream); + + for child in tag.body.iter() { + *idx += 1; + + let ch_name = "p".to_string() + &idx.to_string(); + let ch_ii = Ident::new(&ch_name, proc_macro2::Span::call_site()); + + children.push(ch_ii); + + visit_node(ii_name, idx, child, stream, thread); + } + + let toks = if children.len() > 0 { + quote!{ + let #ii = #comp_ii.build(Some(children![#(#children),*])); + } + } else { + quote!{ + let #ii = #comp_ii.build::>(None); + } + }; + + toks.to_tokens(thread); + } + }; +} + +#[proc_macro] +pub fn view(input: TokenStream) -> TokenStream { + let buffer = TokenBuffer::new(input); + let cursor = buffer.begin(); + let res = Node::parse(cursor); + + let mut output = proc_macro2::TokenStream::new(); + let mut thread = proc_macro2::TokenStream::new(); + let mut idx = 0; + + match res { + Ok((root, _)) => visit_node("default", &mut idx, &root, &mut output, &mut thread), + Err(err) => panic!("Error: {:?}", err) + }; + + let toks = quote! {{ + let ctx_default = DefaultContext; + #output + #thread + p0 + }}; + + toks.into() +} \ No newline at end of file diff --git a/maple-macro/src/node.rs b/maple-macro/src/node.rs new file mode 100644 index 0000000..b064911 --- /dev/null +++ b/maple-macro/src/node.rs @@ -0,0 +1,34 @@ +use syn::synom::Synom; +use syn::{LitStr, Expr}; +use crate::tag::Tag; + + +#[derive(Debug)] +crate enum Node { + Tag(Tag), + Text(LitStr), + Expr(Expr), + Embed(Expr) +} + +impl Synom for Node { + named!(parse -> Self, do_parse!( + value: alt!( + syn!(Tag) => { |tag| Node::Tag(tag) } + | + braces!( + do_parse!( + punct!(.) >> + punct!(.) >> + punct!(.) >> + tt: syn!(Expr) >> (tt) + )) => { |d| Node::Embed(d.1) } + | + braces!(syn!(Expr)) => { |d| Node::Expr(d.1) } + | + syn!(LitStr) => { |_str| Node::Text(_str) } + ) >> + + (value) + )); +} \ No newline at end of file diff --git a/maple-macro/src/tag.rs b/maple-macro/src/tag.rs new file mode 100644 index 0000000..46bfca5 --- /dev/null +++ b/maple-macro/src/tag.rs @@ -0,0 +1,58 @@ +use crate::attribute::Attribute; +use crate::node::Node; +use syn::TypePath; +use syn::synom::Synom; + +#[derive(Debug)] +crate struct Tag { + pub path: TypePath, + pub attributes: Vec, + pub body: Vec +} + +impl Tag { + pub fn new(open: TypePath, close: Option, attrs: Vec, body: Vec) -> Result { + if let Some(close_ref) = &close { + if &open != close_ref { + return Err(format!("Open({}) and closing({}) tags are not matching!", quote!{#open}, quote!{#close_ref})) + } + } + + Ok(Tag { + path: open, + attributes: attrs, + body + }) + } +} + +impl Synom for Tag { + named!(parse -> Self, do_parse!( + t: alt!( + do_parse!( + punct!(<) >> + open: syn!(TypePath) >> + attrs: many0!(syn!(Attribute)) >> + punct!(>) >> + body: many0!(syn!(Node)) >> + punct!(<) >> punct!(/) >> + close: syn!(TypePath) >> + punct!(>) >> + + (match Tag::new(open, Some(close), attrs, body) { + Ok(v) => v, + Err(e) => panic!("{:?}", e) + }) + ) => {|x|x} + | + do_parse!( + punct!(<) >> + open: syn!(TypePath) >> + attrs: many0!(syn!(Attribute)) >> + punct!(/) >> + punct!(>) >> + (Tag::new(open, None, attrs, vec![]).unwrap()) + ) => {|x|x} + ) >> (t) + )); +} \ No newline at end of file diff --git a/maple-stdui/.gitignore b/maple-stdui/.gitignore new file mode 100644 index 0000000..088ba6b --- /dev/null +++ b/maple-stdui/.gitignore @@ -0,0 +1,10 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk diff --git a/maple-stdui/Cargo.toml b/maple-stdui/Cargo.toml new file mode 100644 index 0000000..4e5bb25 --- /dev/null +++ b/maple-stdui/Cargo.toml @@ -0,0 +1,11 @@ +cargo-features = ["edition"] + +[package] +name = "maple-stdui" +version = "0.1.0" +authors = ["Andrey Tkachenko "] +edition = "2018" + +[dependencies] +maple-macro = {path = "../maple-macro"} +maple-core = {path = "../maple-core"} diff --git a/maple-stdui/src/lib.rs b/maple-stdui/src/lib.rs new file mode 100644 index 0000000..efd3359 --- /dev/null +++ b/maple-stdui/src/lib.rs @@ -0,0 +1,9 @@ +pub mod panel; +pub mod tabs; +pub mod text; + +pub mod prelude { + pub use super::panel::*; + pub use super::tabs; + pub use super::text::*; +} \ No newline at end of file diff --git a/maple-stdui/src/panel.rs b/maple-stdui/src/panel.rs new file mode 100644 index 0000000..338e875 --- /dev/null +++ b/maple-stdui/src/panel.rs @@ -0,0 +1,16 @@ +use maple_core::prelude::*; + +pub struct Panel { + props: PanelProps +} + +#[derive(Default)] +pub struct PanelProps {} + +impl Component for Panel { + type Props = PanelProps; + + fn create(props: PanelProps) -> Self { + Panel { props } + } +} \ No newline at end of file diff --git a/maple-stdui/src/tabs.rs b/maple-stdui/src/tabs.rs new file mode 100644 index 0000000..8343325 --- /dev/null +++ b/maple-stdui/src/tabs.rs @@ -0,0 +1,168 @@ +use std::ops::Deref; +use maple_core::prelude::*; +use std::rc::Rc; +use std::cell::RefCell; + +#[derive(Clone)] +pub struct TabsContext { + inner: T, + tabs: Rc>>> +} + +impl TabsContext { + pub fn add_tab(&self, tab_title: Option<&'static str>) { + self.tabs.borrow_mut().push(tab_title); + } + + pub fn get_tabs(&self) -> Vec> { + return self.tabs.borrow().clone() + } +} + +impl Context for TabsContext { + type Context = T; + + fn unwrap(self) -> Self::Context { + self.inner + } + + fn wrap(ctx: Self::Context) -> Self { + Self { + inner: ctx, + tabs: Default::default() + } + } +} + +#[derive(Clone)] +pub struct TabsBodyContext { inner: TabsContext } + +impl Context for TabsBodyContext { + type Context = TabsContext; + + fn unwrap(self) -> Self::Context { + self.inner + } + + fn wrap(ctx: Self::Context) -> Self { + Self { + inner: ctx + } + } +} + +impl Deref for TabsBodyContext { + type Target = TabsContext; + + fn deref(&self) -> &Self::Target { + return &self.inner; + } +} + +pub struct Tabs { + props: TabsProps +} + +pub enum TabsEvent { + TabChange(usize) +} + +#[derive(Default)] +pub struct TabsProps { + pub on_tab_change: Option>> +} + +impl Component for Tabs { + type Props = TabsProps; + type Msg = TabsEvent; + + fn create(props: TabsProps) -> Self { + Tabs { props } + } + + fn update(&mut self, msg: Self::Msg) -> bool { + match msg { + TabsEvent::TabChange(idx) => { + if let Some(cb) = &self.props.on_tab_change { + cb.call(idx); + } + } + }; + + false + } +} + +pub struct Header { + props: HeaderProps, + tabs_ctx: Option> +} + +pub enum HeaderEvent { + Click(usize) +} + +impl ComponentMsg for HeaderEvent { + type Component = Header; +} + +#[derive(Default)] +pub struct HeaderProps { + pub on_click: Option>> +} + +impl Component for Header { + type Props = HeaderProps; + type Msg = HeaderEvent; + + fn create(props: HeaderProps) -> Self { + Header { + props, + tabs_ctx: Default::default() + } + } + + fn update(&mut self, msg: Self::Msg) -> bool { + match msg { + HeaderEvent::Click(idx) => { + if let Some(cb) = &self.props.on_click { + cb.call(idx); + } + } + }; + + false + } +} + +pub struct Body { + props: BodyProps +} + +#[derive(Default)] +pub struct BodyProps {} + +impl Component for Body { + type Props = BodyProps; + + fn create(props: BodyProps) -> Self { + Body { props } + } +} + +pub struct Tab { + props: TabProps +} + +#[derive(Default)] +pub struct TabProps { + pub title: Option<&'static str> +} + +impl Component for Tab { + type Props = TabProps; + + fn create(props: TabProps) -> Self { + Tab { props } + } +} \ No newline at end of file diff --git a/maple-stdui/src/text.rs b/maple-stdui/src/text.rs new file mode 100644 index 0000000..73cdbfa --- /dev/null +++ b/maple-stdui/src/text.rs @@ -0,0 +1,27 @@ +use std::borrow::Cow; +use maple_core::prelude::Component; + +pub struct Text { + pub props: TextProps +} + +#[derive(Default)] +pub struct TextProps { + pub text: Option> +} + +impl TextProps { + pub fn new>>(txt: T) -> Self { + TextProps { + text: Some(txt.into()) + } + } +} + +impl Component for Text { + type Props = TextProps; + + fn create(props: TextProps) -> Self { + Self { props } + } +} \ No newline at end of file diff --git a/maple-stdweb-ui/.gitignore b/maple-stdweb-ui/.gitignore new file mode 100644 index 0000000..6936990 --- /dev/null +++ b/maple-stdweb-ui/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/maple-stdweb-ui/Cargo.toml b/maple-stdweb-ui/Cargo.toml new file mode 100644 index 0000000..6a9d127 --- /dev/null +++ b/maple-stdweb-ui/Cargo.toml @@ -0,0 +1,14 @@ +cargo-features = ["edition"] + +[package] +name = "maple-stdweb-ui" +version = "0.1.0" +authors = ["Andrey Tkachenko "] +edition = "2018" + +[dependencies] +maple-core = {path = "../maple-core"} +maple-stdweb = {path = "../maple-stdweb"} +maple-stdui = {path = "../maple-stdui"} +maple-macro = {path = "../maple-macro"} +maple-stdweb-ui = {path = "../maple-stdweb-ui"} \ No newline at end of file diff --git a/maple-stdweb-ui/src/lib.rs b/maple-stdweb-ui/src/lib.rs new file mode 100644 index 0000000..44d6864 --- /dev/null +++ b/maple-stdweb-ui/src/lib.rs @@ -0,0 +1,9 @@ +#![feature(proc_macro_non_items, existential_type)] + +pub mod panel; +pub mod tabs; + +pub use self::panel::*; +pub use self::tabs::*; + +pub use maple_stdweb_ui::HtmlEngine; \ No newline at end of file diff --git a/maple-stdweb-ui/src/panel.rs b/maple-stdweb-ui/src/panel.rs new file mode 100644 index 0000000..19554a9 --- /dev/null +++ b/maple-stdweb-ui/src/panel.rs @@ -0,0 +1,30 @@ +use maple_core::prelude::*; +use super::HtmlEngine; +use maple_macro::view; +use maple_stdweb::*; +use maple_stdui::prelude::Panel; + +impl View for Panel { + type InputContext = DefaultContext; + type OutputContext = DefaultContext; + + fn receive_context(&mut self, ctx: Self::InputContext) -> Self::OutputContext { + ctx + } +} + +impl ViewX for Panel { + type Engine = HtmlEngine; + type ChildrenEngine = HtmlEngine; + existential type Renderable: Renderable; + + fn build(self, children: Option) -> Self::Renderable + where C: Renderable + 'static + { + view! { +
+ { ...children } +
+ } + } +} \ No newline at end of file diff --git a/maple-stdweb-ui/src/tabs.rs b/maple-stdweb-ui/src/tabs.rs new file mode 100644 index 0000000..cbbd3f7 --- /dev/null +++ b/maple-stdweb-ui/src/tabs.rs @@ -0,0 +1,111 @@ +use maple_core::prelude::*; +use maple_macro::view; +use maple_stdweb::*; +use maple_stdui::prelude::tabs::*; +use super::HtmlEngine; +use std::ops::Deref; +use std::rc::Rc; +use std::cell::RefCell; + + + +impl View for Tabs { + type InputContext = DefaultContext; + type OutputContext = TabsContext; + + fn receive_context(&mut self, ctx: Self::InputContext) -> Self::OutputContext { + TabsContext::wrap(ctx) + } +} + +impl Tabs { + pub fn build(self, children: Option) -> impl Renderable + where C: Renderable + 'static + { + view! { +
+ { ... children } +
+ } + } +} + +impl View for Header { + type InputContext = TabsContext; + type OutputContext = DefaultContext; + + fn receive_context(&mut self, ctx: Self::InputContext) -> Self::OutputContext { + self.tabs_ctx = Some(ctx.clone()); + + ctx.unwrap() + } +} + +impl Header { + pub fn build(self, _children: Option) -> impl Renderable + where C: Renderable + 'static + { + let tabs = self.tabs_ctx.map(|ctx| + ctx.get_tabs() + .iter() + .enumerate() + .map(|(i, tab)| view! { + + {match tab { + Some(val) => val, + None => "" + }} + + }) + .collect::>()); + + view! { +
+ { ...tabs } +
+ } + } +} + +impl View for Body { + type InputContext = TabsContext; + type OutputContext = TabsBodyContext; + + fn receive_context(&mut self, ctx: Self::InputContext) -> Self::OutputContext { + TabsBodyContext::wrap(ctx) + } +} + +impl Body { + pub fn build(self, children: Option) -> impl Renderable + where C: Renderable + 'static + { + view! { +
+ { ... children } +
+ } + } +} + +impl View for Tab { + type InputContext = TabsBodyContext; + type OutputContext = DefaultContext; + + fn receive_context(&mut self, ctx: Self::InputContext) -> Self::OutputContext { + ctx.add_tab(self.props.title); + ctx.unwrap().unwrap() + } +} + +impl Tab { + pub fn build(self, children: Option) -> impl Renderable + where C: Renderable + 'static + { + view! { +
+ { ... children } +
+ } + } +} \ No newline at end of file diff --git a/maple-stdweb/.gitignore b/maple-stdweb/.gitignore new file mode 100644 index 0000000..6936990 --- /dev/null +++ b/maple-stdweb/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/maple-stdweb/Cargo.toml b/maple-stdweb/Cargo.toml new file mode 100644 index 0000000..0820918 --- /dev/null +++ b/maple-stdweb/Cargo.toml @@ -0,0 +1,10 @@ +cargo-features = ["edition"] + +[package] +name = "maple-stdweb" +version = "0.1.0" +authors = ["Andrey Tkachenko "] +edition = "2018" + +[dependencies] +maple-core = {path = '../maple-core'} diff --git a/maple-stdweb/src/button.rs b/maple-stdweb/src/button.rs new file mode 100644 index 0000000..2cbf59a --- /dev/null +++ b/maple-stdweb/src/button.rs @@ -0,0 +1,30 @@ +use maple_core::prelude::Component; + +pub enum ButtonEvents { + Click +} + +pub struct Button { + props: ButtonProps +} + +#[derive(Default)] +pub struct ButtonProps { + pub id: Option<&'static str>, + pub name: Option<&'static str>, + pub class: Option<&'static str>, + pub click: Option ButtonEvents>> +} + +impl Component for Button { + type Props = ButtonProps; + type Msg = ButtonEvents; + + fn create(props: ButtonProps) -> Self { + Button { props } + } + + fn update(&mut self, msg: Self::Msg) -> bool { + false + } +} \ No newline at end of file diff --git a/maple-stdweb/src/canvas.rs b/maple-stdweb/src/canvas.rs new file mode 100644 index 0000000..ec42943 --- /dev/null +++ b/maple-stdweb/src/canvas.rs @@ -0,0 +1,30 @@ +use maple_core::prelude::Component; + +pub enum CanvasEvents { + Click +} + +pub struct Canvas { + pub props: CanvasProps +} + +#[derive(Default)] +pub struct CanvasProps { + pub id: Option<&'static str>, + pub name: Option<&'static str>, + pub class: Option<&'static str>, + pub click: Option CanvasEvents>> +} + +impl Component for Canvas { + type Props = CanvasProps; + type Msg = CanvasEvents; + + fn create(props: CanvasProps) -> Self { + Canvas { props } + } + + fn update(&mut self, msg: Self::Msg) -> bool { + true + } +} \ No newline at end of file diff --git a/maple-stdweb/src/canvas_comp/circle.rs b/maple-stdweb/src/canvas_comp/circle.rs new file mode 100644 index 0000000..38b056c --- /dev/null +++ b/maple-stdweb/src/canvas_comp/circle.rs @@ -0,0 +1,23 @@ +use maple_core::prelude::Component; + +pub struct Circle { + pub props: CircleProps +} + +#[derive(Default)] +pub struct CircleProps { + pub id: Option<&'static str>, + pub fill: Option<&'static str>, + pub stroke: Option<&'static str>, + pub cx: Option, + pub cy: Option, + pub r: Option +} + +impl Component for Circle { + type Props = CircleProps; + + fn create(props: CircleProps) -> Self { + Circle { props } + } +} \ No newline at end of file diff --git a/maple-stdweb/src/canvas_comp/mod.rs b/maple-stdweb/src/canvas_comp/mod.rs new file mode 100644 index 0000000..77ba92e --- /dev/null +++ b/maple-stdweb/src/canvas_comp/mod.rs @@ -0,0 +1,5 @@ +mod circle; +mod rect; + +pub use self::circle::{Circle, CircleProps}; +pub use self::rect::{Rect, RectProps}; \ No newline at end of file diff --git a/maple-stdweb/src/canvas_comp/rect.rs b/maple-stdweb/src/canvas_comp/rect.rs new file mode 100644 index 0000000..3665fb3 --- /dev/null +++ b/maple-stdweb/src/canvas_comp/rect.rs @@ -0,0 +1,25 @@ +use maple_core::prelude::Component; + +pub struct Rect { + pub props: RectProps +} + +#[derive(Default)] +pub struct RectProps { + pub id: Option<&'static str>, + pub name: Option<&'static str>, + pub fill: Option<&'static str>, + pub stroke: Option<&'static str>, + pub x1: Option, + pub y1: Option, + pub x2: Option, + pub y2: Option +} + +impl Component for Rect { + type Props = RectProps; + + fn create(props: RectProps) -> Self { + Rect { props } + } +} \ No newline at end of file diff --git a/maple-stdweb/src/div.rs b/maple-stdweb/src/div.rs new file mode 100644 index 0000000..8a450f1 --- /dev/null +++ b/maple-stdweb/src/div.rs @@ -0,0 +1,32 @@ +use maple_core::prelude::*; + +pub enum DivEvents { + Click +} + +pub struct Div { + pub props: DivProps +} + +#[derive(Default)] +pub struct DivProps { + pub id: Option<&'static str>, + pub name: Option<&'static str>, + pub class: Option<&'static str>, + pub click: Option DivEvents>> +} + +impl Component for Div { + type Props = DivProps; + type Msg = DivEvents; + + fn create(props: DivProps) -> Self { + Div { + props + } + } + + fn update(&mut self, msg: Self::Msg) -> bool { + false + } +} \ No newline at end of file diff --git a/maple-stdweb/src/lib.rs b/maple-stdweb/src/lib.rs new file mode 100644 index 0000000..619ccc1 --- /dev/null +++ b/maple-stdweb/src/lib.rs @@ -0,0 +1,39 @@ +mod div; +mod span; +mod button; +mod canvas; +mod canvas_comp; + +pub use self::canvas_comp::*; +pub use self::div::{Div, DivEvents, DivProps}; +pub use self::span::{Span, SpanProps}; +pub use self::button::{Button, ButtonEvents, ButtonProps}; +pub use self::canvas::{Canvas, CanvasEvents, CanvasProps}; + +use std::borrow::Cow; +use maple_core::prelude::Component; + +pub struct Text { + pub props: TextProps +} + +#[derive(Default)] +pub struct TextProps { + pub text: Option> +} + +impl TextProps { + pub fn new>>(txt: T) -> Self { + TextProps { + text: Some(txt.into()) + } + } +} + +impl Component for Text { + type Props = TextProps; + + fn create(props: TextProps) -> Self { + Self { props } + } +} \ No newline at end of file diff --git a/maple-stdweb/src/span.rs b/maple-stdweb/src/span.rs new file mode 100644 index 0000000..b2de7cb --- /dev/null +++ b/maple-stdweb/src/span.rs @@ -0,0 +1,38 @@ +use maple_core::prelude::*; + +pub enum SpanEvents { + Click +} + +pub struct Span { + props: SpanProps +} + +#[derive(Default)] +pub struct SpanProps { + pub id: Option<&'static str>, + pub name: Option<&'static str>, + pub class: Option<&'static str>, + pub click: Option>> +} + +impl Component for Span { + type Props = SpanProps; + type Msg = SpanEvents; + + fn create(props: SpanProps) -> Self { + Span { props } + } + + fn update(&mut self, msg: Self::Msg) -> bool { + match msg { + SpanEvents::Click => { + if let Some(cb) = &self.props.click { + cb.call(()); + } + } + }; + + false + } +} \ No newline at end of file diff --git a/maple/Cargo.toml b/maple/Cargo.toml new file mode 100644 index 0000000..d044a99 --- /dev/null +++ b/maple/Cargo.toml @@ -0,0 +1,13 @@ +cargo-features = ["edition"] + +[package] +name = "maple" +version = "0.1.0" +authors = ["Andrey Tkachenko "] +edition = "2018" + +[dependencies] +maple-core = {path = "../maple-core"} +maple-macro = {path = "../maple-macro"} +maple-stdui = {path = "../maple-stdui"} +maple-stdweb = {path = "../maple-stdweb"} \ No newline at end of file diff --git a/maple/src/lib.rs b/maple/src/lib.rs new file mode 100644 index 0000000..072c869 --- /dev/null +++ b/maple/src/lib.rs @@ -0,0 +1,9 @@ +#[macro_use] +extern crate maple_core; + +pub mod prelude { + pub use maple_core::prelude::*; + pub use maple_macro::view; + pub use maple_stdweb::*; + pub use maple_stdui::*; +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index dcd46fc..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,5 +0,0 @@ -#![feature(associated_type_defaults, never_type, unsize)] - -mod convert; -use std::marker::PhantomData; -pub use convert::*; \ No newline at end of file diff --git a/src/node.rs b/src/node.rs deleted file mode 100644 index d587a4c..0000000 --- a/src/node.rs +++ /dev/null @@ -1,43 +0,0 @@ -/// -/// -/// -/// -pub struct Node - where - E: Engine, - CHE: Engine, - I: RenderImplementation, - CH: Renderable -{ - item: I, - children: CH -} - -impl Node - where - E: Engine, - CHE: Engine, - I: RenderImplementation, - CH: Renderable -{ - pub fn new(item: I, children: CH) -> Self { - Self { - item, - children - } - } -} - -impl Renderable for Node - where - E: Engine, - CHE: Engine, - I: RenderImplementation, - CH: Renderable -{ - type Engine = E; - - fn render(&self, eng: &Self::Engine) { - self.item.render_impl(eng, &self.children) - } -} diff --git a/src/renderable.rs b/src/renderable.rs deleted file mode 100644 index 94df7c8..0000000 --- a/src/renderable.rs +++ /dev/null @@ -1,101 +0,0 @@ -/// defines implementation of how the children elements binds to the component -/// in dom backend it actually attaches/detaches elements to each other -/// -/// -pub trait RenderImplementation { - type Engine: Engine; - type ChildrenEngine: Engine = Self::Engine; - - fn render_impl(&self, eng: &Self::Engine, children: &C) - where C: Renderable; -} - -/// Trait Renderable -/// defines implementation of how the component should be rendered itself -/// -/// -/// -pub trait Renderable { - type Engine: Engine; - - fn render(&self, eng: &Self::Engine); -} - -/// -/// for nodes without children -/// -/// -pub struct Stub { - _e: PhantomData -} - -impl Renderable for Stub { - type Engine = E; - - fn render(&self, _eng: &Self::Engine) {} -} - -impl Stub { - pub fn new() -> Self { - Self { - _e: PhantomData - } - } -} - -impl Renderable for T - where E: Engine, - T: RenderImplementation -{ - type Engine = E; - - fn render(&self, eng: &Self::Engine) { - self.render_impl(eng, &Stub::new()) - } -} - -impl Renderable for Box> { - type Engine = E; - - fn render(&self, eng: &Self::Engine) { - (**self).render(eng); - } -} - -impl <'a, E> Renderable for Vec<&'a (dyn Renderable + 'a)> - where E: Engine + 'a -{ - type Engine = E; - - fn render(&self, eng: &Self::Engine) { - for child in self.iter() { - child.render(eng); - } - } -} - -impl Renderable for Vec - where E: Engine, - T: Renderable -{ - type Engine = E; - - fn render(&self, eng: &Self::Engine) { - for child in self.iter() { - child.render(eng); - } - } -} - -impl Renderable for Option - where E: Engine, - T: Renderable -{ - type Engine = E; - - fn render(&self, eng: &Self::Engine) { - if let Some(inner) = self { - inner.render(eng); - } - } -} \ No newline at end of file