mirror of https://github.com/linebender/xilem
Impl `View` directly on text, and refactor action return types.
This commit is contained in:
parent
da58556d15
commit
89ab953167
|
@ -1215,6 +1215,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28999cda5ef6916ffd33fb4a7b87e1de633c47c0dc6d97905fee1cdaa142b94d"
|
checksum = "28999cda5ef6916ffd33fb4a7b87e1de633c47c0dc6d97905fee1cdaa142b94d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gloo-events",
|
"gloo-events",
|
||||||
|
"gloo-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1227,6 +1228,19 @@ dependencies = [
|
||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gloo-utils"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glow"
|
name = "glow"
|
||||||
version = "0.12.2"
|
version = "0.12.2"
|
||||||
|
@ -1498,6 +1512,12 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.64"
|
version = "0.3.64"
|
||||||
|
@ -2244,6 +2264,12 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
@ -2276,6 +2302,17 @@ dependencies = [
|
||||||
"syn 2.0.18",
|
"syn 2.0.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.99"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46266871c240a00b8f503b877622fe33430b3c7d963bdc0f2adc511e54a1eae3"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_repr"
|
name = "serde_repr"
|
||||||
version = "0.1.12"
|
version = "0.1.12"
|
||||||
|
|
|
@ -6,68 +6,7 @@ edition = "2021"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["typed"]
|
default = ["typed"]
|
||||||
typed = [
|
typed = ['web-sys/FocusEvent', 'web-sys/HtmlAnchorElement', 'web-sys/HtmlAreaElement', 'web-sys/HtmlAudioElement', 'web-sys/HtmlBrElement', 'web-sys/HtmlButtonElement', 'web-sys/HtmlCanvasElement', 'web-sys/HtmlDataElement', 'web-sys/HtmlDataListElement', 'web-sys/HtmlDetailsElement', 'web-sys/HtmlDialogElement', 'web-sys/HtmlDivElement', 'web-sys/HtmlDListElement', 'web-sys/HtmlEmbedElement', 'web-sys/HtmlFieldSetElement', 'web-sys/HtmlFormElement', 'web-sys/HtmlHeadingElement', 'web-sys/HtmlHrElement', 'web-sys/HtmlIFrameElement', 'web-sys/HtmlImageElement', 'web-sys/HtmlInputElement', 'web-sys/HtmlLabelElement', 'web-sys/HtmlLegendElement', 'web-sys/HtmlLiElement', 'web-sys/HtmlMapElement', 'web-sys/HtmlMenuElement', 'web-sys/HtmlMeterElement', 'web-sys/HtmlModElement', 'web-sys/HtmlObjectElement', 'web-sys/HtmlOListElement', 'web-sys/HtmlOptGroupElement', 'web-sys/HtmlOptionElement', 'web-sys/HtmlOutputElement', 'web-sys/HtmlParagraphElement', 'web-sys/HtmlPictureElement', 'web-sys/HtmlPreElement', 'web-sys/HtmlProgressElement', 'web-sys/HtmlQuoteElement', 'web-sys/HtmlScriptElement', 'web-sys/HtmlSelectElement', 'web-sys/HtmlSlotElement', 'web-sys/HtmlSourceElement', 'web-sys/HtmlSpanElement', 'web-sys/HtmlTableElement', 'web-sys/HtmlTableCellElement', 'web-sys/HtmlTableColElement', 'web-sys/HtmlTableCaptionElement', 'web-sys/HtmlTableRowElement', 'web-sys/HtmlTableSectionElement', 'web-sys/HtmlTemplateElement', 'web-sys/HtmlTextAreaElement', 'web-sys/HtmlTimeElement', 'web-sys/HtmlTrackElement', 'web-sys/HtmlUListElement', 'web-sys/HtmlVideoElement', 'web-sys/InputEvent', 'web-sys/KeyboardEvent', 'web-sys/MouseEvent', 'web-sys/PointerEvent', 'web-sys/WheelEvent']
|
||||||
'web-sys/FocusEvent',
|
|
||||||
'web-sys/HtmlAnchorElement',
|
|
||||||
'web-sys/HtmlAreaElement',
|
|
||||||
'web-sys/HtmlAudioElement',
|
|
||||||
'web-sys/HtmlBrElement',
|
|
||||||
'web-sys/HtmlButtonElement',
|
|
||||||
'web-sys/HtmlCanvasElement',
|
|
||||||
'web-sys/HtmlDataElement',
|
|
||||||
'web-sys/HtmlDataListElement',
|
|
||||||
'web-sys/HtmlDetailsElement',
|
|
||||||
'web-sys/HtmlDialogElement',
|
|
||||||
'web-sys/HtmlDivElement',
|
|
||||||
'web-sys/HtmlDListElement',
|
|
||||||
'web-sys/HtmlEmbedElement',
|
|
||||||
'web-sys/HtmlFieldSetElement',
|
|
||||||
'web-sys/HtmlFormElement',
|
|
||||||
'web-sys/HtmlHeadingElement',
|
|
||||||
'web-sys/HtmlHrElement',
|
|
||||||
'web-sys/HtmlIFrameElement',
|
|
||||||
'web-sys/HtmlImageElement',
|
|
||||||
'web-sys/HtmlInputElement',
|
|
||||||
'web-sys/HtmlLabelElement',
|
|
||||||
'web-sys/HtmlLegendElement',
|
|
||||||
'web-sys/HtmlLiElement',
|
|
||||||
'web-sys/HtmlMapElement',
|
|
||||||
'web-sys/HtmlMenuElement',
|
|
||||||
'web-sys/HtmlMeterElement',
|
|
||||||
'web-sys/HtmlModElement',
|
|
||||||
'web-sys/HtmlObjectElement',
|
|
||||||
'web-sys/HtmlOListElement',
|
|
||||||
'web-sys/HtmlOptGroupElement',
|
|
||||||
'web-sys/HtmlOptionElement',
|
|
||||||
'web-sys/HtmlOutputElement',
|
|
||||||
'web-sys/HtmlParagraphElement',
|
|
||||||
'web-sys/HtmlPictureElement',
|
|
||||||
'web-sys/HtmlPreElement',
|
|
||||||
'web-sys/HtmlProgressElement',
|
|
||||||
'web-sys/HtmlQuoteElement',
|
|
||||||
'web-sys/HtmlScriptElement',
|
|
||||||
'web-sys/HtmlSelectElement',
|
|
||||||
'web-sys/HtmlSlotElement',
|
|
||||||
'web-sys/HtmlSourceElement',
|
|
||||||
'web-sys/HtmlSpanElement',
|
|
||||||
'web-sys/HtmlTableElement',
|
|
||||||
'web-sys/HtmlTableCellElement',
|
|
||||||
'web-sys/HtmlTableColElement',
|
|
||||||
'web-sys/HtmlTableCaptionElement',
|
|
||||||
'web-sys/HtmlTableRowElement',
|
|
||||||
'web-sys/HtmlTableSectionElement',
|
|
||||||
'web-sys/HtmlTemplateElement',
|
|
||||||
'web-sys/HtmlTextAreaElement',
|
|
||||||
'web-sys/HtmlTimeElement',
|
|
||||||
'web-sys/HtmlTrackElement',
|
|
||||||
'web-sys/HtmlUListElement',
|
|
||||||
'web-sys/HtmlVideoElement',
|
|
||||||
'web-sys/InputEvent',
|
|
||||||
'web-sys/KeyboardEvent',
|
|
||||||
'web-sys/MouseEvent',
|
|
||||||
'web-sys/PointerEvent',
|
|
||||||
'web-sys/WheelEvent',
|
|
||||||
]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bitflags = "1.3.2"
|
bitflags = "1.3.2"
|
||||||
|
@ -75,7 +14,7 @@ wasm-bindgen = "0.2.87"
|
||||||
kurbo = "0.9.1"
|
kurbo = "0.9.1"
|
||||||
xilem_core = { path = "../xilem_core" }
|
xilem_core = { path = "../xilem_core" }
|
||||||
log = "0.4.19"
|
log = "0.4.19"
|
||||||
gloo = { version = "0.8.1", default-features = false, features = ["events"] }
|
gloo = { version = "0.8.1", default-features = false, features = ["events", "utils"] }
|
||||||
|
|
||||||
[dependencies.web-sys]
|
[dependencies.web-sys]
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
|
|
|
@ -10,19 +10,49 @@ macro_rules! events {
|
||||||
|
|
||||||
macro_rules! event {
|
macro_rules! event {
|
||||||
($ty_name:ident, $builder_name:ident, $name:literal, $web_sys_ty:ty) => {
|
($ty_name:ident, $builder_name:ident, $name:literal, $web_sys_ty:ty) => {
|
||||||
pub struct $ty_name<V, F>(crate::OnEvent<$web_sys_ty, V, F>);
|
pub struct $ty_name<T, A, V, F, OA>
|
||||||
|
|
||||||
pub fn $builder_name<V, F>(child: V, callback: F) -> $ty_name<V, F> {
|
|
||||||
$ty_name(crate::on_event($name, child, callback))
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V, F> crate::view::ViewMarker for $ty_name<V, F> {}
|
|
||||||
|
|
||||||
impl<T, A, V, F> crate::view::View<T, A> for $ty_name<V, F>
|
|
||||||
where
|
where
|
||||||
V: crate::view::View<T, A>,
|
V: crate::view::View<T, A>,
|
||||||
F: Fn(&mut T, &$crate::Event<$web_sys_ty, V::Element>) -> $crate::MessageResult<A>,
|
F: Fn(&mut T, &$crate::Event<$web_sys_ty, V::Element>) -> OA,
|
||||||
V::Element: 'static,
|
V::Element: 'static,
|
||||||
|
OA: $crate::event::OptionalAction<A>,
|
||||||
|
{
|
||||||
|
inner: crate::OnEvent<$web_sys_ty, V, F>,
|
||||||
|
data: std::marker::PhantomData<T>,
|
||||||
|
action: std::marker::PhantomData<A>,
|
||||||
|
optional_action: std::marker::PhantomData<OA>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn $builder_name<T, A, V, F, OA>(child: V, callback: F) -> $ty_name<T, A, V, F, OA>
|
||||||
|
where
|
||||||
|
V: crate::view::View<T, A>,
|
||||||
|
F: Fn(&mut T, &$crate::Event<$web_sys_ty, V::Element>) -> OA,
|
||||||
|
V::Element: 'static,
|
||||||
|
OA: $crate::event::OptionalAction<A>,
|
||||||
|
{
|
||||||
|
$ty_name {
|
||||||
|
inner: crate::on_event($name, child, callback),
|
||||||
|
data: std::marker::PhantomData,
|
||||||
|
action: std::marker::PhantomData,
|
||||||
|
optional_action: std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, A, V, F, OA> crate::view::ViewMarker for $ty_name<T, A, V, F, OA>
|
||||||
|
where
|
||||||
|
V: crate::view::View<T, A>,
|
||||||
|
F: Fn(&mut T, &$crate::Event<$web_sys_ty, V::Element>) -> OA,
|
||||||
|
V::Element: 'static,
|
||||||
|
OA: $crate::event::OptionalAction<A>,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, A, V, F, OA> crate::view::View<T, A> for $ty_name<T, A, V, F, OA>
|
||||||
|
where
|
||||||
|
V: crate::view::View<T, A>,
|
||||||
|
F: Fn(&mut T, &$crate::Event<$web_sys_ty, V::Element>) -> OA,
|
||||||
|
V::Element: 'static,
|
||||||
|
OA: $crate::event::OptionalAction<A>,
|
||||||
{
|
{
|
||||||
type State = crate::event::OnEventState<V::State>;
|
type State = crate::event::OnEventState<V::State>;
|
||||||
type Element = V::Element;
|
type Element = V::Element;
|
||||||
|
@ -31,7 +61,7 @@ macro_rules! event {
|
||||||
&self,
|
&self,
|
||||||
cx: &mut crate::context::Cx,
|
cx: &mut crate::context::Cx,
|
||||||
) -> (xilem_core::Id, Self::State, Self::Element) {
|
) -> (xilem_core::Id, Self::State, Self::Element) {
|
||||||
self.0.build(cx)
|
self.inner.build(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rebuild(
|
fn rebuild(
|
||||||
|
@ -42,7 +72,7 @@ macro_rules! event {
|
||||||
state: &mut Self::State,
|
state: &mut Self::State,
|
||||||
element: &mut Self::Element,
|
element: &mut Self::Element,
|
||||||
) -> crate::ChangeFlags {
|
) -> crate::ChangeFlags {
|
||||||
self.0.rebuild(cx, &prev.0, id, state, element)
|
self.inner.rebuild(cx, &prev.inner, id, state, element)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn message(
|
fn message(
|
||||||
|
@ -52,7 +82,7 @@ macro_rules! event {
|
||||||
message: Box<dyn std::any::Any>,
|
message: Box<dyn std::any::Any>,
|
||||||
app_state: &mut T,
|
app_state: &mut T,
|
||||||
) -> xilem_core::MessageResult<A> {
|
) -> xilem_core::MessageResult<A> {
|
||||||
self.0.message(id_path, state, message, app_state)
|
self.inner.message(id_path, state, message, app_state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -37,12 +37,13 @@ impl<E, V, F> OnEvent<E, V, F> {
|
||||||
|
|
||||||
impl<E, V, F> ViewMarker for OnEvent<E, V, F> {}
|
impl<E, V, F> ViewMarker for OnEvent<E, V, F> {}
|
||||||
|
|
||||||
impl<T, A, E, F, V> View<T, A> for OnEvent<E, V, F>
|
impl<T, A, E, F, V, OA> View<T, A> for OnEvent<E, V, F>
|
||||||
where
|
where
|
||||||
F: Fn(&mut T, &Event<E, V::Element>) -> MessageResult<A>,
|
F: Fn(&mut T, &Event<E, V::Element>) -> OA,
|
||||||
V: View<T, A>,
|
V: View<T, A>,
|
||||||
E: JsCast + 'static,
|
E: JsCast + 'static,
|
||||||
V::Element: 'static,
|
V::Element: 'static,
|
||||||
|
OA: OptionalAction<A>,
|
||||||
{
|
{
|
||||||
type State = OnEventState<V::State>;
|
type State = OnEventState<V::State>;
|
||||||
|
|
||||||
|
@ -90,7 +91,10 @@ where
|
||||||
app_state: &mut T,
|
app_state: &mut T,
|
||||||
) -> MessageResult<A> {
|
) -> MessageResult<A> {
|
||||||
if let Some(msg) = message.downcast_ref::<EventMsg<Event<E, V::Element>>>() {
|
if let Some(msg) = message.downcast_ref::<EventMsg<Event<E, V::Element>>>() {
|
||||||
(self.callback)(app_state, &msg.event)
|
match (self.callback)(app_state, &msg.event).action() {
|
||||||
|
Some(a) => MessageResult::Action(a),
|
||||||
|
None => MessageResult::Nop,
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
self.child
|
self.child
|
||||||
.message(id_path, &mut state.child_state, message, app_state)
|
.message(id_path, &mut state.child_state, message, app_state)
|
||||||
|
@ -143,3 +147,39 @@ impl<Evt, El> Deref for Event<Evt, El> {
|
||||||
&self.raw
|
&self.raw
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implement this trait for types you want to use as actions.
|
||||||
|
///
|
||||||
|
/// The trait exists because otherwise we couldn't provide versions
|
||||||
|
/// of listeners that take `()`, `A` and `Option<A>`.
|
||||||
|
pub trait Action {}
|
||||||
|
|
||||||
|
/// Trait that allows callbacks to be polymorphic on return type
|
||||||
|
/// (`Action`, `Option<Action>` or `()`)
|
||||||
|
pub trait OptionalAction<A>: sealed::Sealed {
|
||||||
|
fn action(self) -> Option<A>;
|
||||||
|
}
|
||||||
|
mod sealed {
|
||||||
|
pub trait Sealed {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl sealed::Sealed for () {}
|
||||||
|
impl<A> OptionalAction<A> for () {
|
||||||
|
fn action(self) -> Option<A> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Action> sealed::Sealed for A {}
|
||||||
|
impl<A: Action> OptionalAction<A> for A {
|
||||||
|
fn action(self) -> Option<A> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Action> sealed::Sealed for Option<A> {}
|
||||||
|
impl<A: Action> OptionalAction<A> for Option<A> {
|
||||||
|
fn action(self) -> Option<A> {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ mod context;
|
||||||
mod event;
|
mod event;
|
||||||
//mod div;
|
//mod div;
|
||||||
mod element;
|
mod element;
|
||||||
mod text;
|
|
||||||
mod view;
|
mod view;
|
||||||
#[cfg(feature = "typed")]
|
#[cfg(feature = "typed")]
|
||||||
mod view_ext;
|
mod view_ext;
|
||||||
|
@ -29,8 +28,7 @@ pub use element::elements;
|
||||||
pub use element::{element, Element, ElementState};
|
pub use element::{element, Element, ElementState};
|
||||||
#[cfg(feature = "typed")]
|
#[cfg(feature = "typed")]
|
||||||
pub use event::events;
|
pub use event::events;
|
||||||
pub use event::{on_event, Event, OnEvent, OnEventState};
|
pub use event::{on_event, Action, Event, OnEvent, OnEventState, OptionalAction};
|
||||||
pub use text::{text, Text};
|
|
||||||
pub use view::{Adapt, AdaptThunk, Pod, View, ViewMarker, ViewSequence};
|
pub use view::{Adapt, AdaptThunk, Pod, View, ViewMarker, ViewSequence};
|
||||||
#[cfg(feature = "typed")]
|
#[cfg(feature = "typed")]
|
||||||
pub use view_ext::ViewExt;
|
pub use view_ext::ViewExt;
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
use wasm_bindgen::JsCast;
|
|
||||||
|
|
||||||
use xilem_core::{Id, MessageResult};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
context::{ChangeFlags, Cx},
|
|
||||||
view::{View, ViewMarker},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Text {
|
|
||||||
text: Cow<'static, str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a text node
|
|
||||||
pub fn text(text: impl Into<Cow<'static, str>>) -> Text {
|
|
||||||
Text { text: text.into() }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ViewMarker for Text {}
|
|
||||||
|
|
||||||
impl<T, A> View<T, A> for Text {
|
|
||||||
type State = ();
|
|
||||||
type Element = web_sys::Text;
|
|
||||||
|
|
||||||
fn build(&self, _cx: &mut Cx) -> (Id, Self::State, Self::Element) {
|
|
||||||
let el = new_text(&self.text);
|
|
||||||
let id = Id::next();
|
|
||||||
(id, (), el.unchecked_into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rebuild(
|
|
||||||
&self,
|
|
||||||
_cx: &mut Cx,
|
|
||||||
prev: &Self,
|
|
||||||
_id: &mut Id,
|
|
||||||
_state: &mut Self::State,
|
|
||||||
element: &mut Self::Element,
|
|
||||||
) -> ChangeFlags {
|
|
||||||
let mut is_changed = ChangeFlags::empty();
|
|
||||||
if prev.text != self.text {
|
|
||||||
element.set_data(&self.text);
|
|
||||||
is_changed |= ChangeFlags::OTHER_CHANGE;
|
|
||||||
}
|
|
||||||
is_changed
|
|
||||||
}
|
|
||||||
|
|
||||||
fn message(
|
|
||||||
&self,
|
|
||||||
_id_path: &[Id],
|
|
||||||
_state: &mut Self::State,
|
|
||||||
_message: Box<dyn std::any::Any>,
|
|
||||||
_app_state: &mut T,
|
|
||||||
) -> MessageResult<A> {
|
|
||||||
MessageResult::Nop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_text(text: &str) -> web_sys::Text {
|
|
||||||
web_sys::Text::new_with_data(text).unwrap()
|
|
||||||
}
|
|
|
@ -4,7 +4,9 @@
|
||||||
//! Integration with xilem_core. This instantiates the View and related
|
//! Integration with xilem_core. This instantiates the View and related
|
||||||
//! traits for DOM node generation.
|
//! traits for DOM node generation.
|
||||||
|
|
||||||
use std::{any::Any, ops::Deref};
|
use std::{any::Any, borrow::Cow, ops::Deref};
|
||||||
|
|
||||||
|
use xilem_core::{Id, MessageResult};
|
||||||
|
|
||||||
use crate::{context::Cx, ChangeFlags};
|
use crate::{context::Cx, ChangeFlags};
|
||||||
|
|
||||||
|
@ -98,3 +100,121 @@ impl Pod {
|
||||||
xilem_core::generate_view_trait! {View, DomNode, Cx, ChangeFlags;}
|
xilem_core::generate_view_trait! {View, DomNode, Cx, ChangeFlags;}
|
||||||
xilem_core::generate_viewsequence_trait! {ViewSequence, View, ViewMarker, DomNode, Cx, ChangeFlags, Pod;}
|
xilem_core::generate_viewsequence_trait! {ViewSequence, View, ViewMarker, DomNode, Cx, ChangeFlags, Pod;}
|
||||||
xilem_core::generate_anyview_trait! {View, Cx, ChangeFlags, AnyNode}
|
xilem_core::generate_anyview_trait! {View, Cx, ChangeFlags, AnyNode}
|
||||||
|
|
||||||
|
impl ViewMarker for &'static str {}
|
||||||
|
impl<T, A> View<T, A> for &'static str {
|
||||||
|
type State = ();
|
||||||
|
type Element = web_sys::Text;
|
||||||
|
|
||||||
|
fn build(&self, _cx: &mut Cx) -> (Id, Self::State, Self::Element) {
|
||||||
|
let el = new_text(self);
|
||||||
|
let id = Id::next();
|
||||||
|
(id, (), el.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rebuild(
|
||||||
|
&self,
|
||||||
|
_cx: &mut Cx,
|
||||||
|
prev: &Self,
|
||||||
|
_id: &mut Id,
|
||||||
|
_state: &mut Self::State,
|
||||||
|
element: &mut Self::Element,
|
||||||
|
) -> ChangeFlags {
|
||||||
|
let mut is_changed = ChangeFlags::empty();
|
||||||
|
if prev != self {
|
||||||
|
element.set_data(self);
|
||||||
|
is_changed |= ChangeFlags::OTHER_CHANGE;
|
||||||
|
}
|
||||||
|
is_changed
|
||||||
|
}
|
||||||
|
|
||||||
|
fn message(
|
||||||
|
&self,
|
||||||
|
_id_path: &[Id],
|
||||||
|
_state: &mut Self::State,
|
||||||
|
_message: Box<dyn std::any::Any>,
|
||||||
|
_app_state: &mut T,
|
||||||
|
) -> MessageResult<A> {
|
||||||
|
MessageResult::Nop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ViewMarker for String {}
|
||||||
|
impl<T, A> View<T, A> for String {
|
||||||
|
type State = ();
|
||||||
|
type Element = web_sys::Text;
|
||||||
|
|
||||||
|
fn build(&self, _cx: &mut Cx) -> (Id, Self::State, Self::Element) {
|
||||||
|
let el = new_text(self);
|
||||||
|
let id = Id::next();
|
||||||
|
(id, (), el.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rebuild(
|
||||||
|
&self,
|
||||||
|
_cx: &mut Cx,
|
||||||
|
prev: &Self,
|
||||||
|
_id: &mut Id,
|
||||||
|
_state: &mut Self::State,
|
||||||
|
element: &mut Self::Element,
|
||||||
|
) -> ChangeFlags {
|
||||||
|
let mut is_changed = ChangeFlags::empty();
|
||||||
|
if prev != self {
|
||||||
|
element.set_data(self);
|
||||||
|
is_changed |= ChangeFlags::OTHER_CHANGE;
|
||||||
|
}
|
||||||
|
is_changed
|
||||||
|
}
|
||||||
|
|
||||||
|
fn message(
|
||||||
|
&self,
|
||||||
|
_id_path: &[Id],
|
||||||
|
_state: &mut Self::State,
|
||||||
|
_message: Box<dyn std::any::Any>,
|
||||||
|
_app_state: &mut T,
|
||||||
|
) -> MessageResult<A> {
|
||||||
|
MessageResult::Nop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ViewMarker for Cow<'static, str> {}
|
||||||
|
impl<T, A> View<T, A> for Cow<'static, str> {
|
||||||
|
type State = ();
|
||||||
|
type Element = web_sys::Text;
|
||||||
|
|
||||||
|
fn build(&self, _cx: &mut Cx) -> (Id, Self::State, Self::Element) {
|
||||||
|
let el = new_text(self);
|
||||||
|
let id = Id::next();
|
||||||
|
(id, (), el.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rebuild(
|
||||||
|
&self,
|
||||||
|
_cx: &mut Cx,
|
||||||
|
prev: &Self,
|
||||||
|
_id: &mut Id,
|
||||||
|
_state: &mut Self::State,
|
||||||
|
element: &mut Self::Element,
|
||||||
|
) -> ChangeFlags {
|
||||||
|
let mut is_changed = ChangeFlags::empty();
|
||||||
|
if prev != self {
|
||||||
|
element.set_data(self);
|
||||||
|
is_changed |= ChangeFlags::OTHER_CHANGE;
|
||||||
|
}
|
||||||
|
is_changed
|
||||||
|
}
|
||||||
|
|
||||||
|
fn message(
|
||||||
|
&self,
|
||||||
|
_id_path: &[Id],
|
||||||
|
_state: &mut Self::State,
|
||||||
|
_message: Box<dyn std::any::Any>,
|
||||||
|
_app_state: &mut T,
|
||||||
|
) -> MessageResult<A> {
|
||||||
|
MessageResult::Nop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_text(text: &str) -> web_sys::Text {
|
||||||
|
web_sys::Text::new_with_data(text).unwrap()
|
||||||
|
}
|
||||||
|
|
|
@ -3,61 +3,77 @@
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use xilem_core::MessageResult;
|
use crate::{class::Class, event::OptionalAction, events as e, view::View, Event};
|
||||||
|
|
||||||
use crate::{class::Class, events as e, view::View, Event};
|
|
||||||
|
|
||||||
pub trait ViewExt<T, A>: View<T, A> + Sized {
|
pub trait ViewExt<T, A>: View<T, A> + Sized {
|
||||||
fn on_click<F: Fn(&mut T, &Event<web_sys::MouseEvent, Self::Element>) -> MessageResult<A>>(
|
fn on_click<
|
||||||
self,
|
OA: OptionalAction<A>,
|
||||||
f: F,
|
F: Fn(&mut T, &Event<web_sys::MouseEvent, Self::Element>) -> OA,
|
||||||
) -> e::OnClick<Self, F>;
|
|
||||||
fn on_dblclick<F: Fn(&mut T, &Event<web_sys::MouseEvent, Self::Element>) -> MessageResult<A>>(
|
|
||||||
self,
|
|
||||||
f: F,
|
|
||||||
) -> e::OnDblClick<Self, F>;
|
|
||||||
fn on_input<F: Fn(&mut T, &Event<web_sys::InputEvent, Self::Element>) -> MessageResult<A>>(
|
|
||||||
self,
|
|
||||||
f: F,
|
|
||||||
) -> e::OnInput<Self, F>;
|
|
||||||
fn on_keydown<
|
|
||||||
F: Fn(&mut T, &Event<web_sys::KeyboardEvent, Self::Element>) -> MessageResult<A>,
|
|
||||||
>(
|
>(
|
||||||
self,
|
self,
|
||||||
f: F,
|
f: F,
|
||||||
) -> e::OnKeyDown<Self, F>;
|
) -> e::OnClick<T, A, Self, F, OA>;
|
||||||
|
fn on_dblclick<
|
||||||
|
OA: OptionalAction<A>,
|
||||||
|
F: Fn(&mut T, &Event<web_sys::MouseEvent, Self::Element>) -> OA,
|
||||||
|
>(
|
||||||
|
self,
|
||||||
|
f: F,
|
||||||
|
) -> e::OnDblClick<T, A, Self, F, OA>;
|
||||||
|
fn on_input<
|
||||||
|
OA: OptionalAction<A>,
|
||||||
|
F: Fn(&mut T, &Event<web_sys::InputEvent, Self::Element>) -> OA,
|
||||||
|
>(
|
||||||
|
self,
|
||||||
|
f: F,
|
||||||
|
) -> e::OnInput<T, A, Self, F, OA>;
|
||||||
|
fn on_keydown<
|
||||||
|
OA: OptionalAction<A>,
|
||||||
|
F: Fn(&mut T, &Event<web_sys::KeyboardEvent, Self::Element>) -> OA,
|
||||||
|
>(
|
||||||
|
self,
|
||||||
|
f: F,
|
||||||
|
) -> e::OnKeyDown<T, A, Self, F, OA>;
|
||||||
fn class(self, class: impl Into<Cow<'static, str>>) -> Class<Self> {
|
fn class(self, class: impl Into<Cow<'static, str>>) -> Class<Self> {
|
||||||
crate::class::class(self, class)
|
crate::class::class(self, class)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, A, V: View<T, A>> ViewExt<T, A> for V {
|
impl<T, A, V: View<T, A>> ViewExt<T, A> for V {
|
||||||
fn on_click<F: Fn(&mut T, &Event<web_sys::MouseEvent, Self::Element>) -> MessageResult<A>>(
|
fn on_click<
|
||||||
|
OA: OptionalAction<A>,
|
||||||
|
F: Fn(&mut T, &Event<web_sys::MouseEvent, Self::Element>) -> OA,
|
||||||
|
>(
|
||||||
self,
|
self,
|
||||||
f: F,
|
f: F,
|
||||||
) -> e::OnClick<Self, F> {
|
) -> e::OnClick<T, A, Self, F, OA> {
|
||||||
e::on_click(self, f)
|
e::on_click(self, f)
|
||||||
}
|
}
|
||||||
fn on_dblclick<
|
fn on_dblclick<
|
||||||
F: Fn(&mut T, &Event<web_sys::MouseEvent, Self::Element>) -> MessageResult<A>,
|
OA: OptionalAction<A>,
|
||||||
|
F: Fn(&mut T, &Event<web_sys::MouseEvent, Self::Element>) -> OA,
|
||||||
>(
|
>(
|
||||||
self,
|
self,
|
||||||
f: F,
|
f: F,
|
||||||
) -> e::OnDblClick<Self, F> {
|
) -> e::OnDblClick<T, A, Self, F, OA> {
|
||||||
e::on_dblclick(self, f)
|
e::on_dblclick(self, f)
|
||||||
}
|
}
|
||||||
fn on_input<F: Fn(&mut T, &Event<web_sys::InputEvent, Self::Element>) -> MessageResult<A>>(
|
fn on_input<
|
||||||
|
OA: OptionalAction<A>,
|
||||||
|
F: Fn(&mut T, &Event<web_sys::InputEvent, Self::Element>) -> OA,
|
||||||
|
>(
|
||||||
self,
|
self,
|
||||||
f: F,
|
f: F,
|
||||||
) -> e::OnInput<Self, F> {
|
) -> e::OnInput<T, A, Self, F, OA> {
|
||||||
crate::events::on_input(self, f)
|
crate::events::on_input(self, f)
|
||||||
}
|
}
|
||||||
fn on_keydown<
|
fn on_keydown<
|
||||||
F: Fn(&mut T, &Event<web_sys::KeyboardEvent, Self::Element>) -> MessageResult<A>,
|
OA: OptionalAction<A>,
|
||||||
|
F: Fn(&mut T, &Event<web_sys::KeyboardEvent, Self::Element>) -> OA,
|
||||||
>(
|
>(
|
||||||
self,
|
self,
|
||||||
f: F,
|
f: F,
|
||||||
) -> e::OnKeyDown<Self, F> {
|
) -> e::OnKeyDown<T, A, Self, F, OA> {
|
||||||
crate::events::on_keydown(self, f)
|
crate::events::on_keydown(self, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use wasm_bindgen::{prelude::*, JsValue};
|
use wasm_bindgen::{prelude::*, JsValue};
|
||||||
use xilem_html::{
|
use xilem_html::{
|
||||||
document_body, elements as el, events as evt, text, App, Event, MessageResult, Text, View,
|
document_body, elements as el,
|
||||||
ViewExt,
|
events::{self as evt},
|
||||||
|
App, Event, View, ViewExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -12,61 +13,54 @@ struct AppState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
fn increment(&mut self) -> MessageResult<()> {
|
fn increment(&mut self) {
|
||||||
self.clicks += 1;
|
self.clicks += 1;
|
||||||
MessageResult::Nop
|
|
||||||
}
|
}
|
||||||
fn decrement(&mut self) -> MessageResult<()> {
|
fn decrement(&mut self) {
|
||||||
self.clicks -= 1;
|
self.clicks -= 1;
|
||||||
MessageResult::Nop
|
|
||||||
}
|
}
|
||||||
fn reset(&mut self) -> MessageResult<()> {
|
fn reset(&mut self) {
|
||||||
self.clicks = 0;
|
self.clicks = 0;
|
||||||
MessageResult::Nop
|
|
||||||
}
|
}
|
||||||
fn change_class(&mut self) -> MessageResult<()> {
|
fn change_class(&mut self) {
|
||||||
if self.class == "gray" {
|
if self.class == "gray" {
|
||||||
self.class = "green";
|
self.class = "green";
|
||||||
} else {
|
} else {
|
||||||
self.class = "gray";
|
self.class = "gray";
|
||||||
}
|
}
|
||||||
MessageResult::Nop
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn change_text(&mut self) -> MessageResult<()> {
|
fn change_text(&mut self) {
|
||||||
if self.text == "test" {
|
if self.text == "test" {
|
||||||
self.text = "test2".into();
|
self.text = "test2".into();
|
||||||
} else {
|
} else {
|
||||||
self.text = "test".into();
|
self.text = "test".into();
|
||||||
}
|
}
|
||||||
MessageResult::Nop
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// You can create functions that generate views.
|
/// You can create functions that generate views.
|
||||||
fn btn<F>(label: &'static str, click_fn: F) -> evt::OnClick<el::Button<Text>, F>
|
fn btn<A, F>(
|
||||||
|
label: &'static str,
|
||||||
|
click_fn: F,
|
||||||
|
) -> evt::OnClick<AppState, A, el::Button<&'static str>, F, ()>
|
||||||
where
|
where
|
||||||
F: Fn(
|
F: Fn(&mut AppState, &Event<web_sys::MouseEvent, web_sys::HtmlButtonElement>),
|
||||||
&mut AppState,
|
|
||||||
&Event<web_sys::MouseEvent, web_sys::HtmlButtonElement>,
|
|
||||||
) -> MessageResult<()>,
|
|
||||||
{
|
{
|
||||||
el::button(text(label)).on_click(click_fn)
|
el::button(label).on_click(click_fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn app_logic(state: &mut AppState) -> impl View<AppState> {
|
fn app_logic(state: &mut AppState) -> impl View<AppState> {
|
||||||
el::div((
|
el::div((
|
||||||
el::span(text(format!("clicked {} times", state.clicks))).attr("class", state.class),
|
el::span(format!("clicked {} times", state.clicks)).attr("class", state.class),
|
||||||
el::br(()),
|
el::br(()),
|
||||||
btn("+1 click", |state, _| AppState::increment(state)),
|
btn("+1 click", |state, _| state.increment()),
|
||||||
btn("-1 click", |state, _| AppState::decrement(state)),
|
btn("-1 click", |state, _| state.decrement()),
|
||||||
btn("reset clicks", |state, _| AppState::reset(state)),
|
btn("reset clicks", |state, _| state.reset()),
|
||||||
btn("a different class", |state, _| {
|
btn("a different class", |state, _| state.change_class()),
|
||||||
AppState::change_class(state)
|
btn("change text", |state, _| state.change_text()),
|
||||||
}),
|
|
||||||
btn("change text", |state, _| AppState::change_text(state)),
|
|
||||||
el::br(()),
|
el::br(()),
|
||||||
text(state.text.clone()),
|
state.text.clone(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
use wasm_bindgen::{prelude::*, JsValue};
|
use wasm_bindgen::{prelude::*, JsValue};
|
||||||
use xilem_html::{
|
use xilem_html::{document_body, element as el, on_event, App, Event, View, ViewMarker};
|
||||||
document_body, element as el, on_event, text, App, Event, MessageResult, View, ViewMarker,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct AppState {
|
struct AppState {
|
||||||
|
@ -9,32 +7,35 @@ struct AppState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
fn increment(&mut self) -> MessageResult<()> {
|
fn increment(&mut self) {
|
||||||
self.clicks += 1;
|
self.clicks += 1;
|
||||||
MessageResult::Nop
|
|
||||||
}
|
}
|
||||||
fn decrement(&mut self) -> MessageResult<()> {
|
fn decrement(&mut self) {
|
||||||
self.clicks -= 1;
|
self.clicks -= 1;
|
||||||
MessageResult::Nop
|
|
||||||
}
|
}
|
||||||
fn reset(&mut self) -> MessageResult<()> {
|
fn reset(&mut self) {
|
||||||
self.clicks = 0;
|
self.clicks = 0;
|
||||||
MessageResult::Nop
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn btn<F>(label: &'static str, click_fn: F) -> impl View<AppState> + ViewMarker
|
fn btn<F>(label: &'static str, click_fn: F) -> impl View<AppState> + ViewMarker
|
||||||
where
|
where
|
||||||
F: Fn(&mut AppState, &Event<web_sys::Event, web_sys::HtmlButtonElement>) -> MessageResult<()>,
|
F: Fn(&mut AppState, &Event<web_sys::Event, web_sys::HtmlButtonElement>),
|
||||||
{
|
{
|
||||||
on_event("click", el("button", text(label)), click_fn)
|
on_event(
|
||||||
|
"click",
|
||||||
|
el("button", label),
|
||||||
|
move |state: &mut AppState, evt: &Event<_, _>| {
|
||||||
|
click_fn(state, evt);
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn app_logic(state: &mut AppState) -> impl View<AppState> {
|
fn app_logic(state: &mut AppState) -> impl View<AppState> {
|
||||||
el::<web_sys::HtmlElement, _>(
|
el::<web_sys::HtmlElement, _>(
|
||||||
"div",
|
"div",
|
||||||
(
|
(
|
||||||
el::<web_sys::HtmlElement, _>("span", text(format!("clicked {} times", state.clicks))),
|
el::<web_sys::HtmlElement, _>("span", format!("clicked {} times", state.clicks)),
|
||||||
btn("+1 click", |state, _| AppState::increment(state)),
|
btn("+1 click", |state, _| AppState::increment(state)),
|
||||||
btn("-1 click", |state, _| AppState::decrement(state)),
|
btn("-1 click", |state, _| AppState::decrement(state)),
|
||||||
btn("reset clicks", |state, _| AppState::reset(state)),
|
btn("reset clicks", |state, _| AppState::reset(state)),
|
||||||
|
|
|
@ -6,7 +6,8 @@ use state::{AppState, Filter, Todo};
|
||||||
|
|
||||||
use wasm_bindgen::{prelude::*, JsValue};
|
use wasm_bindgen::{prelude::*, JsValue};
|
||||||
use xilem_html::{
|
use xilem_html::{
|
||||||
elements as el, get_element_by_id, text, Adapt, App, MessageResult, View, ViewExt, ViewMarker,
|
elements as el, events::on_click, get_element_by_id, Action, Adapt, App, Event, MessageResult,
|
||||||
|
View, ViewExt, ViewMarker,
|
||||||
};
|
};
|
||||||
|
|
||||||
// All of these actions arise from within a `Todo`, but we need access to the full state to reduce
|
// All of these actions arise from within a `Todo`, but we need access to the full state to reduce
|
||||||
|
@ -17,6 +18,8 @@ enum TodoAction {
|
||||||
Destroy(u64),
|
Destroy(u64),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Action for TodoAction {}
|
||||||
|
|
||||||
fn todo_item(todo: &mut Todo, editing: bool) -> impl View<Todo, TodoAction> + ViewMarker {
|
fn todo_item(todo: &mut Todo, editing: bool) -> impl View<Todo, TodoAction> + ViewMarker {
|
||||||
let mut class = String::new();
|
let mut class = String::new();
|
||||||
if todo.completed {
|
if todo.completed {
|
||||||
|
@ -36,16 +39,12 @@ fn todo_item(todo: &mut Todo, editing: bool) -> impl View<Todo, TodoAction> + Vi
|
||||||
el::div((
|
el::div((
|
||||||
input.on_click(|state: &mut Todo, _| {
|
input.on_click(|state: &mut Todo, _| {
|
||||||
state.completed = !state.completed;
|
state.completed = !state.completed;
|
||||||
MessageResult::RequestRebuild
|
|
||||||
}),
|
|
||||||
el::label(text(todo.title.clone())).on_dblclick(|state: &mut Todo, _| {
|
|
||||||
MessageResult::Action(TodoAction::SetEditing(state.id))
|
|
||||||
}),
|
}),
|
||||||
|
el::label(todo.title.clone())
|
||||||
|
.on_dblclick(|state: &mut Todo, _| TodoAction::SetEditing(state.id)),
|
||||||
el::button(())
|
el::button(())
|
||||||
.attr("class", "destroy")
|
.attr("class", "destroy")
|
||||||
.on_click(|state: &mut Todo, _| {
|
.on_click(|state: &mut Todo, _| TodoAction::Destroy(state.id)),
|
||||||
MessageResult::Action(TodoAction::Destroy(state.id))
|
|
||||||
}),
|
|
||||||
))
|
))
|
||||||
.attr("class", "view"),
|
.attr("class", "view"),
|
||||||
el::input(())
|
el::input(())
|
||||||
|
@ -55,18 +54,17 @@ fn todo_item(todo: &mut Todo, editing: bool) -> impl View<Todo, TodoAction> + Vi
|
||||||
let key = evt.key();
|
let key = evt.key();
|
||||||
if key == "Enter" {
|
if key == "Enter" {
|
||||||
state.save_editing();
|
state.save_editing();
|
||||||
MessageResult::Action(TodoAction::CancelEditing)
|
Some(TodoAction::CancelEditing)
|
||||||
} else if key == "Escape" {
|
} else if key == "Escape" {
|
||||||
MessageResult::Action(TodoAction::CancelEditing)
|
Some(TodoAction::CancelEditing)
|
||||||
} else {
|
} else {
|
||||||
MessageResult::Nop
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on_input(|state: &mut Todo, evt| {
|
.on_input(|state: &mut Todo, evt| {
|
||||||
state.title_editing.clear();
|
state.title_editing.clear();
|
||||||
state.title_editing.push_str(&evt.target().value());
|
state.title_editing.push_str(&evt.target().value());
|
||||||
evt.prevent_default();
|
evt.prevent_default();
|
||||||
MessageResult::Nop
|
|
||||||
}),
|
}),
|
||||||
))
|
))
|
||||||
.attr("class", class)
|
.attr("class", class)
|
||||||
|
@ -80,12 +78,12 @@ fn footer_view(state: &mut AppState) -> impl View<AppState> + ViewMarker {
|
||||||
};
|
};
|
||||||
|
|
||||||
let clear_button = (state.todos.iter().filter(|todo| todo.completed).count() > 0).then(|| {
|
let clear_button = (state.todos.iter().filter(|todo| todo.completed).count() > 0).then(|| {
|
||||||
el::button(text("Clear completed"))
|
on_click(
|
||||||
.attr("class", "clear-completed")
|
el::button("Clear completed").attr("class", "clear-completed"),
|
||||||
.on_click(|state: &mut AppState, _| {
|
|state: &mut AppState, _| {
|
||||||
state.todos.retain(|todo| !todo.completed);
|
state.todos.retain(|todo| !todo.completed);
|
||||||
MessageResult::RequestRebuild
|
},
|
||||||
})
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let filter_class = |filter| {
|
let filter_class = |filter| {
|
||||||
|
@ -98,40 +96,37 @@ fn footer_view(state: &mut AppState) -> impl View<AppState> + ViewMarker {
|
||||||
|
|
||||||
el::footer((
|
el::footer((
|
||||||
el::span((
|
el::span((
|
||||||
el::strong(text(state.todos.len().to_string())),
|
el::strong(state.todos.len().to_string()),
|
||||||
text(format!(" {} left", item_str)),
|
format!(" {} left", item_str),
|
||||||
))
|
))
|
||||||
.attr("class", "todo-count"),
|
.attr("class", "todo-count"),
|
||||||
el::ul((
|
el::ul((
|
||||||
el::li(
|
el::li(on_click(
|
||||||
el::a(text("All"))
|
el::a("All")
|
||||||
.attr("href", "#/")
|
.attr("href", "#/")
|
||||||
.attr("class", filter_class(Filter::All))
|
.attr("class", filter_class(Filter::All)),
|
||||||
.on_click(|state: &mut AppState, _| {
|
|state: &mut AppState, _| {
|
||||||
state.filter = Filter::All;
|
state.filter = Filter::All;
|
||||||
MessageResult::RequestRebuild
|
},
|
||||||
}),
|
)),
|
||||||
),
|
" ",
|
||||||
text(" "),
|
el::li(on_click(
|
||||||
el::li(
|
el::a("Active")
|
||||||
el::a(text("Active"))
|
|
||||||
.attr("href", "#/active")
|
.attr("href", "#/active")
|
||||||
.attr("class", filter_class(Filter::Active))
|
.attr("class", filter_class(Filter::Active)),
|
||||||
.on_click(|state: &mut AppState, _| {
|
|state: &mut AppState, _| {
|
||||||
state.filter = Filter::Active;
|
state.filter = Filter::Active;
|
||||||
MessageResult::RequestRebuild
|
},
|
||||||
}),
|
)),
|
||||||
),
|
" ",
|
||||||
text(" "),
|
el::li(on_click(
|
||||||
el::li(
|
el::a("Completed")
|
||||||
el::a(text("Completed"))
|
|
||||||
.attr("href", "#/completed")
|
.attr("href", "#/completed")
|
||||||
.attr("class", filter_class(Filter::Completed))
|
.attr("class", filter_class(Filter::Completed)),
|
||||||
.on_click(|state: &mut AppState, _| {
|
|state: &mut AppState, _| {
|
||||||
state.filter = Filter::Completed;
|
state.filter = Filter::Completed;
|
||||||
MessageResult::RequestRebuild
|
},
|
||||||
}),
|
)),
|
||||||
),
|
|
||||||
))
|
))
|
||||||
.attr("class", "filters"),
|
.attr("class", "filters"),
|
||||||
clear_button,
|
clear_button,
|
||||||
|
@ -177,23 +172,26 @@ fn app_logic(state: &mut AppState) -> impl View<AppState> {
|
||||||
let footer = (!state.todos.is_empty()).then(|| footer_view(state));
|
let footer = (!state.todos.is_empty()).then(|| footer_view(state));
|
||||||
el::div((
|
el::div((
|
||||||
el::header((
|
el::header((
|
||||||
el::h1(text("TODOs")),
|
el::h1("TODOs"),
|
||||||
el::input(())
|
el::input(())
|
||||||
.attr("class", "new-todo")
|
.attr("class", "new-todo")
|
||||||
.attr("placeholder", "What needs to be done?")
|
.attr("placeholder", "What needs to be done?")
|
||||||
.attr("value", state.new_todo.clone())
|
.attr("value", state.new_todo.clone())
|
||||||
.attr("autofocus", "true")
|
.attr("autofocus", "true")
|
||||||
.on_keydown(|state: &mut AppState, evt| {
|
.on_keydown(
|
||||||
|
|state: &mut AppState, evt| {
|
||||||
if evt.key() == "Enter" {
|
if evt.key() == "Enter" {
|
||||||
state.create_todo();
|
state.create_todo();
|
||||||
}
|
}
|
||||||
MessageResult::RequestRebuild
|
},
|
||||||
})
|
)
|
||||||
.on_input(|state: &mut AppState, evt| {
|
.on_input(
|
||||||
|
|state: &mut AppState,
|
||||||
|
evt: &Event<web_sys::InputEvent, web_sys::HtmlInputElement>| {
|
||||||
state.update_new_todo(&evt.target().value());
|
state.update_new_todo(&evt.target().value());
|
||||||
evt.prevent_default();
|
evt.prevent_default();
|
||||||
MessageResult::RequestRebuild
|
},
|
||||||
}),
|
),
|
||||||
))
|
))
|
||||||
.attr("class", "header"),
|
.attr("class", "header"),
|
||||||
main,
|
main,
|
||||||
|
|
Loading…
Reference in New Issue