Initial commit
This commit is contained in:
parent
3d9dd24614
commit
874832e82b
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
/target
|
||||||
|
**/*.rs.bk
|
||||||
|
Cargo.lock
|
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "dominator"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Pauan <pcxunlimited@gmail.com>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
stdweb = { path = "../stdweb" }
|
||||||
|
stdweb-derive = { path = "../stdweb/stdweb-derive" }
|
||||||
|
futures = "0.1.18"
|
||||||
|
lazy_static = "1.0.0"
|
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "todomvc"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Pauan <pcxunlimited@gmail.com>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
stdweb = { path = "../../../stdweb" }
|
||||||
|
dominator = { path = "../.." }
|
|
@ -0,0 +1,94 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate stdweb;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate dominator;
|
||||||
|
|
||||||
|
use std::rc::Rc;
|
||||||
|
use stdweb::web::{document, HtmlElement};
|
||||||
|
use stdweb::web::event::ClickEvent;
|
||||||
|
use stdweb::web::IParentNode;
|
||||||
|
|
||||||
|
use dominator::{Dom, signal};
|
||||||
|
use dominator::signal::Signal;
|
||||||
|
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
stylesheet!("div", {
|
||||||
|
style("border", "5px solid black");
|
||||||
|
});
|
||||||
|
|
||||||
|
let foobar = class! {
|
||||||
|
style("border-right", "10px solid purple");
|
||||||
|
};
|
||||||
|
|
||||||
|
/*let media_query = stylesheet!(format!("@media (max-width: 500px) .{}", foobar), {
|
||||||
|
style("border-left", "10px solid teal");
|
||||||
|
});*/
|
||||||
|
|
||||||
|
let mut width = 100;
|
||||||
|
|
||||||
|
let (sender, receiver) = signal::unsync::mutable(width);
|
||||||
|
|
||||||
|
html!("div", {
|
||||||
|
style("border", "10px solid blue");
|
||||||
|
children(&mut [
|
||||||
|
html!("div", {
|
||||||
|
style("width", receiver.map(|x| Some(format!("{}px", x))));
|
||||||
|
style("height", "50px");
|
||||||
|
style("background-color", "green");
|
||||||
|
event(move |event: ClickEvent| {
|
||||||
|
width += 100;
|
||||||
|
console!(log, &event);
|
||||||
|
sender.set(width).unwrap();
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
html!("div", {
|
||||||
|
style("width", "50px");
|
||||||
|
style("height", "50px");
|
||||||
|
style("background-color", "red");
|
||||||
|
children(&mut [
|
||||||
|
html!("div", {
|
||||||
|
style("width", "10px");
|
||||||
|
style("height", "10px");
|
||||||
|
style("background-color", "orange");
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}),
|
||||||
|
|
||||||
|
html!("div", {
|
||||||
|
style("width", "50px");
|
||||||
|
style("height", "50px");
|
||||||
|
style("background-color", "red");
|
||||||
|
class(&foobar, true);
|
||||||
|
children(&mut [
|
||||||
|
html!("div", {
|
||||||
|
style("width", "10px");
|
||||||
|
style("height", "10px");
|
||||||
|
style("background-color", "orange");
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}),
|
||||||
|
|
||||||
|
Dom::with_state(Rc::new(vec![1, 2, 3]), |a| {
|
||||||
|
html!("div", {
|
||||||
|
style("width", "100px");
|
||||||
|
style("height", "100px");
|
||||||
|
style("background-color", "orange");
|
||||||
|
class("foo", true);
|
||||||
|
class("bar", false);
|
||||||
|
event(clone!({ a } move |event: ClickEvent| {
|
||||||
|
console!(log, &*a, &event);
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
|
||||||
|
html!("input", {
|
||||||
|
focused(true);
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
}).insert_into(
|
||||||
|
&document().query_selector("body").unwrap().unwrap()
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,869 @@
|
||||||
|
use std;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use stdweb::{Reference, Value, ReferenceType};
|
||||||
|
use stdweb::unstable::{TryFrom, TryInto};
|
||||||
|
use stdweb::web::{IEventTarget, INode, IElement, IHtmlElement, Node, TextNode};
|
||||||
|
use stdweb::web::event::ConcreteEvent;
|
||||||
|
use signal::{Signal, DropHandle};
|
||||||
|
|
||||||
|
|
||||||
|
// TODO this should be in stdweb
|
||||||
|
pub trait IStyle: ReferenceType {
|
||||||
|
// TODO check that the style *actually* was changed
|
||||||
|
// TODO handle browser prefixes
|
||||||
|
#[inline]
|
||||||
|
fn set_style(&self, name: &str, value: &str, important: bool) {
|
||||||
|
let important = if important { "important" } else { "" };
|
||||||
|
|
||||||
|
js! { @(no_return)
|
||||||
|
@{self.as_ref()}.style.setProperty(@{name}, @{value}, @{important});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: IHtmlElement> IStyle for A {}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO this should be in stdweb
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)]
|
||||||
|
#[reference(instance_of = "CSSStyleRule")]
|
||||||
|
pub struct CssStyleRule(Reference);
|
||||||
|
|
||||||
|
impl IStyle for CssStyleRule {}
|
||||||
|
|
||||||
|
|
||||||
|
pub mod traits {
|
||||||
|
use super::IStyle;
|
||||||
|
use super::internal::Callbacks;
|
||||||
|
use stdweb::Reference;
|
||||||
|
use stdweb::web::{INode, IElement, IHtmlElement, TextNode};
|
||||||
|
|
||||||
|
pub trait DomBuilder {
|
||||||
|
type Value;
|
||||||
|
|
||||||
|
fn value(&self) -> &Self::Value;
|
||||||
|
|
||||||
|
fn callbacks(&mut self) -> &mut Callbacks;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DomText {
|
||||||
|
fn set_text<A>(self, &mut A)
|
||||||
|
// TODO use an interface rather than TextNode
|
||||||
|
where A: DomBuilder<Value = TextNode>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DomProperty {
|
||||||
|
fn set_property<A, B>(self, &mut B, &str)
|
||||||
|
// TODO it would be nice to be able to remove this Clone constraint somehow
|
||||||
|
where A: AsRef<Reference> + Clone + 'static,
|
||||||
|
B: DomBuilder<Value = A>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DomAttribute {
|
||||||
|
fn set_attribute<A, B>(self, &mut B, &str, Option<&str>)
|
||||||
|
// TODO it would be nice to be able to remove this Clone constraint somehow
|
||||||
|
where A: IElement + Clone + 'static,
|
||||||
|
B: DomBuilder<Value = A>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DomClass {
|
||||||
|
fn toggle_class<A, B>(self, &mut B, &str)
|
||||||
|
// TODO it would be nice to be able to remove this Clone constraint somehow
|
||||||
|
where A: IElement + Clone + 'static,
|
||||||
|
B: DomBuilder<Value = A>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DomStyle {
|
||||||
|
fn set_style<A, B>(self, &mut B, &str, bool)
|
||||||
|
// TODO it would be nice to be able to remove this Clone constraint somehow
|
||||||
|
where A: IStyle + Clone + 'static,
|
||||||
|
B: DomBuilder<Value = A>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DomFocused {
|
||||||
|
fn set_focused<A, B>(self, &mut B)
|
||||||
|
// TODO it would be nice to be able to remove this Clone constraint somehow
|
||||||
|
where A: IHtmlElement + Clone + 'static,
|
||||||
|
B: DomBuilder<Value = A>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DomChildren {
|
||||||
|
fn insert_children<A, B>(self, &mut B)
|
||||||
|
// TODO it would be nice to be able to remove this Clone constraint somehow
|
||||||
|
where A: INode + Clone + 'static,
|
||||||
|
B: DomBuilder<Value = A>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub mod dom_operations {
|
||||||
|
use std;
|
||||||
|
use stdweb::unstable::{TryFrom, TryInto};
|
||||||
|
use stdweb::{Value, Reference};
|
||||||
|
use stdweb::web::{TextNode, IHtmlElement, IElement};
|
||||||
|
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn create_element_ns<A: IElement>(name: &str, namespace: &str) -> A
|
||||||
|
where <A as TryFrom<Value>>::Error: std::fmt::Debug {
|
||||||
|
js!( return document.createElementNS(@{namespace}, @{name}); ).try_into().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO this should be in stdweb
|
||||||
|
#[inline]
|
||||||
|
pub fn set_text(element: &TextNode, value: &str) {
|
||||||
|
js! { @(no_return)
|
||||||
|
// http://jsperf.com/textnode-performance
|
||||||
|
@{element}.data = @{value};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO replace with element.focus() and element.blur()
|
||||||
|
// TODO make element.focus() and element.blur() inline
|
||||||
|
#[inline]
|
||||||
|
pub fn set_focused<A: IHtmlElement>(element: &A, focused: bool) {
|
||||||
|
js! { @(no_return)
|
||||||
|
var element = @{element.as_ref()};
|
||||||
|
|
||||||
|
if (@{focused}) {
|
||||||
|
element.focus();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
element.blur();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn toggle_class<A: IElement>(element: &A, name: &str, toggle: bool) {
|
||||||
|
js! { @(no_return)
|
||||||
|
@{element.as_ref()}.classList.toggle(@{name}, @{toggle});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn _set_attribute_ns<A: IElement>(element: &A, name: &str, value: &str, namespace: &str) {
|
||||||
|
js! { @(no_return)
|
||||||
|
@{element.as_ref()}.setAttributeNS(@{namespace}, @{name}, @{value});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn _set_attribute<A: IElement>(element: &A, name: &str, value: &str) {
|
||||||
|
js! { @(no_return)
|
||||||
|
@{element.as_ref()}.setAttribute(@{name}, @{value});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO check that the attribute *actually* was changed
|
||||||
|
#[inline]
|
||||||
|
pub fn set_attribute<A: IElement>(element: &A, name: &str, value: &str, namespace: Option<&str>) {
|
||||||
|
match namespace {
|
||||||
|
Some(namespace) => _set_attribute_ns(element, name, value, namespace),
|
||||||
|
None => _set_attribute(element, name, value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn _remove_attribute_ns<A: IElement>(element: &A, name: &str, namespace: &str) {
|
||||||
|
js! { @(no_return)
|
||||||
|
@{element.as_ref()}.removeAttributeNS(@{namespace}, @{name});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn _remove_attribute<A: IElement>(element: &A, name: &str) {
|
||||||
|
js! { @(no_return)
|
||||||
|
@{element.as_ref()}.removeAttribute(@{name});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn remove_attribute<A: IElement>(element: &A, name: &str, namespace: Option<&str>) {
|
||||||
|
match namespace {
|
||||||
|
Some(namespace) => _remove_attribute_ns(element, name, namespace),
|
||||||
|
None => _remove_attribute(element, name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO check that the property *actually* was changed ?
|
||||||
|
#[inline]
|
||||||
|
pub fn set_property<A: AsRef<Reference>>(obj: &A, name: &str, value: &str) {
|
||||||
|
js! { @(no_return)
|
||||||
|
@{obj.as_ref()}[@{name}] = @{value};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub mod internal {
|
||||||
|
use std;
|
||||||
|
|
||||||
|
|
||||||
|
// TODO replace this with FnOnce later
|
||||||
|
trait IRemoveCallback {
|
||||||
|
fn call(self: Box<Self>);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F: FnOnce()> IRemoveCallback for F {
|
||||||
|
#[inline]
|
||||||
|
fn call(self: Box<Self>) {
|
||||||
|
self();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO replace this with FnOnce later
|
||||||
|
trait IInsertCallback {
|
||||||
|
fn call(self: Box<Self>, &mut Callbacks);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F: FnOnce(&mut Callbacks)> IInsertCallback for F {
|
||||||
|
#[inline]
|
||||||
|
fn call(self: Box<Self>, callbacks: &mut Callbacks) {
|
||||||
|
self(callbacks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct InsertCallback(Box<IInsertCallback>);
|
||||||
|
|
||||||
|
pub struct RemoveCallback(Box<IRemoveCallback>);
|
||||||
|
|
||||||
|
|
||||||
|
pub struct Callbacks {
|
||||||
|
pub after_insert: Vec<InsertCallback>,
|
||||||
|
pub after_remove: Vec<RemoveCallback>,
|
||||||
|
// TODO figure out a better way
|
||||||
|
pub(crate) trigger_remove: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Callbacks {
|
||||||
|
#[inline]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
after_insert: vec![],
|
||||||
|
after_remove: vec![],
|
||||||
|
trigger_remove: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn after_insert<A: FnOnce(&mut Callbacks) + 'static>(&mut self, callback: A) {
|
||||||
|
self.after_insert.push(InsertCallback(Box::new(callback)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn after_remove<A: FnOnce() + 'static>(&mut self, callback: A) {
|
||||||
|
self.after_remove.push(RemoveCallback(Box::new(callback)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO runtime checks to make sure this isn't called multiple times ?
|
||||||
|
#[inline]
|
||||||
|
pub fn trigger_after_insert(&mut self) {
|
||||||
|
let mut callbacks = Callbacks::new();
|
||||||
|
|
||||||
|
// TODO verify that this is correct
|
||||||
|
// TODO is this the most efficient way to accomplish this ?
|
||||||
|
std::mem::swap(&mut callbacks.after_remove, &mut self.after_remove);
|
||||||
|
|
||||||
|
for f in self.after_insert.drain(..) {
|
||||||
|
f.0.call(&mut callbacks);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.after_insert.shrink_to_fit();
|
||||||
|
|
||||||
|
// TODO figure out a better way of verifying this
|
||||||
|
assert_eq!(callbacks.after_insert.len(), 0);
|
||||||
|
|
||||||
|
// TODO verify that this is correct
|
||||||
|
std::mem::swap(&mut callbacks.after_remove, &mut self.after_remove);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn trigger_after_remove(&mut self) {
|
||||||
|
for f in self.after_remove.drain(..) {
|
||||||
|
f.0.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO is this a good idea?
|
||||||
|
self.after_remove.shrink_to_fit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Callbacks {
|
||||||
|
#[inline]
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if self.trigger_remove {
|
||||||
|
self.trigger_after_remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
use self::traits::{DomBuilder, DomText, DomProperty, DomAttribute, DomClass, DomStyle, DomFocused, DomChildren};
|
||||||
|
use self::internal::Callbacks;
|
||||||
|
|
||||||
|
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS#Valid%20Namespace%20URIs
|
||||||
|
pub const HTML_NAMESPACE: &str = "http://www.w3.org/1999/xhtml";
|
||||||
|
pub const SVG_NAMESPACE: &str = "http://www.w3.org/2000/svg";
|
||||||
|
|
||||||
|
|
||||||
|
pub struct TextBuilder {
|
||||||
|
element: TextNode,
|
||||||
|
callbacks: Callbacks,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DomBuilder for TextBuilder {
|
||||||
|
type Value = TextNode;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn value(&self) -> &Self::Value {
|
||||||
|
&self.element
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn callbacks(&mut self) -> &mut Callbacks {
|
||||||
|
&mut self.callbacks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct Dom {
|
||||||
|
element: Node,
|
||||||
|
callbacks: Callbacks,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dom {
|
||||||
|
#[inline]
|
||||||
|
fn new(element: Node) -> Self {
|
||||||
|
Self {
|
||||||
|
element,
|
||||||
|
callbacks: Callbacks::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn empty() -> Self {
|
||||||
|
// TODO is there a better way of doing this ?
|
||||||
|
Self::new(js!( return document.createComment(""); ).try_into().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn with_state<A, F>(mut state: A, initializer: F) -> Dom
|
||||||
|
where A: 'static,
|
||||||
|
F: FnOnce(&mut A) -> Dom {
|
||||||
|
|
||||||
|
let mut dom = initializer(&mut state);
|
||||||
|
|
||||||
|
dom.callbacks.after_remove(move || drop(state));
|
||||||
|
|
||||||
|
dom
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO return a Handle
|
||||||
|
#[inline]
|
||||||
|
pub fn insert_into<A: INode>(mut self, parent: &A) {
|
||||||
|
parent.append_child(&self.element);
|
||||||
|
|
||||||
|
self.callbacks.trigger_after_insert();
|
||||||
|
|
||||||
|
// This prevents it from calling trigger_after_remove
|
||||||
|
self.callbacks.trigger_remove = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: DomText> From<A> for Dom {
|
||||||
|
#[inline]
|
||||||
|
fn from(dom: A) -> Self {
|
||||||
|
let mut text = TextBuilder {
|
||||||
|
element: js!( return document.createTextNode(""); ).try_into().unwrap(),
|
||||||
|
callbacks: Callbacks::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
dom.set_text(&mut text);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
element: text.element.into(),
|
||||||
|
callbacks: text.callbacks,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a str> for Dom {
|
||||||
|
#[inline]
|
||||||
|
fn from(value: &'a str) -> Self {
|
||||||
|
Self::new(js!( return document.createTextNode(@{value}); ).try_into().unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct HtmlBuilder<A> {
|
||||||
|
element: A,
|
||||||
|
callbacks: Callbacks,
|
||||||
|
// TODO verify this with static types instead ?
|
||||||
|
has_children: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A> DomBuilder for HtmlBuilder<A> {
|
||||||
|
type Value = A;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn value(&self) -> &Self::Value {
|
||||||
|
&self.element
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn callbacks(&mut self) -> &mut Callbacks {
|
||||||
|
&mut self.callbacks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO add in SVG nodes
|
||||||
|
impl<A: IElement> HtmlBuilder<A>
|
||||||
|
where <A as TryFrom<Value>>::Error: std::fmt::Debug {
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn new(name: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
element: dom_operations::create_element_ns(name, HTML_NAMESPACE),
|
||||||
|
callbacks: Callbacks::new(),
|
||||||
|
has_children: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: AsRef<Reference> + Clone + 'static> HtmlBuilder<A> {
|
||||||
|
#[inline]
|
||||||
|
pub fn property<B: DomProperty>(mut self, name: &str, value: B) -> Self {
|
||||||
|
value.set_property(&mut self, name);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: IEventTarget> HtmlBuilder<A> {
|
||||||
|
// TODO maybe inline this ?
|
||||||
|
// TODO replace with element.add_event_listener
|
||||||
|
fn _event<T, F>(&mut self, listener: F)
|
||||||
|
where T: ConcreteEvent,
|
||||||
|
F: FnMut(T) + 'static {
|
||||||
|
|
||||||
|
let element = self.element.as_ref();
|
||||||
|
|
||||||
|
let listener = js!(
|
||||||
|
var listener = @{listener};
|
||||||
|
@{&element}.addEventListener(@{T::EVENT_TYPE}, listener);
|
||||||
|
return listener;
|
||||||
|
);
|
||||||
|
|
||||||
|
let element = element.clone();
|
||||||
|
|
||||||
|
self.callbacks.after_remove(move || {
|
||||||
|
js! { @(no_return)
|
||||||
|
var listener = @{listener};
|
||||||
|
@{element}.removeEventListener(@{T::EVENT_TYPE}, listener);
|
||||||
|
listener.drop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn event<T, F>(mut self, listener: F) -> Self
|
||||||
|
where T: ConcreteEvent,
|
||||||
|
F: FnMut(T) + 'static {
|
||||||
|
self._event(listener);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: INode + Clone + 'static> HtmlBuilder<A> {
|
||||||
|
#[inline]
|
||||||
|
pub fn children<B: DomChildren>(mut self, children: B) -> Self {
|
||||||
|
assert_eq!(self.has_children, false);
|
||||||
|
self.has_children = true;
|
||||||
|
|
||||||
|
children.insert_children(&mut self);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: IElement + Clone + 'static> HtmlBuilder<A> {
|
||||||
|
#[inline]
|
||||||
|
pub fn attribute<B: DomAttribute>(mut self, name: &str, value: B) -> Self {
|
||||||
|
value.set_attribute(&mut self, name, None);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn attribute_namespace<B: DomAttribute>(mut self, name: &str, value: B, namespace: &str) -> Self {
|
||||||
|
value.set_attribute(&mut self, name, Some(namespace));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn class<B: DomClass>(mut self, name: &str, value: B) -> Self {
|
||||||
|
value.toggle_class(&mut self, name);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: IHtmlElement + Clone + 'static> HtmlBuilder<A> {
|
||||||
|
#[inline]
|
||||||
|
pub fn style<B: DomStyle>(mut self, name: &str, value: B) -> Self {
|
||||||
|
value.set_style(&mut self, name, false);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn style_important<B: DomStyle>(mut self, name: &str, value: B) -> Self {
|
||||||
|
value.set_style(&mut self, name, true);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn focused<B: DomFocused>(mut self, value: B) -> Self {
|
||||||
|
value.set_focused(&mut self);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Into<Node>> From<HtmlBuilder<A>> for Dom {
|
||||||
|
#[inline]
|
||||||
|
fn from(dom: HtmlBuilder<A>) -> Self {
|
||||||
|
Self {
|
||||||
|
element: dom.element.into(),
|
||||||
|
callbacks: dom.callbacks,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl<'a> DomProperty for &'a str {
|
||||||
|
#[inline]
|
||||||
|
fn set_property<A: AsRef<Reference>, B: DomBuilder<Value = A>>(self, builder: &mut B, name: &str) {
|
||||||
|
dom_operations::set_property(builder.value(), name, self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DomAttribute for &'a str {
|
||||||
|
#[inline]
|
||||||
|
fn set_attribute<A: IElement, B: DomBuilder<Value = A>>(self, builder: &mut B, name: &str, namespace: Option<&str>) {
|
||||||
|
dom_operations::set_attribute(builder.value(), name, self, namespace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DomClass for bool {
|
||||||
|
#[inline]
|
||||||
|
fn toggle_class<A: IElement, B: DomBuilder<Value = A>>(self, builder: &mut B, name: &str) {
|
||||||
|
dom_operations::toggle_class(builder.value(), name, self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DomStyle for &'a str {
|
||||||
|
#[inline]
|
||||||
|
fn set_style<A: IStyle, B: DomBuilder<Value = A>>(self, builder: &mut B, name: &str, important: bool) {
|
||||||
|
builder.value().set_style(name, self, important);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DomFocused for bool {
|
||||||
|
#[inline]
|
||||||
|
fn set_focused<A: IHtmlElement + Clone + 'static, B: DomBuilder<Value = A>>(self, builder: &mut B) {
|
||||||
|
let value = builder.value().clone();
|
||||||
|
|
||||||
|
// This needs to use `after_insert` because calling `.focus()` on an element before it is in the DOM has no effect
|
||||||
|
builder.callbacks().after_insert(move |_| {
|
||||||
|
dom_operations::set_focused(&value, self);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO figure out how to make this owned rather than &mut
|
||||||
|
impl<'a, A: IntoIterator<Item = &'a mut Dom>> DomChildren for A {
|
||||||
|
#[inline]
|
||||||
|
fn insert_children<B: INode, C: DomBuilder<Value = B>>(self, builder: &mut C) {
|
||||||
|
for dom in self.into_iter() {
|
||||||
|
{
|
||||||
|
let callbacks = builder.callbacks();
|
||||||
|
callbacks.after_insert.append(&mut dom.callbacks.after_insert);
|
||||||
|
callbacks.after_remove.append(&mut dom.callbacks.after_remove);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.value().append_child(&dom.element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl<S: Signal<Value = String> + 'static> DomProperty for S {
|
||||||
|
// TODO inline this ?
|
||||||
|
fn set_property<A: AsRef<Reference> + Clone + 'static, B: DomBuilder<Value = A>>(self, builder: &mut B, name: &str) {
|
||||||
|
let element = builder.value().clone();
|
||||||
|
let name = name.to_owned();
|
||||||
|
|
||||||
|
let handle = self.for_each(move |value| {
|
||||||
|
dom_operations::set_property(&element, &name, &value);
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.callbacks().after_remove(move || handle.stop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: Signal<Value = Option<String>> + 'static> DomAttribute for S {
|
||||||
|
// TODO inline this ?
|
||||||
|
fn set_attribute<A: IElement + Clone + 'static, B: DomBuilder<Value = A>>(self, builder: &mut B, name: &str, namespace: Option<&str>) {
|
||||||
|
let element = builder.value().clone();
|
||||||
|
let name = name.to_owned();
|
||||||
|
let namespace = namespace.map(|x| x.to_owned());
|
||||||
|
|
||||||
|
let handle = self.for_each(move |value| {
|
||||||
|
// TODO figure out a way to avoid this
|
||||||
|
let namespace = namespace.as_ref().map(|x| x.as_str());
|
||||||
|
|
||||||
|
match value {
|
||||||
|
Some(value) => dom_operations::set_attribute(&element, &name, &value, namespace),
|
||||||
|
None => dom_operations::remove_attribute(&element, &name, namespace),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.callbacks().after_remove(move || handle.stop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: Signal<Value = bool> + 'static> DomClass for S {
|
||||||
|
// TODO inline this ?
|
||||||
|
fn toggle_class<A: IElement + Clone + 'static, B: DomBuilder<Value = A>>(self, builder: &mut B, name: &str) {
|
||||||
|
let element = builder.value().clone();
|
||||||
|
let name = name.to_owned();
|
||||||
|
|
||||||
|
let handle = self.for_each(move |value| {
|
||||||
|
dom_operations::toggle_class(&element, &name, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.callbacks().after_remove(move || handle.stop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: Signal<Value = Option<String>> + 'static> DomStyle for S {
|
||||||
|
// TODO inline this ?
|
||||||
|
fn set_style<A: IStyle + Clone + 'static, B: DomBuilder<Value = A>>(self, builder: &mut B, name: &str, important: bool) {
|
||||||
|
let element = builder.value().clone();
|
||||||
|
let name = name.to_owned();
|
||||||
|
|
||||||
|
let handle = self.for_each(move |value| {
|
||||||
|
match value {
|
||||||
|
Some(value) => element.set_style(&name, &value, important),
|
||||||
|
None => element.set_style(&name, "", important),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.callbacks().after_remove(move || handle.stop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: Signal<Value = bool> + 'static> DomFocused for S {
|
||||||
|
// TODO inline this ?
|
||||||
|
fn set_focused<A: IHtmlElement + Clone + 'static, B: DomBuilder<Value = A>>(self, builder: &mut B) {
|
||||||
|
let element = builder.value().clone();
|
||||||
|
let callbacks = builder.callbacks();
|
||||||
|
|
||||||
|
// This needs to use `after_insert` because calling `.focus()` on an element before it is in the DOM has no effect
|
||||||
|
callbacks.after_insert(move |callbacks| {
|
||||||
|
let handle = self.for_each(move |value| {
|
||||||
|
dom_operations::set_focused(&element, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO verify that this is correct under all circumstances
|
||||||
|
callbacks.after_remove(move || handle.stop());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct StylesheetBuilder {
|
||||||
|
element: CssStyleRule,
|
||||||
|
callbacks: Callbacks,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StylesheetBuilder {
|
||||||
|
#[inline]
|
||||||
|
pub fn new(selector: &str) -> Self {
|
||||||
|
lazy_static! {
|
||||||
|
// TODO better static type for this
|
||||||
|
static ref STYLESHEET: Reference = js!(
|
||||||
|
// TODO use createElementNS ?
|
||||||
|
var e = document.createElement("style");
|
||||||
|
e.type = "text/css";
|
||||||
|
document.head.appendChild(e);
|
||||||
|
return e.sheet;
|
||||||
|
).try_into().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
element: js!(
|
||||||
|
var stylesheet = @{&*STYLESHEET};
|
||||||
|
var length = stylesheet.cssRules.length;
|
||||||
|
stylesheet.insertRule(@{selector} + "{}", length);
|
||||||
|
return stylesheet.cssRules[length];
|
||||||
|
).try_into().unwrap(),
|
||||||
|
callbacks: Callbacks::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn style<B: DomStyle>(mut self, name: &str, value: B) -> Self {
|
||||||
|
value.set_style(&mut self, name, false);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn style_important<B: DomStyle>(mut self, name: &str, value: B) -> Self {
|
||||||
|
value.set_style(&mut self, name, true);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO return a Handle
|
||||||
|
#[inline]
|
||||||
|
pub fn done(mut self) {
|
||||||
|
self.callbacks.trigger_after_insert();
|
||||||
|
|
||||||
|
// This prevents it from calling trigger_after_remove
|
||||||
|
self.callbacks.trigger_remove = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DomBuilder for StylesheetBuilder {
|
||||||
|
type Value = CssStyleRule;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn value(&self) -> &Self::Value {
|
||||||
|
&self.element
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn callbacks(&mut self) -> &mut Callbacks {
|
||||||
|
&mut self.callbacks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct ClassBuilder {
|
||||||
|
stylesheet: StylesheetBuilder,
|
||||||
|
class_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClassBuilder {
|
||||||
|
#[inline]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let class_name = {
|
||||||
|
lazy_static! {
|
||||||
|
// TODO can this be made more efficient ?
|
||||||
|
static ref CLASS_ID: Mutex<u32> = Mutex::new(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut id = CLASS_ID.lock().unwrap();
|
||||||
|
|
||||||
|
*id += 1;
|
||||||
|
|
||||||
|
// TODO make this more efficient ?
|
||||||
|
format!("__class_{}__", id)
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
// TODO make this more efficient ?
|
||||||
|
stylesheet: StylesheetBuilder::new(&format!(".{}", class_name)),
|
||||||
|
class_name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn style<B: DomStyle>(mut self, name: &str, value: B) -> Self {
|
||||||
|
self.stylesheet = self.stylesheet.style(name, value);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn style_important<B: DomStyle>(mut self, name: &str, value: B) -> Self {
|
||||||
|
self.stylesheet = self.stylesheet.style_important(name, value);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO return a Handle ?
|
||||||
|
#[inline]
|
||||||
|
pub fn done(self) -> String {
|
||||||
|
self.stylesheet.done();
|
||||||
|
self.class_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DomBuilder for ClassBuilder {
|
||||||
|
type Value = CssStyleRule;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn value(&self) -> &Self::Value {
|
||||||
|
self.stylesheet.value()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn callbacks(&mut self) -> &mut Callbacks {
|
||||||
|
self.stylesheet.callbacks()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! html {
|
||||||
|
($kind:expr => $t:ty) => {
|
||||||
|
html!($kind => $t, {})
|
||||||
|
};
|
||||||
|
($kind:expr => $t:ty, { $( $name:ident( $( $args:expr ),* ); )* }) => {{
|
||||||
|
let a: $crate::HtmlBuilder<$t> = $crate::HtmlBuilder::new($kind)$(.$name($($args),*))*;
|
||||||
|
let b: $crate::Dom = a.into();
|
||||||
|
b
|
||||||
|
}};
|
||||||
|
|
||||||
|
($kind:expr) => {
|
||||||
|
// TODO need better hygiene for HtmlElement
|
||||||
|
html!($kind => HtmlElement)
|
||||||
|
};
|
||||||
|
($kind:expr, { $( $name:ident( $( $args:expr ),* ); )* }) => {{
|
||||||
|
// TODO need better hygiene for HtmlElement
|
||||||
|
html!($kind => HtmlElement, { $( $name( $( $args ),* ); )* })
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! stylesheet {
|
||||||
|
($rule:expr) => {
|
||||||
|
stylesheet!($rule, {})
|
||||||
|
};
|
||||||
|
($rule:expr, { $( $name:ident( $( $args:expr ),* ); )* }) => {{
|
||||||
|
$crate::StylesheetBuilder::new($rule)$(.$name($($args),*))*.done()
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! class {
|
||||||
|
($( $name:ident( $( $args:expr ),* ); )*) => {{
|
||||||
|
$crate::ClassBuilder::new()$(.$name($($args),*))*.done()
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO move into stdweb
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! clone {
|
||||||
|
({$($x:ident),+} $y:expr) => {{
|
||||||
|
$(let $x = $x.clone();)+
|
||||||
|
$y
|
||||||
|
}};
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate stdweb;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate stdweb_derive;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate lazy_static;
|
||||||
|
|
||||||
|
extern crate futures;
|
||||||
|
|
||||||
|
mod dom;
|
||||||
|
pub use dom::*;
|
||||||
|
|
||||||
|
pub mod signal;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
assert_eq!(2 + 2, 4);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,356 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::cell::Cell;
|
||||||
|
use futures::{Async, Poll};
|
||||||
|
use futures::future::ok;
|
||||||
|
use futures::stream::Stream;
|
||||||
|
use stdweb::PromiseFuture;
|
||||||
|
|
||||||
|
|
||||||
|
pub trait Signal {
|
||||||
|
type Value;
|
||||||
|
|
||||||
|
// TODO use Async<Option<Self::Value>> to allow the Signal to end ?
|
||||||
|
fn poll(&mut self) -> Async<Self::Value>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn to_stream(self) -> SignalStream<Self>
|
||||||
|
where Self: Sized {
|
||||||
|
SignalStream {
|
||||||
|
signal: self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn map<A, B>(self, callback: A) -> Map<Self, A>
|
||||||
|
where A: FnMut(Self::Value) -> B,
|
||||||
|
Self: Sized {
|
||||||
|
Map {
|
||||||
|
signal: self,
|
||||||
|
callback,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn map_dedupe<A, B>(self, callback: A) -> MapDedupe<Self, A>
|
||||||
|
where A: FnMut(&Self::Value) -> B,
|
||||||
|
Self: Sized {
|
||||||
|
MapDedupe {
|
||||||
|
old_value: None,
|
||||||
|
signal: self,
|
||||||
|
callback,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn filter_map<A, B>(self, callback: A) -> FilterMap<Self, A>
|
||||||
|
where A: FnMut(Self::Value) -> Option<B>,
|
||||||
|
Self: Sized {
|
||||||
|
FilterMap {
|
||||||
|
signal: self,
|
||||||
|
callback,
|
||||||
|
first: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn flatten(self) -> Flatten<Self>
|
||||||
|
where Self::Value: Signal,
|
||||||
|
Self: Sized {
|
||||||
|
Flatten {
|
||||||
|
signal: self,
|
||||||
|
inner: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn and_then<A, B>(self, callback: A) -> Flatten<Map<Self, A>>
|
||||||
|
where A: FnMut(Self::Value) -> B,
|
||||||
|
B: Signal,
|
||||||
|
Self: Sized {
|
||||||
|
self.map(callback).flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO make this more efficient
|
||||||
|
fn for_each<A>(self, callback: A) -> DropHandle
|
||||||
|
where A: Fn(Self::Value) + 'static,
|
||||||
|
Self: Sized + 'static {
|
||||||
|
|
||||||
|
let (handle, stream) = drop_handle(self.to_stream());
|
||||||
|
|
||||||
|
PromiseFuture::spawn(
|
||||||
|
stream.for_each(move |value| {
|
||||||
|
callback(value);
|
||||||
|
ok(())
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
handle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO figure out a more efficient way to implement this
|
||||||
|
#[inline]
|
||||||
|
fn drop_handle<A: Stream>(stream: A) -> (DropHandle, DropStream<A>) {
|
||||||
|
let done: Rc<Cell<bool>> = Rc::new(Cell::new(false));
|
||||||
|
|
||||||
|
let drop_handle = DropHandle {
|
||||||
|
done: done.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let drop_stream = DropStream {
|
||||||
|
done,
|
||||||
|
stream,
|
||||||
|
};
|
||||||
|
|
||||||
|
(drop_handle, drop_stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO rename this to something else ?
|
||||||
|
#[must_use]
|
||||||
|
pub struct DropHandle {
|
||||||
|
done: Rc<Cell<bool>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO change this to use Drop, but it requires some changes to the after_remove callback system
|
||||||
|
impl DropHandle {
|
||||||
|
#[inline]
|
||||||
|
pub fn stop(self) {
|
||||||
|
self.done.set(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct DropStream<A> {
|
||||||
|
done: Rc<Cell<bool>>,
|
||||||
|
stream: A,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Stream> Stream for DropStream<A> {
|
||||||
|
type Item = A::Item;
|
||||||
|
type Error = A::Error;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||||
|
if self.done.get() {
|
||||||
|
Ok(Async::Ready(None))
|
||||||
|
|
||||||
|
} else {
|
||||||
|
self.stream.poll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct SignalStream<A> {
|
||||||
|
signal: A,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Signal> Stream for SignalStream<A> {
|
||||||
|
type Item = A::Value;
|
||||||
|
// TODO use Void instead ?
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||||
|
Ok(self.signal.poll().map(Some))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct Map<A, B> {
|
||||||
|
signal: A,
|
||||||
|
callback: B,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A, B, C> Signal for Map<A, B>
|
||||||
|
where A: Signal,
|
||||||
|
B: FnMut(A::Value) -> C {
|
||||||
|
type Value = C;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn poll(&mut self) -> Async<Self::Value> {
|
||||||
|
self.signal.poll().map(|value| (self.callback)(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct MapDedupe<A: Signal, B> {
|
||||||
|
old_value: Option<A::Value>,
|
||||||
|
signal: A,
|
||||||
|
callback: B,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A, B, C> Signal for MapDedupe<A, B>
|
||||||
|
where A: Signal,
|
||||||
|
A::Value: PartialEq,
|
||||||
|
// TODO should this use Fn instead ?
|
||||||
|
B: FnMut(&A::Value) -> C {
|
||||||
|
|
||||||
|
type Value = C;
|
||||||
|
|
||||||
|
// TODO should this use #[inline] ?
|
||||||
|
fn poll(&mut self) -> Async<Self::Value> {
|
||||||
|
loop {
|
||||||
|
match self.signal.poll() {
|
||||||
|
Async::Ready(value) => {
|
||||||
|
let has_changed = match self.old_value {
|
||||||
|
Some(ref old_value) => *old_value != value,
|
||||||
|
None => true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if has_changed {
|
||||||
|
let output = (self.callback)(&value);
|
||||||
|
self.old_value = Some(value);
|
||||||
|
return Async::Ready(output);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Async::NotReady => return Async::NotReady,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct FilterMap<A, B> {
|
||||||
|
signal: A,
|
||||||
|
callback: B,
|
||||||
|
first: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A, B, C> Signal for FilterMap<A, B>
|
||||||
|
where A: Signal,
|
||||||
|
B: FnMut(A::Value) -> Option<C> {
|
||||||
|
type Value = Option<C>;
|
||||||
|
|
||||||
|
// TODO should this use #[inline] ?
|
||||||
|
#[inline]
|
||||||
|
fn poll(&mut self) -> Async<Self::Value> {
|
||||||
|
loop {
|
||||||
|
match self.signal.poll() {
|
||||||
|
Async::Ready(value) => match (self.callback)(value) {
|
||||||
|
Some(value) => {
|
||||||
|
self.first = false;
|
||||||
|
return Async::Ready(Some(value));
|
||||||
|
},
|
||||||
|
None => if self.first {
|
||||||
|
self.first = false;
|
||||||
|
return Async::Ready(None);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Async::NotReady => return Async::NotReady,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct Flatten<A: Signal> {
|
||||||
|
signal: A,
|
||||||
|
inner: Option<A::Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A> Signal for Flatten<A>
|
||||||
|
where A: Signal,
|
||||||
|
A::Value: Signal {
|
||||||
|
type Value = <<A as Signal>::Value as Signal>::Value;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn poll(&mut self) -> Async<Self::Value> {
|
||||||
|
match self.signal.poll() {
|
||||||
|
Async::Ready(mut inner) => {
|
||||||
|
let poll = inner.poll();
|
||||||
|
self.inner = Some(inner);
|
||||||
|
poll
|
||||||
|
},
|
||||||
|
|
||||||
|
Async::NotReady => match self.inner {
|
||||||
|
Some(ref mut inner) => inner.poll(),
|
||||||
|
None => Async::NotReady,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO verify that this is correct
|
||||||
|
pub mod unsync {
|
||||||
|
use super::Signal;
|
||||||
|
use std::rc::{Rc, Weak};
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use futures::Async;
|
||||||
|
use futures::task;
|
||||||
|
|
||||||
|
|
||||||
|
struct Inner<A> {
|
||||||
|
value: Option<A>,
|
||||||
|
task: Option<task::Task>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct Sender<A> {
|
||||||
|
inner: Weak<RefCell<Inner<A>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A> Sender<A> {
|
||||||
|
pub fn set(&self, value: A) -> Result<(), A> {
|
||||||
|
if let Some(inner) = self.inner.upgrade() {
|
||||||
|
let mut inner = inner.borrow_mut();
|
||||||
|
|
||||||
|
inner.value = Some(value);
|
||||||
|
|
||||||
|
if let Some(task) = inner.task.take() {
|
||||||
|
drop(inner);
|
||||||
|
task.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Err(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct Receiver<A> {
|
||||||
|
inner: Rc<RefCell<Inner<A>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A> Signal for Receiver<A> {
|
||||||
|
type Value = A;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn poll(&mut self) -> Async<Self::Value> {
|
||||||
|
let mut inner = self.inner.borrow_mut();
|
||||||
|
|
||||||
|
// TODO is this correct ?
|
||||||
|
match inner.value.take() {
|
||||||
|
Some(value) => Async::Ready(value),
|
||||||
|
None => {
|
||||||
|
inner.task = Some(task::current());
|
||||||
|
Async::NotReady
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn mutable<A>(initial_value: A) -> (Sender<A>, Receiver<A>) {
|
||||||
|
let inner = Rc::new(RefCell::new(Inner {
|
||||||
|
value: Some(initial_value),
|
||||||
|
task: None,
|
||||||
|
}));
|
||||||
|
|
||||||
|
let sender = Sender {
|
||||||
|
inner: Rc::downgrade(&inner),
|
||||||
|
};
|
||||||
|
|
||||||
|
let receiver = Receiver {
|
||||||
|
inner,
|
||||||
|
};
|
||||||
|
|
||||||
|
(sender, receiver)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue