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"
|
||||
dependencies = [
|
||||
"gloo-events",
|
||||
"gloo-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1227,6 +1228,19 @@ dependencies = [
|
|||
"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]]
|
||||
name = "glow"
|
||||
version = "0.12.2"
|
||||
|
@ -1498,6 +1512,12 @@ dependencies = [
|
|||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.64"
|
||||
|
@ -2244,6 +2264,12 @@ dependencies = [
|
|||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
|
@ -2276,6 +2302,17 @@ dependencies = [
|
|||
"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]]
|
||||
name = "serde_repr"
|
||||
version = "0.1.12"
|
||||
|
|
|
@ -6,68 +6,7 @@ edition = "2021"
|
|||
|
||||
[features]
|
||||
default = ["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',
|
||||
]
|
||||
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']
|
||||
|
||||
[dependencies]
|
||||
bitflags = "1.3.2"
|
||||
|
@ -75,7 +14,7 @@ wasm-bindgen = "0.2.87"
|
|||
kurbo = "0.9.1"
|
||||
xilem_core = { path = "../xilem_core" }
|
||||
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]
|
||||
version = "0.3.4"
|
||||
|
|
|
@ -10,19 +10,49 @@ macro_rules! events {
|
|||
|
||||
macro_rules! event {
|
||||
($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 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>
|
||||
pub struct $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>) -> $crate::MessageResult<A>,
|
||||
F: Fn(&mut T, &$crate::Event<$web_sys_ty, V::Element>) -> OA,
|
||||
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 Element = V::Element;
|
||||
|
@ -31,7 +61,7 @@ macro_rules! event {
|
|||
&self,
|
||||
cx: &mut crate::context::Cx,
|
||||
) -> (xilem_core::Id, Self::State, Self::Element) {
|
||||
self.0.build(cx)
|
||||
self.inner.build(cx)
|
||||
}
|
||||
|
||||
fn rebuild(
|
||||
|
@ -42,7 +72,7 @@ macro_rules! event {
|
|||
state: &mut Self::State,
|
||||
element: &mut Self::Element,
|
||||
) -> crate::ChangeFlags {
|
||||
self.0.rebuild(cx, &prev.0, id, state, element)
|
||||
self.inner.rebuild(cx, &prev.inner, id, state, element)
|
||||
}
|
||||
|
||||
fn message(
|
||||
|
@ -52,7 +82,7 @@ macro_rules! event {
|
|||
message: Box<dyn std::any::Any>,
|
||||
app_state: &mut T,
|
||||
) -> 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<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
|
||||
F: Fn(&mut T, &Event<E, V::Element>) -> MessageResult<A>,
|
||||
F: Fn(&mut T, &Event<E, V::Element>) -> OA,
|
||||
V: View<T, A>,
|
||||
E: JsCast + 'static,
|
||||
V::Element: 'static,
|
||||
OA: OptionalAction<A>,
|
||||
{
|
||||
type State = OnEventState<V::State>;
|
||||
|
||||
|
@ -90,7 +91,10 @@ where
|
|||
app_state: &mut T,
|
||||
) -> MessageResult<A> {
|
||||
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 {
|
||||
self.child
|
||||
.message(id_path, &mut state.child_state, message, app_state)
|
||||
|
@ -143,3 +147,39 @@ impl<Evt, El> Deref for Event<Evt, El> {
|
|||
&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 div;
|
||||
mod element;
|
||||
mod text;
|
||||
mod view;
|
||||
#[cfg(feature = "typed")]
|
||||
mod view_ext;
|
||||
|
@ -29,8 +28,7 @@ pub use element::elements;
|
|||
pub use element::{element, Element, ElementState};
|
||||
#[cfg(feature = "typed")]
|
||||
pub use event::events;
|
||||
pub use event::{on_event, Event, OnEvent, OnEventState};
|
||||
pub use text::{text, Text};
|
||||
pub use event::{on_event, Action, Event, OnEvent, OnEventState, OptionalAction};
|
||||
pub use view::{Adapt, AdaptThunk, Pod, View, ViewMarker, ViewSequence};
|
||||
#[cfg(feature = "typed")]
|
||||
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
|
||||
//! 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};
|
||||
|
||||
|
@ -98,3 +100,121 @@ impl Pod {
|
|||
xilem_core::generate_view_trait! {View, DomNode, Cx, ChangeFlags;}
|
||||
xilem_core::generate_viewsequence_trait! {ViewSequence, View, ViewMarker, DomNode, Cx, ChangeFlags, Pod;}
|
||||
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 xilem_core::MessageResult;
|
||||
|
||||
use crate::{class::Class, events as e, view::View, Event};
|
||||
use crate::{class::Class, event::OptionalAction, events as e, view::View, Event};
|
||||
|
||||
pub trait ViewExt<T, A>: View<T, A> + Sized {
|
||||
fn on_click<F: Fn(&mut T, &Event<web_sys::MouseEvent, Self::Element>) -> MessageResult<A>>(
|
||||
self,
|
||||
f: F,
|
||||
) -> 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>,
|
||||
fn on_click<
|
||||
OA: OptionalAction<A>,
|
||||
F: Fn(&mut T, &Event<web_sys::MouseEvent, Self::Element>) -> OA,
|
||||
>(
|
||||
self,
|
||||
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> {
|
||||
crate::class::class(self, class)
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
f: F,
|
||||
) -> e::OnClick<Self, F> {
|
||||
) -> e::OnClick<T, A, Self, F, OA> {
|
||||
e::on_click(self, f)
|
||||
}
|
||||
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,
|
||||
f: F,
|
||||
) -> e::OnDblClick<Self, F> {
|
||||
) -> e::OnDblClick<T, A, Self, F, OA> {
|
||||
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,
|
||||
f: F,
|
||||
) -> e::OnInput<Self, F> {
|
||||
) -> e::OnInput<T, A, Self, F, OA> {
|
||||
crate::events::on_input(self, f)
|
||||
}
|
||||
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,
|
||||
f: F,
|
||||
) -> e::OnKeyDown<Self, F> {
|
||||
) -> e::OnKeyDown<T, A, Self, F, OA> {
|
||||
crate::events::on_keydown(self, f)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use wasm_bindgen::{prelude::*, JsValue};
|
||||
use xilem_html::{
|
||||
document_body, elements as el, events as evt, text, App, Event, MessageResult, Text, View,
|
||||
ViewExt,
|
||||
document_body, elements as el,
|
||||
events::{self as evt},
|
||||
App, Event, View, ViewExt,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -12,61 +13,54 @@ struct AppState {
|
|||
}
|
||||
|
||||
impl AppState {
|
||||
fn increment(&mut self) -> MessageResult<()> {
|
||||
fn increment(&mut self) {
|
||||
self.clicks += 1;
|
||||
MessageResult::Nop
|
||||
}
|
||||
fn decrement(&mut self) -> MessageResult<()> {
|
||||
fn decrement(&mut self) {
|
||||
self.clicks -= 1;
|
||||
MessageResult::Nop
|
||||
}
|
||||
fn reset(&mut self) -> MessageResult<()> {
|
||||
fn reset(&mut self) {
|
||||
self.clicks = 0;
|
||||
MessageResult::Nop
|
||||
}
|
||||
fn change_class(&mut self) -> MessageResult<()> {
|
||||
fn change_class(&mut self) {
|
||||
if self.class == "gray" {
|
||||
self.class = "green";
|
||||
} else {
|
||||
self.class = "gray";
|
||||
}
|
||||
MessageResult::Nop
|
||||
}
|
||||
|
||||
fn change_text(&mut self) -> MessageResult<()> {
|
||||
fn change_text(&mut self) {
|
||||
if self.text == "test" {
|
||||
self.text = "test2".into();
|
||||
} else {
|
||||
self.text = "test".into();
|
||||
}
|
||||
MessageResult::Nop
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
F: Fn(
|
||||
&mut AppState,
|
||||
&Event<web_sys::MouseEvent, web_sys::HtmlButtonElement>,
|
||||
) -> MessageResult<()>,
|
||||
F: Fn(&mut AppState, &Event<web_sys::MouseEvent, web_sys::HtmlButtonElement>),
|
||||
{
|
||||
el::button(text(label)).on_click(click_fn)
|
||||
el::button(label).on_click(click_fn)
|
||||
}
|
||||
|
||||
fn app_logic(state: &mut AppState) -> impl View<AppState> {
|
||||
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(()),
|
||||
btn("+1 click", |state, _| AppState::increment(state)),
|
||||
btn("-1 click", |state, _| AppState::decrement(state)),
|
||||
btn("reset clicks", |state, _| AppState::reset(state)),
|
||||
btn("a different class", |state, _| {
|
||||
AppState::change_class(state)
|
||||
}),
|
||||
btn("change text", |state, _| AppState::change_text(state)),
|
||||
btn("+1 click", |state, _| state.increment()),
|
||||
btn("-1 click", |state, _| state.decrement()),
|
||||
btn("reset clicks", |state, _| state.reset()),
|
||||
btn("a different class", |state, _| state.change_class()),
|
||||
btn("change text", |state, _| state.change_text()),
|
||||
el::br(()),
|
||||
text(state.text.clone()),
|
||||
state.text.clone(),
|
||||
))
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
use wasm_bindgen::{prelude::*, JsValue};
|
||||
use xilem_html::{
|
||||
document_body, element as el, on_event, text, App, Event, MessageResult, View, ViewMarker,
|
||||
};
|
||||
use xilem_html::{document_body, element as el, on_event, App, Event, View, ViewMarker};
|
||||
|
||||
#[derive(Default)]
|
||||
struct AppState {
|
||||
|
@ -9,32 +7,35 @@ struct AppState {
|
|||
}
|
||||
|
||||
impl AppState {
|
||||
fn increment(&mut self) -> MessageResult<()> {
|
||||
fn increment(&mut self) {
|
||||
self.clicks += 1;
|
||||
MessageResult::Nop
|
||||
}
|
||||
fn decrement(&mut self) -> MessageResult<()> {
|
||||
fn decrement(&mut self) {
|
||||
self.clicks -= 1;
|
||||
MessageResult::Nop
|
||||
}
|
||||
fn reset(&mut self) -> MessageResult<()> {
|
||||
fn reset(&mut self) {
|
||||
self.clicks = 0;
|
||||
MessageResult::Nop
|
||||
}
|
||||
}
|
||||
|
||||
fn btn<F>(label: &'static str, click_fn: F) -> impl View<AppState> + ViewMarker
|
||||
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> {
|
||||
el::<web_sys::HtmlElement, _>(
|
||||
"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::decrement(state)),
|
||||
btn("reset clicks", |state, _| AppState::reset(state)),
|
||||
|
|
|
@ -6,7 +6,8 @@ use state::{AppState, Filter, Todo};
|
|||
|
||||
use wasm_bindgen::{prelude::*, JsValue};
|
||||
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
|
||||
|
@ -17,6 +18,8 @@ enum TodoAction {
|
|||
Destroy(u64),
|
||||
}
|
||||
|
||||
impl Action for TodoAction {}
|
||||
|
||||
fn todo_item(todo: &mut Todo, editing: bool) -> impl View<Todo, TodoAction> + ViewMarker {
|
||||
let mut class = String::new();
|
||||
if todo.completed {
|
||||
|
@ -36,16 +39,12 @@ fn todo_item(todo: &mut Todo, editing: bool) -> impl View<Todo, TodoAction> + Vi
|
|||
el::div((
|
||||
input.on_click(|state: &mut Todo, _| {
|
||||
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(())
|
||||
.attr("class", "destroy")
|
||||
.on_click(|state: &mut Todo, _| {
|
||||
MessageResult::Action(TodoAction::Destroy(state.id))
|
||||
}),
|
||||
.on_click(|state: &mut Todo, _| TodoAction::Destroy(state.id)),
|
||||
))
|
||||
.attr("class", "view"),
|
||||
el::input(())
|
||||
|
@ -55,18 +54,17 @@ fn todo_item(todo: &mut Todo, editing: bool) -> impl View<Todo, TodoAction> + Vi
|
|||
let key = evt.key();
|
||||
if key == "Enter" {
|
||||
state.save_editing();
|
||||
MessageResult::Action(TodoAction::CancelEditing)
|
||||
Some(TodoAction::CancelEditing)
|
||||
} else if key == "Escape" {
|
||||
MessageResult::Action(TodoAction::CancelEditing)
|
||||
Some(TodoAction::CancelEditing)
|
||||
} else {
|
||||
MessageResult::Nop
|
||||
None
|
||||
}
|
||||
})
|
||||
.on_input(|state: &mut Todo, evt| {
|
||||
state.title_editing.clear();
|
||||
state.title_editing.push_str(&evt.target().value());
|
||||
evt.prevent_default();
|
||||
MessageResult::Nop
|
||||
}),
|
||||
))
|
||||
.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(|| {
|
||||
el::button(text("Clear completed"))
|
||||
.attr("class", "clear-completed")
|
||||
.on_click(|state: &mut AppState, _| {
|
||||
on_click(
|
||||
el::button("Clear completed").attr("class", "clear-completed"),
|
||||
|state: &mut AppState, _| {
|
||||
state.todos.retain(|todo| !todo.completed);
|
||||
MessageResult::RequestRebuild
|
||||
})
|
||||
},
|
||||
)
|
||||
});
|
||||
|
||||
let filter_class = |filter| {
|
||||
|
@ -98,40 +96,37 @@ fn footer_view(state: &mut AppState) -> impl View<AppState> + ViewMarker {
|
|||
|
||||
el::footer((
|
||||
el::span((
|
||||
el::strong(text(state.todos.len().to_string())),
|
||||
text(format!(" {} left", item_str)),
|
||||
el::strong(state.todos.len().to_string()),
|
||||
format!(" {} left", item_str),
|
||||
))
|
||||
.attr("class", "todo-count"),
|
||||
el::ul((
|
||||
el::li(
|
||||
el::a(text("All"))
|
||||
el::li(on_click(
|
||||
el::a("All")
|
||||
.attr("href", "#/")
|
||||
.attr("class", filter_class(Filter::All))
|
||||
.on_click(|state: &mut AppState, _| {
|
||||
.attr("class", filter_class(Filter::All)),
|
||||
|state: &mut AppState, _| {
|
||||
state.filter = Filter::All;
|
||||
MessageResult::RequestRebuild
|
||||
}),
|
||||
),
|
||||
text(" "),
|
||||
el::li(
|
||||
el::a(text("Active"))
|
||||
},
|
||||
)),
|
||||
" ",
|
||||
el::li(on_click(
|
||||
el::a("Active")
|
||||
.attr("href", "#/active")
|
||||
.attr("class", filter_class(Filter::Active))
|
||||
.on_click(|state: &mut AppState, _| {
|
||||
.attr("class", filter_class(Filter::Active)),
|
||||
|state: &mut AppState, _| {
|
||||
state.filter = Filter::Active;
|
||||
MessageResult::RequestRebuild
|
||||
}),
|
||||
),
|
||||
text(" "),
|
||||
el::li(
|
||||
el::a(text("Completed"))
|
||||
},
|
||||
)),
|
||||
" ",
|
||||
el::li(on_click(
|
||||
el::a("Completed")
|
||||
.attr("href", "#/completed")
|
||||
.attr("class", filter_class(Filter::Completed))
|
||||
.on_click(|state: &mut AppState, _| {
|
||||
.attr("class", filter_class(Filter::Completed)),
|
||||
|state: &mut AppState, _| {
|
||||
state.filter = Filter::Completed;
|
||||
MessageResult::RequestRebuild
|
||||
}),
|
||||
),
|
||||
},
|
||||
)),
|
||||
))
|
||||
.attr("class", "filters"),
|
||||
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));
|
||||
el::div((
|
||||
el::header((
|
||||
el::h1(text("TODOs")),
|
||||
el::h1("TODOs"),
|
||||
el::input(())
|
||||
.attr("class", "new-todo")
|
||||
.attr("placeholder", "What needs to be done?")
|
||||
.attr("value", state.new_todo.clone())
|
||||
.attr("autofocus", "true")
|
||||
.on_keydown(|state: &mut AppState, evt| {
|
||||
.on_keydown(
|
||||
|state: &mut AppState, evt| {
|
||||
if evt.key() == "Enter" {
|
||||
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());
|
||||
evt.prevent_default();
|
||||
MessageResult::RequestRebuild
|
||||
}),
|
||||
},
|
||||
),
|
||||
))
|
||||
.attr("class", "header"),
|
||||
main,
|
||||
|
|
Loading…
Reference in New Issue