Merge pull request #53 from DioxusLabs/jk/polish

docs: remove all usages of static closure syntax and update readme
This commit is contained in:
Jonathan Kelley 2022-01-02 18:35:55 -05:00 committed by GitHub
commit d7e967ba5b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
66 changed files with 792 additions and 2019 deletions

View file

@ -22,19 +22,24 @@
alt="docs.rs docs" />
</a>
<!-- CI -->
<a href="https://github.com/jkelleyrtp/dioxus/actions">
<!-- <a href="https://github.com/jkelleyrtp/dioxus/actions">
<img src="https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg"
alt="CI status" />
</a> -->
<!--Awesome -->
<a href="https://github.com/dioxuslabs/awesome-dioxus">
<img src="https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg"
alt="Awesome Page" />
</a>
</div>
<div align="center">
<h3>
<a href="https://dioxuslabs.com/guide"> Guide </a>
<span> | </span>
<a href="https://dioxuslabs.com"> Website </a>
<span> | </span>
<a href="https://github.com/DioxusLabs/awesome-dioxus"> Examples </a>
<a href="https://dioxuslabs.com/guide"> Guide </a>
<span> | </span>
<a href="https://github.com/DioxusLabs/example-projects"> Examples </a>
</h3>
</div>
@ -66,6 +71,15 @@ If you know React, then you already know Dioxus.
- Multi-channel asynchronous scheduler for first-class async support.
- And more! Read the [full release post here](https://dioxuslabs.com/).
### Examples
All examples in this repo are desktop apps. To run an example, simply clone this repo and use cargo with the `desktop` feature enabled. For SSR examples, you might need to enable SSR instead.
```
cargo run --features desktop --example EXAMPLE
```
## Get Started with...
<table style="width:100%" align="center">
@ -80,8 +94,7 @@ If you know React, then you already know Dioxus.
</table>
## Examples Projects:
## Example Projects:
| File Navigator (Desktop) | WiFi scanner (Desktop) | TodoMVC (All platforms) | E-commerce w/ Tailwind (Liveview) |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@ -90,9 +103,6 @@ If you know React, then you already know Dioxus.
See the awesome-dioxus page for a curated list of content in the Dioxus Ecosystem.
## Running examples locally
All local examples are built for the desktop renderer. This means you can simply clone this repo and call `cargo run --example EXAMPLE_NAME`. To run non-desktop examples, checkout the example projects shown above.
## Why Dioxus and why Rust?

View file

@ -81,11 +81,6 @@ fn App(cx: Scope)-> Element {
}
```
This syntax even enables us to write a one-line component:
```rust
static App: Component = |cx| rsx!(cx, "hello world!");
```
Alternatively, for match statements, we can just return the builder itself and pass it into a final, single call to `cx.render`:
```rust

View file

@ -132,12 +132,6 @@ fn App(cx: Scope) -> Element {
}
```
Writing `fn App(cx: Scope) -> Element {` might become tedious. Rust will also let you write functions as static closures, but these types of Components cannot have props that borrow data.
```rust
static App: Component = |cx| cx.render(rsx!(div { "Hello, world!" }));
```
### What is this `Scope` object?
Coming from React, the `Scope` object might be confusing. In React, you'll want to store data between renders with hooks. However, hooks rely on global variables which make them difficult to integrate in multi-tenant systems like server-rendering.

View file

@ -115,7 +115,7 @@ fn App(cx: Scope) -> Element {
Tiny components:
```rust
static App: Component = |cx, _| rsx!(cx, div {"hello world!"});
static App: Component = |cx| rsx!(cx, div {"hello world!"});
```
Borrowed prop contents:

View file

@ -17,27 +17,32 @@ fn app(cx: Scope) -> Element {
let (async_count, dir) = (count.for_async(), *direction);
let task = use_coroutine(&cx, move || async move {
loop {
TimeoutFuture::new(250).await;
*async_count.modify() += dir;
let task = use_coroutine(&cx, move || {
//
async move {
loop {
TimeoutFuture::new(250).await;
// *async_count.modify() += dir;
}
}
});
rsx!(cx, div {
h1 {"count is {count}"}
button { onclick: move |_| task.stop(),
"Stop counting"
}
button { onclick: move |_| task.resume(),
"Start counting"
}
button {
onclick: move |_| {
*direction.modify() *= -1;
task.restart();
},
"Switch counting direcion"
cx.render(rsx! {
div {
h1 {"count is {count}"}
button { onclick: move |_| task.stop(),
"Stop counting"
}
button { onclick: move |_| task.resume(),
"Start counting"
}
button {
onclick: move |_| {
*direction.modify() *= -1;
task.restart();
},
"Switch counting direcion"
}
}
})
}

View file

@ -17,10 +17,10 @@ and is proven to be safe with MIRI.
use dioxus::prelude::*;
fn main() {
dioxus::desktop::launch(App);
dioxus::desktop::launch(app);
}
fn App(cx: Scope) -> Element {
fn app(cx: Scope) -> Element {
let text = cx.use_hook(|_| vec![String::from("abc=def")]);
let first = text.get_mut(0).unwrap();

View file

@ -12,7 +12,7 @@ use dioxus::prelude::*;
use separator::Separatable;
fn main() {
// dioxus::desktop::launch(app);
dioxus::desktop::launch(app);
}
fn app(cx: Scope) -> Element {

View file

@ -1,33 +0,0 @@
use dioxus_core::prelude::*;
use dioxus_core_macro::format_args_f;
use dioxus_core_macro::rsx;
use dioxus_html as dioxus_elements;
fn main() {
let mut dom = VirtualDom::new(EXAMPLE);
dom.rebuild();
println!("{}", dom);
}
pub static EXAMPLE: Component = |cx| {
let list = (0..10).map(|_f| {
rsx! {
"{_f}"
}
});
cx.render(Some(LazyNodes::new(move |cx| {
cx.raw_element(
"div",
None,
[],
[],
[
cx.text(format_args!("hello")),
cx.text(format_args!("hello")),
cx.fragment_from_iter(list),
],
None,
)
})))
};

View file

@ -1,108 +0,0 @@
use bumpalo::Bump;
fn main() {}
fn build(factory: Factory) {
factory.text();
div::new(factory)
.r#class()
.r#tag()
.r#type()
.add_children()
.iter_children()
.finish();
}
/// # The `div` element
///
///
/// The <div> HTML element is the generic container for flow content. It has no effect on the content or layout until
/// styled in some way using CSS (e.g. styling is directly applied to it, or some kind of layout model like Flexbox is
/// applied to its parent element).
///
/// As a "pure" container, the <div> element does not inherently represent anything. Instead, it's used to group content
/// so it can be easily styled using the class or id attributes, marking a section of a document as being written in a
/// different language (using the lang attribute), and so on.
///
/// ## Usage
/// ```
/// rsx!{
/// div { class: "tall", id: "unique id"
/// h1 {}
/// p {}
/// }
/// }
///
/// ```
///
/// ## Specifications
/// - Content categories: Flow content, palpable content.
/// - Permitted content: Flow content.
/// - Permitted parents: Any element that accepts flow content.
#[allow(non_camel_case_types)]
struct div {}
struct h1 {}
struct h2 {}
trait BasicElement: Sized {
const TagName: &'static str;
fn get_bump(&self) -> &Bump;
fn new(factory: Factory) -> Self;
fn add_children(self) -> Self {
self
}
fn iter_children(self) -> Self {
self
}
fn finish(self) -> Self {
self
}
}
impl BasicElement for div {
const TagName: &'static str = "div";
fn get_bump(&self) -> &Bump {
todo!()
}
fn new(_factory: Factory) -> Self {
todo!()
}
}
impl div {
fn class(self) -> Self {
self
}
fn tag(self) -> Self {
self
}
fn r#type(self) -> Self {
self
}
}
#[derive(Clone, Copy)]
struct Factory<'a> {
bump: &'a bumpalo::Bump,
}
impl<'a> Factory<'a> {
fn text(&self) -> &str {
todo!()
}
fn new_el(&'a self, tag: &'static str) -> ElementBuilder<'a> {
ElementBuilder {
bump: self.bump,
tag,
}
}
}
struct ElementBuilder<'a> {
tag: &'static str,
bump: &'a bumpalo::Bump,
}

View file

@ -1,118 +0,0 @@
#![allow(non_snake_case)]
use dioxus::component::Scope;
use dioxus::events::on::MouseEvent;
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_core_macro::*;
use dioxus_html as dioxus_elements;
use rand::prelude::*;
use std::fmt::Display;
fn main() {
let mut dom = VirtualDom::new(App);
let g = dom.rebuild();
assert!(g.edits.len() > 1);
}
fn App((cx, props): Scope) -> Element {
let mut rng = SmallRng::from_entropy();
let rows = (0..10_000_usize).map(|f| {
let label = Label::new(&mut rng);
rsx! {
Row {
row_id: f,
label: label
}
}
});
cx.render(rsx! {
table {
tbody {
{rows}
}
}
})
}
#[derive(PartialEq, Props)]
struct RowProps {
row_id: usize,
label: Label,
}
fn Row<'a>((cx, props): Scope<'a, RowProps>) -> Element<'a> {
let handler = move |evt: MouseEvent| {
let g = evt.button;
};
cx.render(rsx! {
tr {
// td { class:"col-md-1", "{cx.props.row_id}" }
// td { class:"col-md-1", onclick: move |_| { /* run onselect */ }
// a { class: "lbl", "{cx.props.label}" }
// }
// td { class: "col-md-1"
// a { class: "remove", onclick: {handler}
// span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" }
// }
// }
// td { class: "col-md-6" }
}
})
}
#[derive(PartialEq)]
struct Label([&'static str; 3]);
impl Label {
fn new(rng: &mut SmallRng) -> Self {
Label([
ADJECTIVES.choose(rng).unwrap(),
COLOURS.choose(rng).unwrap(),
NOUNS.choose(rng).unwrap(),
])
}
}
impl Display for Label {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} {} {}", self.0[0], self.0[1], self.0[2])
}
}
static ADJECTIVES: &[&str] = &[
"pretty",
"large",
"big",
"small",
"tall",
"short",
"long",
"handsome",
"plain",
"quaint",
"clean",
"elegant",
"easy",
"angry",
"crazy",
"helpful",
"mushy",
"odd",
"unsightly",
"adorable",
"important",
"inexpensive",
"cheap",
"expensive",
"fancy",
];
static COLOURS: &[&str] = &[
"red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black",
"orange",
];
static NOUNS: &[&str] = &[
"table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger",
"pizza", "mouse", "keyboard",
];

View file

@ -32,16 +32,20 @@ use dioxus::prelude::*;
struct NoKeysProps {
data: std::collections::HashMap<u32, String>,
}
static AntipatternNoKeys: Component<NoKeysProps> = |cx| {
fn AntipatternNoKeys(cx: Scope<NoKeysProps>) -> Element {
// WRONG: Make sure to add keys!
rsx!(cx, ul {
{cx.props.data.iter().map(|(k, v)| rsx!(li { "List item: {v}" }))}
cx.render(rsx! {
ul {
cx.props.data.iter().map(|(k, v)| rsx!(li { "List item: {v}" }))
}
});
// RIGHT: Like this:
rsx!(cx, ul {
{cx.props.data.iter().map(|(k, v)| rsx!(li { key: "{k}", "List item: {v}" }))}
cx.render(rsx! {
ul {
cx.props.data.iter().map(|(k, v)| rsx!(li { key: "{k}", "List item: {v}" }))
}
})
};
}
/// Antipattern: Deeply nested fragments
/// ------------------------------------
@ -54,9 +58,9 @@ static AntipatternNoKeys: Component<NoKeysProps> = |cx| {
///
/// Only Component and Fragment nodes are susceptible to this issue. Dioxus mitigates this with components by providing
/// an API for registering shared state without the ContextProvider pattern.
static AntipatternNestedFragments: Component = |cx| {
fn AntipatternNestedFragments(cx: Scope<()>) -> Element {
// Try to avoid heavily nesting fragments
rsx!(cx,
cx.render(rsx!(
Fragment {
Fragment {
Fragment {
@ -68,8 +72,8 @@ static AntipatternNestedFragments: Component = |cx| {
}
}
}
)
};
))
}
/// Antipattern: Using state after it's been updated
/// -----------------------------------------------
@ -82,13 +86,13 @@ static AntipatternNestedFragments: Component = |cx| {
/// However, calling set_state will *not* update the current version of state in the component. This should be easy to
/// recognize from the function signature, but Dioxus will not update the "live" version of state. Calling `set_state`
/// merely places a new value in the queue and schedules the component for a future update.
static AntipatternRelyingOnSetState: Component = |cx| {
fn AntipatternRelyingOnSetState(cx: Scope) -> Element {
let (state, set_state) = use_state(&cx, || "Hello world").classic();
set_state("New state");
// This will return false! `state` will *still* be "Hello world"
assert!(state == &"New state");
todo!()
};
}
/// Antipattern: Capitalization
/// ---------------------------
@ -120,16 +124,16 @@ static antipattern_component: Component = |cx| todo!();
struct MisuedHooksProps {
should_render_state: bool,
}
static AntipatternMisusedHooks: Component<MisuedHooksProps> = |cx| {
fn AntipatternMisusedHooks(cx: Scope<MisuedHooksProps>) -> Element {
if props.should_render_state {
// do not place a hook in the conditional!
// prefer to move it out of the conditional
let (state, set_state) = use_state(&cx, || "hello world").classic();
rsx!(cx, div { "{state}" })
cx.render(rsx!(div { "{state}" }))
} else {
rsx!(cx, div { "Not rendering state" })
cx.render(rsx!(div { "Not rendering state" }))
}
};
}
/// Antipattern: Downcasting refs and panicking
/// ------------------------------------------
@ -173,9 +177,9 @@ static _example: Component = |cx| todo!();
/// libraries.
static __example: Component = |cx| todo!();
pub static Example: Component = |cx| {
fn Example(cx: Scope) -> Element {
cx.render(rsx! {
AntipatternNoKeys { data: std::collections::HashMap::new() }
AntipatternNestedFragments {}
})
};
}

View file

@ -9,7 +9,7 @@
use dioxus::prelude::*;
pub static Example: Component = |cx| {
pub fn Example(cx: Scope) -> Element {
cx.render(rsx! {
div {
Greeting {
@ -18,20 +18,21 @@ pub static Example: Component = |cx| {
}
}
})
};
#[derive(PartialEq, Props)]
struct GreetingProps {
name: &'static str,
}
static Greeting: Component<GreetingProps> = |cx| {
#[derive(PartialEq, Props)]
struct GreetingProps<'a> {
name: &'static str,
children: Element<'a>,
}
pub fn Greeting<'a>(cx: Scope<'a, GreetingProps<'a>>) -> Element {
cx.render(rsx! {
div {
h1 { "Hello, {cx.props.name}!" }
p { "Welcome to the Dioxus framework" }
br {}
{cx.children()}
&cx.props.children
}
})
};
}

View file

@ -18,7 +18,7 @@
use dioxus::prelude::*;
pub static Example: Component = |cx| {
fn Example(cx: Scope) -> Element {
cx.render(rsx! {
div {
Banner {
@ -29,16 +29,19 @@ pub static Example: Component = |cx| {
}
}
})
};
}
pub static Banner: Component = |cx| {
#[derive(Props)]
struct BannerProps {
children: Element<'a>,
}
fn Banner(cx: Scope) -> Element {
cx.render(rsx! {
div {
h1 { "This is a great banner!" }
div { class: "content"
{cx.children()}
}
div { class: "content", &cx.props.children }
footer { "Wow, what a great footer" }
}
})
};
}

View file

@ -16,15 +16,15 @@ use dioxus::prelude::*;
pub struct MyProps {
should_show: bool,
}
pub static Example0: Component<MyProps> = |cx| {
pub fn Example0(cx: Scope<MyProps>) -> Element {
cx.render(rsx! {
div {
{cx.props.should_show.then(|| rsx!{
cx.props.should_show.then(|| rsx!{
h1 { "showing the title!" }
})}
})
}
})
};
}
// Convert a boolean conditional into an either/or
// Because rsx! is lazy (produces a closure), we cannot use it in two branch arms. To use it in matching/branching, we
@ -39,31 +39,29 @@ pub static Example0: Component<MyProps> = |cx| {
pub struct MyProps1 {
should_show: bool,
}
pub static Example1: Component<MyProps1> = |cx| {
pub fn Example1(cx: Scope<MyProps1>) -> Element {
cx.render(rsx! {
div {
// With matching
{match props.should_show {
match props.should_show {
true => cx.render(rsx!(div {"it is true!"})),
false => rsx!(cx, div {"it is false!"}),
}}
},
// or with just regular conditions
{if props.should_show {
if props.should_show {
rsx!(cx, div {"it is true!"})
} else {
rsx!(cx, div {"it is false!"})
}}
},
// or with optional chaining
{
props.should_show
props.should_show
.then(|| rsx!(cx, div {"it is true!"}))
.unwrap_or_else(|| rsx!(cx, div {"it is false!"}))
}
}
})
};
}
/// Matching can be expanded
@ -77,19 +75,19 @@ pub enum Color {
pub struct MyProps2 {
color: Color,
}
pub static Example2: Component<MyProps2> = |cx| {
pub fn Example2(cx: Scope<MyProps2>) -> Element {
cx.render(rsx! {
div {
{match props.color {
match props.color {
Color::Green => rsx!(cx, div {"it is Green!"}),
Color::Yellow => rsx!(cx, div {"it is Yellow!"}),
Color::Red => rsx!(cx, div {"it is Red!"}),
}}
}
}
})
};
}
pub static Example: Component = |cx| {
pub fn Example(cx: Scope<()>) -> Element {
let should_show = use_state(&cx, || false);
let mut color_index = use_state(&cx, || 0);
let color = match *color_index % 2 {
@ -101,11 +99,11 @@ pub static Example: Component = |cx| {
cx.render(rsx! {
div {
button {
onclick: move |_| should_show.set(!*should_show)
onclick: move |_| should_show.set(!*should_show),
"click to toggle showing the examples"
}
button {
onclick: move |_| color_index += 1
onclick: move |_| color_index += 1,
"click to for the enxt color"
}
Example0 { should_show: *should_show }
@ -113,4 +111,4 @@ pub static Example: Component = |cx| {
Example2 { color: color }
}
})
};
}

View file

@ -1,16 +1,16 @@
use dioxus::prelude::*;
fn main() {}
pub static Example: Component = |cx| {
pub fn Example(cx: Scope) -> Element {
cx.render(rsx! {
div {
}
})
};
}
// A controlled component:
static ControlledSelect: Component = |cx| {
pub fn ControlledSelect(cx: Scope) -> Element {
let value = use_state(&cx, || String::from("Grapefruit"));
cx.render(rsx! {
select { value: "{value}", onchange: move |evt| value.set(evt.value()),
@ -20,10 +20,10 @@ static ControlledSelect: Component = |cx| {
option { value: "Mango", "Mango"}
}
})
};
}
// TODO - how do uncontrolled things work?
static UncontrolledSelect: Component = |cx| {
pub fn UncontrolledSelect(cx: Scope) -> Element {
let value = use_state(&cx, || String::new());
cx.render(rsx! {
@ -34,4 +34,4 @@ static UncontrolledSelect: Component = |cx| {
option { value: "Mango", "Mango"}
}
})
};
}

View file

@ -11,7 +11,7 @@
use dioxus::prelude::*;
pub static Example: Component = |cx| {
pub fn Example(cx: Scope) -> Element {
cx.render(rsx! {
div {
custom_element {
@ -19,7 +19,7 @@ pub static Example: Component = |cx| {
}
}
})
};
}
mod dioxus_elements {
use std::fmt::Arguments;

View file

@ -5,4 +5,6 @@
use dioxus::prelude::*;
pub static Example: Component = |cx| cx.render(rsx! { Fragment {} });
pub fn Example(cx: Scope) -> Element {
None
}

View file

@ -23,20 +23,20 @@ fn main() {}
/// This is one way to go about error handling (just toss things away with unwrap).
/// However, if you get it wrong, the whole app will crash.
/// This is pretty flimsy.
static App: Component = |cx| {
pub fn App(cx: Scope) -> Element {
let data = get_data().unwrap();
cx.render(rsx!( div { "{data}" } ))
};
}
/// This is a pretty verbose way of error handling
/// However, it's still pretty good since we don't panic, just fail to render anything
static App1: Component = |cx| {
pub fn App1(cx: Scope) -> Element {
let data = match get_data() {
Some(data) => data,
None => return None,
};
cx.render(rsx!( div { "{data}" } ))
};
}
/// This is an even better form of error handling.
/// However, it _does_ make the component go blank, which might not be desirable.
@ -46,33 +46,37 @@ static App1: Component = |cx| {
/// a user is logged in.
///
/// Dioxus will throw an error in the console if the None-path is ever taken.
static App2: Component = |cx| {
pub fn App2(cx: Scope) -> Element {
let data = get_data()?;
cx.render(rsx!( div { "{data}" } ))
};
}
/// This is top-tier error handling since it displays a failure state.
///
/// However, the error is lacking in context.
static App3: Component = |cx| match get_data() {
Some(data) => cx.render(rsx!( div { "{data}" } )),
None => cx.render(rsx!( div { "Failed to load data :(" } )),
};
pub fn App3(cx: Scope) -> Element {
match get_data() {
Some(data) => cx.render(rsx!( div { "{data}" } )),
None => cx.render(rsx!( div { "Failed to load data :(" } )),
}
}
/// For errors that return results, it's possible to short-circuit the match-based error handling with `.ok()` which converts
/// a Result<T, V> into an Option<T> and lets you abort rendering by early-returning `None`
static App4: Component = |cx| {
pub fn App4(cx: Scope) -> Element {
let data = get_data_err().ok()?;
cx.render(rsx!( div { "{data}" } ))
};
}
/// This is great error handling since it displays a failure state... with context!
///
/// Hopefully you'll never need to display a screen like this. It's rather bad taste
static App5: Component = |cx| match get_data_err() {
Ok(data) => cx.render(rsx!( div { "{data}" } )),
Err(c) => cx.render(rsx!( div { "Failed to load data: {c}" } )),
};
pub fn App5(cx: Scope) -> Element {
match get_data_err() {
Ok(data) => cx.render(rsx!( div { "{data}" } )),
Err(c) => cx.render(rsx!( div { "Failed to load data: {c}" } )),
}
}
// this fetching function produces "nothing"
fn get_data() -> Option<String> {

View file

@ -10,17 +10,25 @@
use dioxus::prelude::*;
pub fn Example(cx: Scope) -> Element {
cx.render(rsx! {
App1 {}
App2 {}
App3 {}
})
}
// Returning multiple elements with rsx! or html!
static App1: Component = |cx| {
pub fn App1(cx: Scope) -> Element {
cx.render(rsx! {
h1 { }
h2 { }
h3 { }
})
};
}
// Using the Fragment component
static App2: Component = |cx| {
pub fn App2(cx: Scope) -> Element {
cx.render(rsx! {
Fragment {
div {}
@ -28,10 +36,10 @@ static App2: Component = |cx| {
"asd"
}
})
};
}
// Using the `fragment` method on the NodeFactory
static App3: Component = |cx| {
pub fn App3(cx: Scope) -> Element {
cx.render(LazyNodes::new(move |fac| {
fac.fragment_from_iter([
fac.text(format_args!("A")),
@ -40,12 +48,4 @@ static App3: Component = |cx| {
fac.text(format_args!("B")),
])
}))
};
pub static Example: Component = |cx| {
cx.render(rsx! {
App1 {}
App2 {}
App3 {}
})
};
}

View file

@ -19,7 +19,7 @@ h1 {color: blue;}
p {color: red;}
"#;
pub static Example: Component = |cx| {
pub fn Example(cx: Scope) -> Element {
cx.render(rsx! {
head { style { "{STYLE}" } }
body {
@ -27,4 +27,4 @@ pub static Example: Component = |cx| {
p {"This is a paragraph"}
}
})
};
}

View file

@ -10,7 +10,7 @@
use dioxus::prelude::*;
pub static Example: Component = |cx| {
pub fn Example(cx: Scope) -> Element {
cx.render(rsx! {
head {
style: { background_color: "powderblue" }
@ -24,15 +24,15 @@ pub static Example: Component = |cx| {
}
}
})
};
}
// .... technically the rsx! macro is slightly broken at the moment and allows styles not wrapped in style {}
// I haven't noticed any name collisions yet, and am tentatively leaving this behavior in..
// Don't rely on it.
static Example2: Component = |cx| {
pub fn Example2(cx: Scope) -> Element {
cx.render(rsx! {
div { color: "red"
"hello world!"
}
})
};
}

View file

@ -15,25 +15,20 @@ use dioxus::prelude::*;
pub static Example: Component = |cx| {
let example_data = use_state(&cx, || 0);
let v = (0..10).map(|f| {
rsx! {
li { onclick: move |_| example_data.set(f)
"ID: {f}"
ul {
(0..10).map(|k| rsx!{
li {
"Sub iterator: {f}.{k}"
}
})
}
}
}
});
cx.render(rsx! {
h3 {"Selected: {example_data}"}
ul {
{v}
(0..10).map(|f| rsx! {
li {
onclick: move |_| example_data.set(f),
"ID: {f}"
ul {
(0..10).map(|k| rsx!{
li { "Sub iterator: {f}.{k}" }
})
}
}
})
}
})
};

View file

@ -7,50 +7,49 @@
use dioxus::prelude::*;
pub static Example: Component = |cx| {
pub fn Example(cx: Scope) -> Element {
cx.render(rsx! {
ButtonList {}
NonUpdatingEvents {}
DisablePropagation {}
})
};
}
/// We can use `set_name` in multiple closures; the closures automatically *copy* the reference to set_name.
static ButtonList: Component = |cx| {
pub fn ButtonList(cx: Scope) -> Element {
let name = use_state(&cx, || "...?");
let names = ["jack", "jill", "john", "jane"]
.iter()
.map(move |n| rsx!(button { onclick: move |_| name.set(n), "{n}" }));
cx.render(rsx!(
div {
h1 { "Hello, {name}" }
{names}
["jack", "jill", "john", "jane"]
.iter()
.map(move |n| rsx!(button { onclick: move |_| name.set(n), "{n}" }))
}
))
};
}
/// This shows how listeners may be without a visible change in the display.
/// Check the console.
static NonUpdatingEvents: Component = |cx| {
rsx!(cx, div {
button {
onclick: move |_| log::info!("Did not cause any updates!")
"Click me to log!"
pub fn NonUpdatingEvents(cx: Scope) -> Element {
cx.render(rsx! {
div {
button {
onclick: move |_| log::info!("Did not cause any updates!"),
"Click me to log!"
}
}
})
};
}
static DisablePropagation: Component = |cx| {
rsx!(cx,
pub fn DisablePropagation(cx: Scope) -> Element {
cx.render(rsx! {
div {
onclick: move |_| log::info!("event propagated to the div!")
button {
onclick: move |evt| {
log::info!("Button will allow propagation");
}
onclick: move |evt| log::info!("Button will allow propagation"),
}
}
)
};
})
}

View file

@ -21,11 +21,11 @@ use dioxus::prelude::*;
// By default, components with no props are always memoized.
// A props of () is considered empty.
pub static Example: Component = |cx| {
pub fn Example(cx: Scope) -> Element {
cx.render(rsx! {
div { "100% memoized!" }
})
};
}
// These props do not borrow any content, and therefore can be safely memoized.
// However, the parent *must* create a new string on every render.
@ -35,11 +35,11 @@ pub struct MyProps1 {
name: String,
}
pub static Example1: Component<MyProps1> = |cx| {
pub fn Example1(cx: Scope<MyProps1>) -> Element {
cx.render(rsx! {
div { "100% memoized! {cx.props.name}" }
})
};
}
// These props do not borrow any content, and therefore can be safely memoized.
// In contrast with the `String` example, these props use `Rc<str>` which operates similar to strings in JavaScript.
@ -49,11 +49,11 @@ pub struct MyProps2 {
name: std::rc::Rc<str>,
}
pub static Example2: Component<MyProps2> = |cx| {
pub fn Example2(cx: Scope<MyProps2>) -> Element {
cx.render(rsx! {
div { "100% memoized! {cx.props.name}" }
})
};
}
// These props *do* borrow any content, and therefore cannot be safely memoized!.
#[derive(PartialEq, Props)]
@ -61,7 +61,7 @@ pub struct MyProps3<'a> {
name: &'a str,
}
// We need to manually specify a lifetime that ensures props and scope (the component's state) share the same lifetime.
// Using the `pub static Example: Component` pattern _will_ specify a lifetime, but that lifetime will be static which might
// Using the `pub fn Example(cx: Scope): Component` pattern _will_ specify a lifetime, but that lifetime will be static which might
// not exactly be what you want
fn Example3(cx: Scope<'a, MyProps3<'a>>) -> DomTree {
cx.render(rsx! {

View file

@ -1,12 +0,0 @@
use dioxus::prelude::*;
fn main() {}
pub static Example: Component = |cx| {
let p = 10;
cx.render(rsx! {
div {
}
})
};

View file

@ -1,10 +0,0 @@
use dioxus::prelude::*;
fn main() {}
pub static Example: Component = |cx| {
cx.render(rsx! {
div {
}
})
};

View file

@ -9,7 +9,7 @@
use dioxus::prelude::*;
pub static Example: Component = |cx| {
pub fn Example(cx: Scope) -> Element {
let props = MyProps {
count: 0,
live: true,
@ -18,22 +18,24 @@ pub static Example: Component = |cx| {
cx.render(rsx! {
Example1 { ..props, count: 10, div {"child"} }
})
};
}
#[derive(PartialEq, Props)]
pub struct MyProps {
#[derive(Props)]
pub struct MyProps<'a> {
count: u32,
live: bool,
name: &'static str,
children: Element<'a>,
}
pub static Example1: Component<MyProps> = |cx, MyProps { count, live, name }| {
pub fn Example1(cx: Scope<MyProps>) -> Element {
let MyProps { count, live, name } = cx.props;
cx.render(rsx! {
div {
h1 { "Hello, {name}"}
h3 {"Are we alive? {live}"}
p {"Count is {count}"}
{ cx.children() }
&cx.props.children
}
})
};
}

View file

@ -1,10 +0,0 @@
use dioxus::prelude::*;
fn main() {}
pub static Example: Component = |cx| {
cx.render(rsx! {
div {
}
})
};

View file

@ -14,7 +14,7 @@ struct DogApi {
}
const ENDPOINT: &str = "https://dog.ceo/api/breeds/image/random";
pub static Example: Component = |cx| {
pub fn Example(cx: Scope) -> Element {
let doggo = use_suspense(
cx,
|| surf::get(ENDPOINT).recv_json::<DogApi>(),
@ -35,4 +35,4 @@ pub static Example: Component = |cx| {
{doggo}
}
))
};
}

View file

@ -42,19 +42,19 @@ pub static Example: Component = |cx| {
div {
h1 {"count is {count}"}
button {
onclick: move |_| task.stop(),
"Stop counting"
onclick: move |_| task.stop()
}
button {
onclick: move |_| task.resume(),
"Start counting"
onclick: move |_| task.resume()
}
button {
"Switch counting direcion"
onclick: move |_| {
direction *= -1;
task.restart();
}
"Switch counting direcion"
}
}
})

View file

@ -1,9 +0,0 @@
use dioxus::prelude::*;
pub static Example: Component = |cx| {
cx.render(rsx! {
div {
}
})
};

View file

@ -1,25 +0,0 @@
use dioxus::prelude::*;
use dioxus::ssr;
pub static Example: Component = |cx| {
let as_string = use_state(&cx, || {
// Currently, SSR is only supported for whole VirtualDOMs
// This is an easy/low hanging fruit to improve upon
let mut dom = VirtualDom::new(SomeApp);
dom.rebuild();
ssr::render_vdom(&dom)
});
cx.render(rsx! {
div { "{as_string}" }
})
};
static SomeApp: Component = |cx| {
cx.render(rsx! {
div { style: {background_color: "blue"}
h1 {"Some amazing app or component"}
p {"Things are great"}
}
})
};

View file

@ -22,12 +22,12 @@
//! the coroutine was initiated. `use_state` always returns the same setter, so you don't need to worry about
fn main() {
dioxus::desktop::launch(App);
dioxus::desktop::launch(app);
}
use dioxus::prelude::*;
static App: Component = |cx| {
fn app(cx: Scope) -> Element {
let p1 = use_state(&cx, || 0);
let p2 = use_state(&cx, || 0);
@ -53,7 +53,7 @@ static App: Component = |cx| {
Horsey { pos: *p2, "horsey 2" }
}
})
};
}
#[derive(Props)]
struct HorseyProps<'a> {

View file

@ -20,12 +20,11 @@ pub struct Client {
}
fn app(cx: Scope) -> Element {
let clients = use_ref(&cx, || vec![] as Vec<Client>);
let scene = use_state(&cx, || Scene::ClientsList);
let firstname = use_state(&cx, String::new);
let lastname = use_state(&cx, String::new);
let description = use_state(&cx, String::new);
let clients = use_ref(&cx, || vec![] as Vec<Client>);
cx.render(rsx!(
body {

View file

@ -1,5 +0,0 @@
use dioxus_core::prelude::*;
use dioxus_core_macro::*;
use dioxus_html as dioxus_elements;
fn main() {}

View file

@ -1,109 +0,0 @@
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_core_macro::*;
use dioxus_hooks::*;
use dioxus_html as dioxus_elements;
fn main() {
dioxus_desktop::launch(App)
}
enum Scene {
ClientsList,
NewClientForm,
Settings,
}
#[derive(Clone, Debug, Default)]
pub struct Client {
pub first_name: String,
pub last_name: String,
pub description: String,
}
static App: Component = |cx| {
let mut scene = use_state(&cx, || Scene::ClientsList);
let clients = use_ref(&cx, || vec![] as Vec<Client>);
let mut firstname = use_state(&cx, String::new);
let mut lastname = use_state(&cx, String::new);
let mut description = use_state(&cx, String::new);
let scene = match scene.get() {
Scene::ClientsList => {
rsx!(cx, div { class: "crm"
h2 { "List of clients" margin_bottom: "10px" }
div { class: "clients" margin_left: "10px"
{clients.read().iter().map(|client| rsx!(
div { class: "client" style: "margin-bottom: 50px"
p { "First Name: {client.first_name}" }
p { "Last Name: {client.last_name}" }
p {"Description: {client.description}"}
})
)}
}
button { class: "pure-button pure-button-primary" onclick: move |_| scene.set(Scene::NewClientForm), "Add New" }
button { class: "pure-button" onclick: move |_| scene.set(Scene::Settings), "Settings" }
})
}
Scene::NewClientForm => {
let add_new = move |_| {
clients.write().push(Client {
description: (*description).clone(),
first_name: (*firstname).clone(),
last_name: (*lastname).clone(),
});
description.set(String::new());
firstname.set(String::new());
lastname.set(String::new());
};
rsx!(cx, div { class: "crm"
h2 {"Add new client" margin_bottom: "10px" }
form { class: "pure-form"
input { class: "new-client firstname" placeholder: "First name" value: "{firstname}"
oninput: move |evt| firstname.set(evt.value)
}
input { class: "new-client lastname" placeholder: "Last name" value: "{lastname}"
oninput: move |evt| lastname.set(evt.value)
}
textarea { class: "new-client description" placeholder: "Description" value: "{description}"
oninput: move |evt| description.set(evt.value)
}
}
button { class: "pure-button pure-button-primary", onclick: {add_new}, "Add New" }
button { class: "pure-button", onclick: move |_| scene.set(Scene::ClientsList), "Go Back" }
})
}
Scene::Settings => {
rsx!(cx, div {
h2 { "Settings" margin_bottom: "10px" }
button {
background: "rgb(202, 60, 60)"
class: "pure-button pure-button-primary"
onclick: move |_| {
clients.write().clear();
scene.set(Scene::ClientsList);
},
"Remove all clients"
}
button {
class: "pure-button pure-button-primary"
onclick: move |_| scene.set(Scene::ClientsList),
"Go Back"
}
})
}
};
rsx!(cx, body {
link {
rel: "stylesheet"
href: "https://unpkg.com/purecss@2.0.6/build/pure-min.css"
integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5"
crossorigin: "anonymous"
}
margin_left: "35%"
h1 {"Dioxus CRM Example"}
{scene}
})
};

View file

@ -1,18 +0,0 @@
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_core_macro::*;
use dioxus_html as dioxus_elements;
fn main() {
dioxus_desktop::launch(App);
}
static App: Component = |cx| {
cx.render(rsx!(
div {
"hello world!"
}
(0..10).map(|f| rsx!( div {"abc {f}"}))
))
};

View file

@ -1,379 +0,0 @@
html,
body {
margin: 0;
padding: 0;
}
button {
margin: 0;
padding: 0;
border: 0;
background: none;
font-size: 100%;
vertical-align: baseline;
font-family: inherit;
font-weight: inherit;
color: inherit;
-webkit-appearance: none;
appearance: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.4em;
background: #f5f5f5;
color: #4d4d4d;
min-width: 230px;
max-width: 550px;
margin: 0 auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-weight: 300;
}
:focus {
outline: 0;
}
.hidden {
display: none;
}
.todoapp {
background: #fff;
margin: 130px 0 40px 0;
position: relative;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.todoapp input::-webkit-input-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
.todoapp input::-moz-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
.todoapp input::input-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
.todoapp h1 {
position: absolute;
top: -155px;
width: 100%;
font-size: 100px;
font-weight: 100;
text-align: center;
color: rgba(175, 47, 47, 0.15);
-webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility;
}
.new-todo,
.edit {
position: relative;
margin: 0;
width: 100%;
font-size: 24px;
font-family: inherit;
font-weight: inherit;
line-height: 1.4em;
border: 0;
color: inherit;
padding: 6px;
border: 1px solid #999;
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.new-todo {
padding: 16px 16px 16px 60px;
border: none;
background: rgba(0, 0, 0, 0.003);
box-shadow: inset 0 -2px 1px rgba(0, 0, 0, 0.03);
}
.main {
position: relative;
z-index: 2;
border-top: 1px solid #e6e6e6;
}
.toggle-all {
text-align: center;
border: none;
/* Mobile Safari */
opacity: 0;
position: absolute;
}
.toggle-all+label {
width: 60px;
height: 34px;
font-size: 0;
position: absolute;
top: -52px;
left: -13px;
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
}
.toggle-all+label:before {
content: '';
font-size: 22px;
color: #e6e6e6;
padding: 10px 27px 10px 27px;
}
.toggle-all:checked+label:before {
color: #737373;
}
.todo-list {
margin: 0;
padding: 0;
list-style: none;
}
.todo-list li {
position: relative;
font-size: 24px;
border-bottom: 1px solid #ededed;
}
.todo-list li:last-child {
border-bottom: none;
}
.todo-list li.editing {
border-bottom: none;
padding: 0;
}
.todo-list li.editing .edit {
display: block;
width: 506px;
padding: 12px 16px;
margin: 0 0 0 43px;
}
.todo-list li.editing .view {
display: none;
}
.todo-list li .toggle {
text-align: center;
width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */
height: auto;
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
border: none;
/* Mobile Safari */
-webkit-appearance: none;
appearance: none;
}
.todo-list li .toggle {
opacity: 0;
}
.todo-list li .toggle+label {
/*
Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433
IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/
*/
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');
background-repeat: no-repeat;
background-position: center left;
}
.todo-list li .toggle:checked+label {
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E');
}
.todo-list li label {
word-break: break-all;
padding: 15px 15px 15px 60px;
display: block;
line-height: 1.2;
transition: color 0.4s;
}
.todo-list li.completed label {
color: #d9d9d9;
text-decoration: line-through;
}
.todo-list li .destroy {
display: none;
position: absolute;
top: 0;
right: 10px;
bottom: 0;
width: 40px;
height: 40px;
margin: auto 0;
font-size: 30px;
color: #cc9a9a;
margin-bottom: 11px;
transition: color 0.2s ease-out;
}
.todo-list li .destroy:hover {
color: #af5b5e;
}
.todo-list li .destroy:after {
content: '×';
}
.todo-list li:hover .destroy {
display: block;
}
.todo-list li .edit {
display: none;
}
.todo-list li.editing:last-child {
margin-bottom: -1px;
}
.footer {
color: #777;
padding: 10px 15px;
height: 20px;
text-align: center;
border-top: 1px solid #e6e6e6;
}
.footer:before {
content: '';
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 50px;
overflow: hidden;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
0 8px 0 -3px #f6f6f6,
0 9px 1px -3px rgba(0, 0, 0, 0.2),
0 16px 0 -6px #f6f6f6,
0 17px 2px -6px rgba(0, 0, 0, 0.2);
}
.todo-count {
float: left;
text-align: left;
}
.todo-count strong {
font-weight: 300;
}
.filters {
margin: 0;
padding: 0;
list-style: none;
position: absolute;
right: 0;
left: 0;
}
.filters li {
display: inline;
}
.filters li a {
color: inherit;
margin: 3px;
padding: 3px 7px;
text-decoration: none;
border: 1px solid transparent;
border-radius: 3px;
}
.filters li a:hover {
border-color: rgba(175, 47, 47, 0.1);
}
.filters li a.selected {
border-color: rgba(175, 47, 47, 0.2);
}
.clear-completed,
html .clear-completed:active {
float: right;
position: relative;
line-height: 20px;
text-decoration: none;
cursor: pointer;
}
.clear-completed:hover {
text-decoration: underline;
}
.info {
margin: 65px auto 0;
color: #bfbfbf;
font-size: 10px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
text-align: center;
}
.info p {
line-height: 1;
}
.info a {
color: inherit;
text-decoration: none;
font-weight: 400;
}
.info a:hover {
text-decoration: underline;
}
/*
Hack to remove background from Mobile Safari.
Can't use it globally since it destroys checkboxes in Firefox
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
.toggle-all,
.todo-list li .toggle {
background: none;
}
.todo-list li .toggle {
height: 40px;
}
}
@media (max-width: 430px) {
.footer {
height: 50px;
}
.filters {
bottom: 10px;
}
}

View file

@ -1,170 +0,0 @@
#![allow(non_upper_case_globals, non_snake_case)]
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_core_macro::*;
use dioxus_hooks::*;
use dioxus_html as dioxus_elements;
use std::collections::HashMap;
fn main() {
dioxus_desktop::launch(App)
}
#[derive(PartialEq)]
pub enum FilterState {
All,
Active,
Completed,
}
#[derive(Debug, PartialEq)]
pub struct TodoItem {
pub id: u32,
pub checked: bool,
pub contents: String,
}
pub type Todos = HashMap<u32, TodoItem>;
pub static App: Component = |cx| {
// Share our TodoList to the todos themselves
use_provide_state(cx, Todos::new);
// Save state for the draft, filter
let draft = use_state(&cx, || "".to_string());
let filter = use_state(&cx, || FilterState::All);
let mut todo_id = use_state(&cx, || 0);
// Consume the todos
let todos = use_shared_state::<Todos>(cx)?;
// Filter the todos based on the filter state
let mut filtered_todos = todos
.read()
.iter()
.filter(|(_, item)| match *filter {
FilterState::All => true,
FilterState::Active => !item.checked,
FilterState::Completed => item.checked,
})
.map(|f| *f.0)
.collect::<Vec<_>>();
filtered_todos.sort_unstable();
// Define the actions to manage the todolist
let mut submit_todo = move || {
if !draft.is_empty() {
todos.write().insert(
*todo_id,
TodoItem {
id: *todo_id,
checked: false,
contents: draft.get().clone(),
},
);
todo_id += 1;
draft.set("".to_string());
}
};
let clear_completed = move || {
todos.write().retain(|_, todo| todo.checked == false);
};
// Some assists in actually rendering the content
let show_clear_completed = todos.read().values().any(|todo| todo.checked);
let items_left = filtered_todos.len();
let item_text = match items_left {
1 => "item",
_ => "items",
};
cx.render(rsx!{
section { class: "todoapp"
style { {[include_str!("./todomvc.css")]} }
div {
header { class: "header"
h1 {"todos"}
input {
class: "new-todo"
placeholder: "What needs to be done?"
value: "{draft}"
autofocus: "true"
oninput: move |evt| draft.set(evt.value)
onkeydown: move |evt| {
if evt.key == "Enter" {
submit_todo();
}
}
}
}
ul { class: "todo-list",
filtered_todos.iter().map(|id| rsx!(TodoEntry { key: "{id}", id: *id }))
}
(!todos.read().is_empty()).then(|| rsx!(
footer { class: "footer",
span { class: "todo-count" strong {"{items_left} "} span {"{item_text} left"} }
ul { class: "filters"
li { class: "All", a { onclick: move |_| filter.set(FilterState::All), "All" }}
li { class: "Active", a { onclick: move |_| filter.set(FilterState::Active), "Active" }}
li { class: "Completed", a { onclick: move |_| filter.set(FilterState::Completed), "Completed" }}
}
show_clear_completed.then(|| rsx!(
button { class: "clear-completed", onclick: move |_| clear_completed(),
"Clear completed"
}
))
}
))
}
}
footer { class: "info"
p {"Double-click to edit a todo"}
p { "Created by ", a { "jkelleyrtp", href: "http://github.com/jkelleyrtp/" }}
p { "Part of ", a { "TodoMVC", href: "http://todomvc.com" }}
}
})
};
#[derive(PartialEq, Props)]
pub struct TodoEntryProps {
id: u32,
}
pub fn TodoEntry((cx, props): Scope<TodoEntryProps>) -> Element {
let todos = use_shared_state::<Todos>(cx)?;
let _todos = todos.read();
let todo = _todos.get(&cx.props.id)?;
let is_editing = use_state(&cx, || false);
let completed = if todo.checked { "completed" } else { "" };
cx.render(rsx!{
li { class: "{completed}"
div { class: "view"
input { class: "toggle" r#type: "checkbox" id: "cbg-{todo.id}" checked: "{todo.checked}"
onchange: move |evt| {
if let Some(todo) = todos.write().get_mut(&cx.props.id) {
todo.checked = evt.value.parse().unwrap()
}
}
}
label { r#for: "cbg-{todo.id}" pointer_events: "none"
"{todo.contents}"
}
{is_editing.then(|| rsx!{
input { value: "{todo.contents}"
oninput: move |evt| {
if let Some(todo) = todos.write().get_mut(&cx.props.id) {
todo.contents = evt.value
}
},
}
})}
}
}
})
}

View file

@ -2,7 +2,7 @@ use dioxus::prelude::*;
use rand::prelude::*;
fn main() {
dioxus::desktop::launch(App);
dioxus::desktop::launch(app);
}
#[derive(Clone, PartialEq)]
@ -29,7 +29,7 @@ impl Label {
}
}
static App: Component = |cx| {
fn app(cx: Scope) -> Element {
let items = use_ref(&cx, || vec![]);
let selected = use_state(&cx, || None);
@ -64,7 +64,7 @@ static App: Component = |cx| {
}
table {
tbody {
{items.read().iter().enumerate().map(|(id, item)| {
items.read().iter().enumerate().map(|(id, item)| {
let is_in_danger = if (*selected).map(|s| s == id).unwrap_or(false) {"danger"} else {""};
rsx!(tr { class: "{is_in_danger}",
td { class:"col-md-1" }
@ -79,13 +79,13 @@ static App: Component = |cx| {
}
td { class: "col-md-6" }
})
})}
})
}
}
span { class: "preloadicon glyphicon glyphicon-remove", aria_hidden: "true" }
}
})
};
}
#[derive(Props)]
struct ActionButtonProps<'a> {
@ -95,9 +95,17 @@ struct ActionButtonProps<'a> {
}
fn ActionButton<'a>(cx: Scope<'a, ActionButtonProps<'a>>) -> Element {
rsx!(cx, div { class: "col-sm-6 smallpad",
button { class:"btn btn-primary btn-block", r#type: "button", id: "{cx.props.id}", onclick: move |_| (cx.props.onclick)(),
"{cx.props.name}"
cx.render(rsx! {
div {
class: "col-sm-6 smallpad",
button {
class:"btn btn-primary btn-block",
r#type: "button",
id: "{cx.props.id}",
onclick: move |_| (cx.props.onclick)(),
"{cx.props.name}"
}
}
})
}

View file

@ -13,12 +13,13 @@ use dioxus::prelude::*;
use dioxus::ssr;
fn main() {
let vdom = VirtualDom::new(App);
let vdom = VirtualDom::new(app);
let content = ssr::render_vdom_cfg(&vdom, |f| f.pre_render(true));
dioxus::desktop::launch_cfg(App, |c| c.with_prerendered(content));
dioxus::desktop::launch_cfg(app, |c| c.with_prerendered(content));
}
static App: Component = |cx| {
fn app(cx: Scope) -> Element {
let val = use_state(&cx, || 0);
cx.render(rsx! {
@ -30,4 +31,4 @@ static App: Component = |cx| {
}
}
})
};
}

View file

@ -7,7 +7,7 @@ use std::sync::Arc;
use dioxus::{events::FormEvent, prelude::*};
fn main() {
dioxus::desktop::launch(App);
dioxus::desktop::launch(app);
}
const FIELDS: &[(&str, &str)] = &[
@ -37,7 +37,7 @@ const FIELDS: &[(&str, &str)] = &[
("week", ""), // degrades to text most of the time
];
static App: Component = |cx| {
fn app(cx: Scope) -> Element {
cx.render(rsx! {
div { margin_left: "30px",
@ -147,4 +147,4 @@ static App: Component = |cx| {
}
})
};
}

View file

@ -33,7 +33,7 @@ fn main() {
AppendChildren { many: 1 },
];
let app: Component = |cx| rsx!(cx, div { "some app" });
let app: Component = |cx| cx.render(rsx!(div { "some app" }));
dioxus_desktop::run(app, (), |c| c.with_edits(edits));
}

View file

@ -21,7 +21,6 @@ use dioxus::desktop::wry::application::dpi::LogicalSize;
use dioxus::events::*;
use dioxus::prelude::*;
const STYLE: &str = include_str!("./assets/calculator.css");
fn main() {
env_logger::init();
dioxus::desktop::launch_cfg(app, |cfg| {
@ -40,32 +39,36 @@ fn app(cx: Scope) -> Element {
let clear_text = if clear_display { "C" } else { "AC" };
let formatted = state.read().formatted_display();
rsx!(cx, div { id: "wrapper",
div { class: "app", style { "{STYLE}" }
div { class: "calculator", onkeypress: move |evt| state.write().handle_keydown(evt),
div { class: "calculator-display", "{formatted}"}
div { class: "calculator-keypad",
div { class: "input-keys",
div { class: "function-keys",
CalculatorKey { name: "key-clear", onclick: move |_| state.write().clear_display(), "{clear_text}" }
CalculatorKey { name: "key-sign", onclick: move |_| state.write().toggle_sign(), "±"}
CalculatorKey { name: "key-percent", onclick: move |_| state.write().toggle_percent(), "%"}
cx.render(rsx!{
div { id: "wrapper",
div {
class: "app",
style { [include_str!("./assets/calculator.css")] }
div { class: "calculator", onkeypress: move |evt| state.write().handle_keydown(evt),
div { class: "calculator-display", "{formatted}"}
div { class: "calculator-keypad",
div { class: "input-keys",
div { class: "function-keys",
CalculatorKey { name: "key-clear", onclick: move |_| state.write().clear_display(), "{clear_text}" }
CalculatorKey { name: "key-sign", onclick: move |_| state.write().toggle_sign(), "±"}
CalculatorKey { name: "key-percent", onclick: move |_| state.write().toggle_percent(), "%"}
}
div { class: "digit-keys",
CalculatorKey { name: "key-0", onclick: move |_| state.write().input_digit(0), "0" }
CalculatorKey { name: "key-dot", onclick: move |_| state.write().input_dot(), "" }
(1..10).map(move |k| rsx!{
CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| state.write().input_digit(k), "{k}" }
})
}
}
div { class: "digit-keys",
CalculatorKey { name: "key-0", onclick: move |_| state.write().input_digit(0), "0" }
CalculatorKey { name: "key-dot", onclick: move |_| state.write().input_dot(), "" }
(1..10).map(move |k| rsx!{
CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| state.write().input_digit(k), "{k}" }
})
div { class: "operator-keys",
CalculatorKey { name:"key-divide", onclick: move |_| state.write().set_operator(Operator::Div), "÷" }
CalculatorKey { name:"key-multiply", onclick: move |_| state.write().set_operator(Operator::Mul), "×" }
CalculatorKey { name:"key-subtract", onclick: move |_| state.write().set_operator(Operator::Sub), "" }
CalculatorKey { name:"key-add", onclick: move |_| state.write().set_operator(Operator::Add), "+" }
CalculatorKey { name:"key-equals", onclick: move |_| state.write().perform_operation(), "=" }
}
}
div { class: "operator-keys",
CalculatorKey { name:"key-divide", onclick: move |_| state.write().set_operator(Operator::Div), "÷" }
CalculatorKey { name:"key-multiply", onclick: move |_| state.write().set_operator(Operator::Mul), "×" }
CalculatorKey { name:"key-subtract", onclick: move |_| state.write().set_operator(Operator::Sub), "" }
CalculatorKey { name:"key-add", onclick: move |_| state.write().set_operator(Operator::Add), "+" }
CalculatorKey { name:"key-equals", onclick: move |_| state.write().perform_operation(), "=" }
}
}
}
}

View file

@ -8,10 +8,10 @@
use dioxus::prelude::*;
fn main() {
env_logger::init();
dioxus::desktop::launch(App);
dioxus::desktop::launch(app);
}
pub static App: Component = |cx| {
fn app(cx: Scope) -> Element {
let state = use_state(&cx, PlayerState::new);
cx.render(rsx!(
@ -26,7 +26,7 @@ pub static App: Component = |cx| {
}
}
))
};
}
enum PlayerAction {
Pause,

View file

@ -5,10 +5,10 @@
use dioxus::prelude::*;
fn main() {
dioxus::desktop::launch(App);
dioxus::desktop::launch(app);
}
static App: Component = |cx| {
fn app(cx: Scope) -> Element {
let mut count = use_state(&cx, || 0);
cx.render(rsx! {
@ -18,4 +18,4 @@ static App: Component = |cx| {
button { onclick: move |_| count -= 1, "Down low!" }
}
})
};
}

View file

@ -23,7 +23,7 @@ pub enum Route {
NotFound,
}
static App: Component = |cx| {
fn app(cx: Scope) -> Element {
let route = use_router(&cx, Route::parse);
cx.render(rsx! {
@ -42,7 +42,7 @@ static App: Component = |cx| {
}
footer {}
})
};
}
impl Route {
// Generate the appropriate route from the "tail" end of the URL

View file

@ -1,3 +1,6 @@
//! This example just flexes the ability to use arbitrary expressions within RSX.
//! It also proves that lifetimes work properly, especially when used with use_ref
use dioxus::prelude::*;
fn main() {

View file

@ -39,7 +39,7 @@
//! - Allow top-level fragments
//!
fn main() {
dioxus::desktop::launch(EXAMPLE);
dioxus::desktop::launch(app);
}
/// When trying to return "nothing" to Dioxus, you'll need to specify the type parameter or Rust will be sad.
@ -49,7 +49,7 @@ const NONE_ELEMENT: Option<()> = None;
use baller::Baller;
use dioxus::prelude::*;
pub static EXAMPLE: Component = |cx| {
fn app(cx: Scope) -> Element {
let formatting = "formatting!";
let formatting_tuple = ("a", "b");
let lazy_fmt = format_args!("lazily formatted text");
@ -192,10 +192,12 @@ pub static EXAMPLE: Component = |cx| {
[helper(&cx, "hello world!")]
}
})
};
}
fn helper<'a>(cx: &'a ScopeState, text: &str) -> Element<'a> {
rsx!(cx, p { "{text}" })
cx.render(rsx! {
p { "{text}" }
})
}
mod baller {
@ -227,5 +229,7 @@ pub fn Taller<'a>(cx: Scope<'a, TallerProps<'a>>) -> Element {
#[inline_props]
fn with_inline<'a>(cx: Scope<'a>, text: &'a str) -> Element {
rsx!(cx, p { "{text}" })
cx.render(rsx! {
p { "{text}" }
})
}

View file

@ -2,16 +2,16 @@ use dioxus::prelude::*;
use dioxus::ssr;
fn main() {
let mut vdom = VirtualDom::new(APP);
let mut vdom = VirtualDom::new(app);
let _ = vdom.rebuild();
println!("{}", ssr::render_vdom(&vdom));
}
static APP: Component = |cx| {
fn app(cx: Scope) -> Element {
cx.render(rsx!(
div {
h1 { "Title" }
p { "Body" }
}
))
};
}

View file

@ -3,8 +3,9 @@ use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_core_macro::*;
use dioxus_html as dioxus_elements;
fn main() {
let mut dom = VirtualDom::new(App);
let mut dom = VirtualDom::new(app);
dom.rebuild();
println!(
"{}",
@ -12,14 +13,14 @@ fn main() {
)
}
pub static App: Component = |cx| {
fn app(cx: Scope) -> Element {
cx.render(rsx!(
div {
class: "overflow-hidden"
ul {
{(0..10).map(|i| rsx!{ li { class: "flex flex-col", "entry: {i}"}})}
(0..10).map(|i| rsx!{ li { class: "flex flex-col", "entry: {i}"}})
}
"hello world!"
}
))
};
}

View file

@ -1,10 +0,0 @@
//! Example: realworld usage of hydration
//!
//!
use dioxus::virtual_dom::VirtualDom;
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_hooks::use_state;
use dioxus_html as dioxus_elements;
fn main() {}

View file

@ -1,298 +0,0 @@
<!-- a js-only interpreter for the dioxus patch stream :) -->
<!DOCTYPE html>
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<meta charset="UTF-8" />
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet" />
</head>
<body>
<div>hello dioxus</div>
</body>
<script>
class Interpreter {
constructor(root) {
this.stack = [root];
}
top() {
return this.stack[this.stack.length - 1];
}
pop() {
return this.stack.pop();
}
}
class OPTABLE {
// 0
SetText(edit, interp) {
interp.top(interp.stack).textContent = edit.text;
}
// 1
RemoveSelfAndNextSiblings(edit) {
const node = interp.pop();
let sibling = node.nextSibling;
while (sibling) {
const temp = sibling.nextSibling;
sibling.remove();
sibling = temp;
}
node.remove();
}
// 2
ReplaceWith(edit, interp) {
const newNode = interp.pop();
const oldNode = interp.pop();
oldNode.replaceWith(newNode);
interp.stack.push(newNode);
}
// 3
SetAttribute(edit, interp) {
const name = edit.name;
const value = edit.value;
const node = interp.top(interp.stack);
node.setAttribute(name, value);
// Some attributes are "volatile" and don't work through `setAttribute`.
if ((name === "value", interp)) {
node.value = value;
}
if ((name === "checked", interp)) {
node.checked = true;
}
if ((name === "selected", interp)) {
node.selected = true;
}
}
// 4
RemoveAttribute(edit, interp) {
const name = edit.name;
const node = interp.top(interp.stack);
node.removeAttribute(name);
// Some attributes are "volatile" and don't work through `removeAttribute`.
if ((name === "value", interp)) {
node.value = null;
}
if ((name === "checked", interp)) {
node.checked = false;
}
if ((name === "selected", interp)) {
node.selected = false;
}
}
// 5
PushReverseChild(edit, interp) {
const n = edit.n;
const parent = interp.top(interp.stack);
const children = parent.childNodes;
const child = children[children.length - n - 1];
interp.stack.push(child);
}
// 6
PopPushChild(edit, interp) {
const n = edit.n;
interp.pop();
const parent = interp.top(interp.stack);
const children = parent.childNodes;
const child = children[n];
interp.stack.push(child);
}
// 7
Pop(edit, interp) {
interp.pop();
}
// 8
AppendChild(edit, interp) {
console.log(interp.stack);
const child = interp.pop();
console.log(interp.stack);
interp.top().appendChild(child);
}
// 9
CreateTextNode(edit, interp) {
// interp.stack.push(document.createTextNode("asd"));
console.log(interp.stack);
interp.stack.push(document.createTextNode(edit.text));
console.log(interp.stack);
}
// 10
CreateElement(edit, interp) {
const tagName = edit.tag_name;
interp.stack.push(document.createElement(tagName));
}
// 11
NewEventListener(edit, interp) {
// todo!
const eventId = mem32[i++];
const eventType = interp.getCachedString(eventId);
const a = mem32[i++];
const b = mem32[i++];
const el = interp.top(interp.stack);
el.addEventListener(eventType, interp.eventHandler);
el[`dodrio-a-${eventType}`] = a;
el[`dodrio-b-${eventType}`] = b;
}
// 12
UpdateEventListener(edit, interp) {
// todo!
const eventId = mem32[i++];
const eventType = interp.getCachedString(eventId);
const el = interp.top(interp.stack);
el[`dodrio-a-${eventType}`] = mem32[i++];
el[`dodrio-b-${eventType}`] = mem32[i++];
}
// 13
RemoveEventListener(edit, interp) {
// todo!
const eventId = mem32[i++];
const eventType = interp.getCachedString(eventId);
const el = interp.top(interp.stack);
el.removeEventListener(eventType, interp.eventHandler);
}
// 14
AddCachedString(edit, interp) {
// todo!
const pointer = mem32[i++];
const length = mem32[i++];
const id = mem32[i++];
const str = string(mem8, pointer, length);
interp.addCachedString(str, id);
}
// 15
DropCachedString(edit, interp) {
// todo!
const id = mem32[i++];
interp.dropCachedString(id);
}
// 16
CreateElementNS(edit, interp) {
// const tagNameId = mem32[i++];
// const tagName = interp.getCachedString(tagNameId);
// const nsId = mem32[i++];
// const ns = interp.getCachedString(nsId);
interp.stack.push(document.createElementNS(edit.ns, edit.tag_name));
}
// 17
SaveChildrenToTemporaries(edit, interp) {
// let temp = mem32[i++];
// const start = mem32[i++];
// const end = mem32[i++];
let temp = edit.temp;
const start = edit.start;
const end = edit.end;
const parent = interp.top(interp.stack);
const children = parent.childNodes;
for (let i = start; i < end; i++, interp) {
interp.temporaries[temp++] = children[i];
}
}
// 18
PushChild(edit, interp) {
const parent = interp.top(interp.stack);
// const n = mem32[i++];
const n = edit.n;
const child = parent.childNodes[n];
interp.stack.push(child);
}
// 19
PushTemporary(edit, interp) {
// const temp = mem32[i++];
const temp = edit.temp;
interp.stack.push(interp.temporaries[temp]);
}
// 20
InsertBefore(edit, interp) {
const before = interp.pop();
const after = interp.pop();
after.parentNode.insertBefore(before, after);
interp.stack.push(before);
}
// 21
PopPushReverseChild(edit, interp) {
// const n = mem32[i++];
const n = edit.n;
interp.pop();
const parent = interp.top(interp.stack);
const children = parent.childNodes;
const child = children[children.length - n - 1];
interp.stack.push(child);
}
// 22
RemoveChild(edit, interp) {
// const n = mem32[i++];
const n = edit.n;
const parent = interp.top(interp.stack);
const child = parent.childNodes[n];
child.remove();
}
// 23
SetClass(edit, interp) {
// const classId = mem32[i++];
const className = edit.class_name;
interp.top(interp.stack).className = className;
}
// 24
SaveTemplate(edit, interp) {
const id = mem32[i++];
const template = interp.top(interp.stack);
interp.saveTemplate(id, template.cloneNode(true));
}
// 25
PushTemplate(edit, interp) {
const id = mem32[i++];
const template = interp.getTemplate(id);
interp.stack.push(template.cloneNode(true));
}
NewListener(edit, interp) {}
}
const op_table = new OPTABLE();
const interpreter = new Interpreter(window.document.body);
function EditListReceived(rawEditList) {
let editList = JSON.parse(rawEditList);
editList.forEach(function (edit, index) {
op_table[edit.type](edit, interpreter);
});
}
let socket = new WebSocket("ws://127.0.0.1:8080/session/itsworkinggg");
socket.onmessage = function(event) {
console.log(event.data);
EditListReceived(event.data);
}
// external.invoke("initiate");
</script>
</html>

View file

@ -1,61 +0,0 @@
//!
//!
//!
use dioxus::virtual_dom::VirtualDom;
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_core_macro::*;
use dioxus_hooks::use_state;
use dioxus_html as dioxus_elements;
fn main() {}
// use tide::{Request, Response};
// #[async_std::main]
// async fn main() -> Result<(), std::io::Error> {
// let mut app = tide::new();
// app.at("/:name").get(|req: Request<()>| async move {
// let initial_name: String = req
// .param("name")
// .map(|f| f.parse().unwrap_or("...?".to_string()))
// .unwrap_or("...?".to_string());
// let mut dom = VirtualDom::new_with_props(Example, ExampleProps { initial_name });
// dom.rebuild();
// Ok(Response::builder(200)
// .body(format!("{}", dioxus_ssr::render_vdom(&dom)))
// .content_type(tide::http::mime::HTML)
// .build())
// });
// println!("Server available at [http://127.0.0.1:8080/bill]");
// app.listen("127.0.0.1:8080").await?;
// Ok(())
// }
// #[derive(PartialEq, Props)]
// struct ExampleProps {
// initial_name: String,
// }
// static Example: Component<ExampleProps> = |cx| {
// let dispaly_name = use_state(&cx, move || props.initial_name.clone());
// cx.render(rsx! {
// div { class: "py-12 px-4 text-center w-full max-w-2xl mx-auto",
// span { class: "text-sm font-semibold"
// "Dioxus Example: Jack and Jill"
// }
// h2 { class: "text-5xl mt-2 mb-6 leading-tight font-semibold font-heading"
// "Hello, {dispaly_name}"
// }
// ul {
// {(0..10).map(|f| rsx!( li {"Element {f}"} ))}
// }
// }
// })
// };

View file

@ -24,7 +24,7 @@ fn main() {
.unwrap();
}
pub static App: Component = |cx| {
pub fn App(cx: Scope) -> Element {
cx.render(rsx!(
div { class: "overflow-hidden"
link { href:"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel:"stylesheet" }
@ -37,9 +37,9 @@ pub static App: Component = |cx| {
Hero {}
}
))
};
}
pub static Header: Component = |cx| {
pub fn Header(cx: Scope) -> Element {
cx.render(rsx! {
div {
header { class: "text-gray-400 bg-gray-900 body-font"
@ -63,9 +63,9 @@ pub static Header: Component = |cx| {
}
}
})
};
}
pub static Hero: Component = |cx| {
pub fn Hero(cx: Scope) -> Element {
//
cx.render(rsx! {
section{ class: "text-gray-400 bg-gray-900 body-font"
@ -102,8 +102,9 @@ pub static Hero: Component = |cx| {
}
}
})
};
pub static Entry: Component = |cx| {
}
pub fn Entry(cx: Scope) -> Element {
//
cx.render(rsx! {
section{ class: "text-gray-400 bg-gray-900 body-font"
@ -114,9 +115,9 @@ pub static Entry: Component = |cx| {
}
}
})
};
}
pub static StacksIcon: Component = |cx| {
pub fn StacksIcon(cx: Scope) -> Element {
cx.render(rsx!(
svg {
xmlns: "http://www.w3.org/2000/svg"
@ -130,8 +131,9 @@ pub static StacksIcon: Component = |cx| {
path { d: "M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"}
}
))
};
pub static RightArrowIcon: Component = |cx| {
}
pub fn RightArrowIcon(cx: Scope) -> Element {
cx.render(rsx!(
svg {
fill: "none"
@ -144,4 +146,4 @@ pub static RightArrowIcon: Component = |cx| {
path { d: "M5 12h14M12 5l7 7-7 7"}
}
))
};
}

View file

@ -14,7 +14,7 @@ fn main() {
const STYLE: &str = "body {overflow:hidden;}";
pub static App: Component = |cx| {
pub fn App(cx: Scope) -> Element {
cx.render(rsx!(
div { class: "overflow-hidden",
style { "{STYLE}" }
@ -28,9 +28,9 @@ pub static App: Component = |cx| {
Hero {}
}
))
};
}
pub static Header: Component = |cx| {
pub fn Header(cx: Scope) -> Element {
cx.render(rsx! {
div {
header { class: "text-gray-400 bg-gray-900 body-font",
@ -54,9 +54,9 @@ pub static Header: Component = |cx| {
}
}
})
};
}
pub static Hero: Component = |cx| {
pub fn Hero(cx: Scope) -> Element {
//
cx.render(rsx! {
section{ class: "text-gray-400 bg-gray-900 body-font",
@ -93,8 +93,8 @@ pub static Hero: Component = |cx| {
}
}
})
};
pub static Entry: Component = |cx| {
}
pub fn Entry(cx: Scope) -> Element {
//
cx.render(rsx! {
section{ class: "text-gray-400 bg-gray-900 body-font",
@ -105,9 +105,9 @@ pub static Entry: Component = |cx| {
}
}
})
};
}
pub static StacksIcon: Component = |cx| {
pub fn StacksIcon(cx: Scope) -> Element {
cx.render(rsx!(
svg {
// xmlns: "http://www.w3.org/2000/svg"
@ -121,8 +121,8 @@ pub static StacksIcon: Component = |cx| {
path { d: "M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"}
}
))
};
pub static RightArrowIcon: Component = |cx| {
}
pub fn RightArrowIcon(cx: Scope) -> Element {
cx.render(rsx!(
svg {
fill: "none",
@ -135,4 +135,4 @@ pub static RightArrowIcon: Component = |cx| {
path { d: "M5 12h14M12 5l7 7-7 7"}
}
))
};
}

View file

@ -12,13 +12,14 @@ fn main() {
fn app(cx: Scope) -> Element {
let count = use_state(&cx, || 0);
use_future(&cx, || {
for_async![count];
use_future(&cx, move || {
let count = UseState::for_async(&count);
// for_async![count];
async move {
loop {
tokio::time::sleep(Duration::from_millis(1000)).await;
count += 1;
}
// loop {
// tokio::time::sleep(Duration::from_millis(1000)).await;
// count += 1;
// }
}
});

View file

@ -4,7 +4,7 @@ use im_rc::HashMap;
use std::rc::Rc;
fn main() {
dioxus::desktop::launch(App);
dioxus::desktop::launch(app);
}
#[derive(PartialEq)]
@ -21,8 +21,7 @@ pub struct TodoItem {
pub contents: String,
}
const STYLE: &str = include_str!("./assets/todomvc.css");
const App: Component = |cx| {
fn app(cx: Scope) -> Element {
let draft = use_state(&cx, || "".to_string());
let todos = use_state(&cx, || HashMap::<u32, Rc<TodoItem>>::new());
let filter = use_state(&cx, || FilterState::All);
@ -48,40 +47,43 @@ const App: Component = |cx| {
_ => "items",
};
rsx!(cx, div { id: "app",
style {"{STYLE}"}
div {
header { class: "header",
h1 {"todos"}
input {
class: "new-todo",
placeholder: "What needs to be done?",
value: "{draft}",
oninput: move |evt| draft.set(evt.value.clone()),
cx.render(rsx!(
div { id: "app",
style { [include_str!("./assets/todomvc.css")] }
div {
header {
class: "header",
h1 { "todos" }
input {
class: "new-todo",
placeholder: "What needs to be done?",
value: "{draft}",
oninput: move |evt| draft.set(evt.value.clone()),
}
}
todolist,
(!todos.is_empty()).then(|| rsx!(
footer {
span {
strong {"{items_left}"}
span {"{item_text} left"}
}
ul { class: "filters",
li { class: "All", a { href: "", onclick: move |_| filter.set(FilterState::All), "All" }}
li { class: "Active", a { href: "active", onclick: move |_| filter.set(FilterState::Active), "Active" }}
li { class: "Completed", a { href: "completed", onclick: move |_| filter.set(FilterState::Completed), "Completed" }}
}
}
))
}
footer { class: "info",
p {"Double-click to edit a todo"}
p { "Created by ", a { href: "http://github.com/jkelleyrtp/", "jkelleyrtp" }}
p { "Part of ", a { href: "http://todomvc.com", "TodoMVC" }}
}
todolist,
(!todos.is_empty()).then(|| rsx!(
footer {
span {
strong {"{items_left}"}
span {"{item_text} left"}
}
ul { class: "filters",
li { class: "All", a { href: "", onclick: move |_| filter.set(FilterState::All), "All" }}
li { class: "Active", a { href: "active", onclick: move |_| filter.set(FilterState::Active), "Active" }}
li { class: "Completed", a { href: "completed", onclick: move |_| filter.set(FilterState::Completed), "Completed" }}
}
}
))
}
footer { class: "info",
p {"Double-click to edit a todo"}
p { "Created by ", a { href: "http://github.com/jkelleyrtp/", "jkelleyrtp" }}
p { "Part of ", a { href: "http://todomvc.com", "TodoMVC" }}
}
})
};
))
}
#[derive(PartialEq, Props)]
pub struct TodoEntryProps {
@ -93,18 +95,20 @@ pub fn TodoEntry(cx: Scope<TodoEntryProps>) -> Element {
let contents = use_state(&cx, || String::from(""));
let todo = &cx.props.todo;
rsx!(cx, li {
"{todo.id}"
input {
class: "toggle",
r#type: "checkbox",
"{todo.checked}"
}
{is_editing.then(|| rsx!{
cx.render(rsx! {
li {
"{todo.id}"
input {
value: "{contents}",
oninput: move |evt| contents.set(evt.value.clone())
class: "toggle",
r#type: "checkbox",
"{todo.checked}"
}
})}
is_editing.then(|| rsx!{
input {
value: "{contents}",
oninput: move |evt| contents.set(evt.value.clone())
}
})
}
})
}

View file

@ -7,22 +7,23 @@
use dioxus::prelude::*;
fn main() {
dioxus::desktop::launch(App);
dioxus::desktop::launch(app);
}
const ENDPOINT: &str = "https://api.openweathermap.org/data/2.5/weather";
static App: Component = |cx| {
fn app(cx: Scope) -> Element {
//
let body = use_suspense(
cx,
&cx,
|| async {
let content = reqwest::get(ENDPOINT)
.await
.unwrap()
.json::<serde_json::Value>()
.await
.unwrap();
todo!()
// let content = reqwest::get(ENDPOINT)
// .await
// .unwrap()
// .json::<serde_json::Value>()
// .await
// .unwrap();
},
|props| {
//
@ -35,13 +36,12 @@ static App: Component = |cx| {
{body}
}
})
};
}
#[derive(PartialEq, Props)]
struct WeatherProps {}
static WeatherDisplay: Component<WeatherProps> = |cx| {
//
fn WeatherDisplay(cx: Scope<WeatherProps>) -> Element {
cx.render(rsx!(
div { class: "flex items-center justify-center flex-col",
div { class: "flex items-center justify-center",
@ -64,4 +64,4 @@ static WeatherDisplay: Component<WeatherProps> = |cx| {
}
}
))
};
}

View file

@ -13,10 +13,10 @@
use dioxus::prelude::*;
fn main() {
dioxus::desktop::launch(App);
dioxus::desktop::launch(app);
}
static App: Component = |cx| {
fn app(cx: Scope) -> Element {
let mut rng = SmallRng::from_entropy();
cx.render(rsx! {
@ -29,7 +29,7 @@ static App: Component = |cx| {
}
}
})
};
}
#[derive(PartialEq, Props)]
struct RowProps {

View file

@ -1,3 +1,7 @@
//! XSS Safety
//!
//! This example proves that Dioxus is safe from XSS attacks.
use dioxus::prelude::*;
fn main() {
@ -9,19 +13,11 @@ fn app(cx: Scope) -> Element {
cx.render(rsx! {
div {
"hello world!"
h1 { "{contents}" }
h3 { [contents.as_str()] }
h3 { "{contents}" }
input {
value: "{contents}",
oninput: move |e| {
contents.set(e.value.clone());
eprintln!("asd");
},
"type": "text",
r#type: "text",
oninput: move |e| contents.set(e.value.clone()),
}
}
})

View file

@ -99,8 +99,8 @@ impl ToTokens for InlinePropsBody {
};
out_tokens.append_all(quote! {
#[allow(non_camel_case)]
#modifiers
#[allow(non_camel_case_types)]
#vis struct #struct_name #generics {
#(#fields),*
}

View file

@ -1,277 +0,0 @@
use dioxus_core::prelude::*;
use std::{
cell::{Cell, Ref, RefCell, RefMut},
fmt::{Debug, Display},
rc::Rc,
};
/// Store state between component renders!
///
/// ## Dioxus equivalent of useState, designed for Rust
///
/// The Dioxus version of `useState` for state management inside components. It allows you to ergonomically store and
/// modify state between component renders. When the state is updated, the component will re-render.
///
/// Dioxus' use_state basically wraps a RefCell with helper methods and integrates it with the VirtualDOM update system.
///
/// [`use_state`] exposes a few helper methods to modify the underlying state:
/// - `.set(new)` allows you to override the "work in progress" value with a new value
/// - `.get_mut()` allows you to modify the WIP value
/// - `.get_wip()` allows you to access the WIP value
/// - `.deref()` provides the previous value (often done implicitly, though a manual dereference with `*` might be required)
///
/// Additionally, a ton of std::ops traits are implemented for the `UseState` wrapper, meaning any mutative type operations
/// will automatically be called on the WIP value.
///
/// ## Combinators
///
/// On top of the methods to set/get state, `use_state` also supports fancy combinators to extend its functionality:
/// - `.classic()` and `.split()` convert the hook into the classic React-style hook
/// ```rust
/// let (state, set_state) = use_state(&cx, || 10).split()
/// ```
///
///
/// Usage:
///
/// ```ignore
/// const Example: Component = |cx| {
/// let counter = use_state(&cx, || 0);
///
/// cx.render(rsx! {
/// div {
/// h1 { "Counter: {counter}" }
/// button { onclick: move |_| counter += 1, "Increment" }
/// button { onclick: move |_| counter -= 1, "Decrement" }
/// }
/// ))
/// }
/// ```
pub fn use_state<'a, T: 'static>(
cx: &'a ScopeState,
initial_state_fn: impl FnOnce() -> T,
) -> UseState<'a, T> {
let hook = cx.use_hook(move |_| {
let first_val = initial_state_fn();
UseStateInner {
current_val: Rc::new(first_val),
update_callback: cx.schedule_update(),
wip: Rc::new(RefCell::new(None)),
update_scheuled: Cell::new(false),
}
});
hook.update_scheuled.set(false);
let mut new_val = hook.wip.borrow_mut();
if new_val.is_some() {
// if there's only one reference (weak or otherwise), we can just swap the values
if let Some(val) = Rc::get_mut(&mut hook.current_val) {
*val = new_val.take().unwrap();
} else {
hook.current_val = Rc::new(new_val.take().unwrap());
}
}
UseState { inner: &*hook }
}
struct UseStateInner<T: 'static> {
current_val: Rc<T>,
update_scheuled: Cell<bool>,
update_callback: Rc<dyn Fn()>,
wip: Rc<RefCell<Option<T>>>,
}
pub struct UseState<'a, T: 'static> {
inner: &'a UseStateInner<T>,
}
impl<T> Copy for UseState<'_, T> {}
impl<'a, T> Clone for UseState<'a, T>
where
T: 'static,
{
fn clone(&self) -> Self {
UseState { inner: self.inner }
}
}
impl<T: Debug> Debug for UseState<'_, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.inner.current_val)
}
}
impl<'a, T: 'static> UseState<'a, T> {
/// Tell the Dioxus Scheduler that we need to be processed
pub fn needs_update(&self) {
if !self.inner.update_scheuled.get() {
self.inner.update_scheuled.set(true);
(self.inner.update_callback)();
}
}
pub fn set(&self, new_val: T) {
*self.inner.wip.borrow_mut() = Some(new_val);
self.needs_update();
}
pub fn get(&self) -> &'a T {
&self.inner.current_val
}
pub fn get_rc(&self) -> &'a Rc<T> {
&self.inner.current_val
}
/// Get the current status of the work-in-progress data
pub fn get_wip(&self) -> Ref<Option<T>> {
self.inner.wip.borrow()
}
/// Get the current status of the work-in-progress data
pub fn get_wip_mut(&self) -> RefMut<Option<T>> {
self.inner.wip.borrow_mut()
}
pub fn classic(self) -> (&'a T, Rc<dyn Fn(T)>) {
(&self.inner.current_val, self.setter())
}
pub fn setter(&self) -> Rc<dyn Fn(T)> {
let slot = self.inner.wip.clone();
Rc::new(move |new| {
*slot.borrow_mut() = Some(new);
})
}
pub fn for_async(self) -> UseState<'static, T> {
todo!()
}
pub fn wtih(self, f: impl FnOnce(&mut T)) {
let mut val = self.inner.wip.borrow_mut();
if let Some(inner) = val.as_mut() {
f(inner);
}
}
}
impl<'a, T: 'static + ToOwned<Owned = T>> UseState<'a, T> {
/// Gain mutable access to the new value via [`RefMut`].
///
/// If `modify` is called, then the component will re-render.
///
/// This method is only available when the value is a `ToOwned` type.
///
/// Mutable access is derived by calling "ToOwned" (IE cloning) on the current value.
///
/// To get a reference to the current value, use `.get()`
pub fn modify(self) -> RefMut<'a, T> {
// make sure we get processed
self.needs_update();
// Bring out the new value, cloning if it we need to
// "get_mut" is locked behind "ToOwned" to make it explicit that cloning occurs to use this
RefMut::map(self.inner.wip.borrow_mut(), |slot| {
if slot.is_none() {
*slot = Some(self.inner.current_val.as_ref().to_owned());
}
slot.as_mut().unwrap()
})
}
pub fn inner(self) -> T {
self.inner.current_val.as_ref().to_owned()
}
}
impl<'a, T> std::ops::Deref for UseState<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.get()
}
}
// enable displaty for the handle
impl<'a, T: 'static + Display> std::fmt::Display for UseState<'a, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.inner.current_val)
}
}
impl<'a, V, T: PartialEq<V>> PartialEq<V> for UseState<'a, T> {
fn eq(&self, other: &V) -> bool {
self.get() == other
}
}
impl<'a, O, T: std::ops::Not<Output = O> + Copy> std::ops::Not for UseState<'a, T> {
type Output = O;
fn not(self) -> Self::Output {
!*self.get()
}
}
/*
Convenience methods for UseState.
Note!
This is not comprehensive.
This is *just* meant to make common operations easier.
*/
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
impl<'a, T: Copy + Add<T, Output = T>> Add<T> for UseState<'a, T> {
type Output = T;
fn add(self, rhs: T) -> Self::Output {
self.inner.current_val.add(rhs)
}
}
impl<'a, T: Copy + Add<T, Output = T>> AddAssign<T> for UseState<'a, T> {
fn add_assign(&mut self, rhs: T) {
self.set(self.inner.current_val.add(rhs));
}
}
impl<'a, T: Copy + Sub<T, Output = T>> Sub<T> for UseState<'a, T> {
type Output = T;
fn sub(self, rhs: T) -> Self::Output {
self.inner.current_val.sub(rhs)
}
}
impl<'a, T: Copy + Sub<T, Output = T>> SubAssign<T> for UseState<'a, T> {
fn sub_assign(&mut self, rhs: T) {
self.set(self.inner.current_val.sub(rhs));
}
}
/// MUL
impl<'a, T: Copy + Mul<T, Output = T>> Mul<T> for UseState<'a, T> {
type Output = T;
fn mul(self, rhs: T) -> Self::Output {
self.inner.current_val.mul(rhs)
}
}
impl<'a, T: Copy + Mul<T, Output = T>> MulAssign<T> for UseState<'a, T> {
fn mul_assign(&mut self, rhs: T) {
self.set(self.inner.current_val.mul(rhs));
}
}
/// DIV
impl<'a, T: Copy + Div<T, Output = T>> Div<T> for UseState<'a, T> {
type Output = T;
fn div(self, rhs: T) -> Self::Output {
self.inner.current_val.div(rhs)
}
}
impl<'a, T: Copy + Div<T, Output = T>> DivAssign<T> for UseState<'a, T> {
fn div_assign(&mut self, rhs: T) {
self.set(self.inner.current_val.div(rhs));
}
}

View file

@ -0,0 +1,211 @@
use super::owned::UseStateOwned;
use std::{
cell::{Ref, RefMut},
fmt::{Debug, Display},
rc::Rc,
};
pub struct UseState<'a, T: 'static>(pub(crate) &'a UseStateOwned<T>);
impl<T> Copy for UseState<'_, T> {}
impl<'a, T: 'static> Clone for UseState<'a, T> {
fn clone(&self) -> Self {
UseState(self.0)
}
}
impl<T: Debug> Debug for UseState<'_, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.0.current_val)
}
}
impl<'a, T: 'static> UseState<'a, T> {
/// Tell the Dioxus Scheduler that we need to be processed
pub fn needs_update(&self) {
if !self.0.update_scheuled.get() {
self.0.update_scheuled.set(true);
(self.0.update_callback)();
}
}
pub fn set(&self, new_val: T) {
*self.0.wip.borrow_mut() = Some(new_val);
self.needs_update();
}
pub fn get(&self) -> &'a T {
&self.0.current_val
}
pub fn get_rc(&self) -> &'a Rc<T> {
&self.0.current_val
}
/// Get the current status of the work-in-progress data
pub fn get_wip(&self) -> Ref<Option<T>> {
self.0.wip.borrow()
}
/// Get the current status of the work-in-progress data
pub fn get_wip_mut(&self) -> RefMut<Option<T>> {
self.0.wip.borrow_mut()
}
pub fn classic(self) -> (&'a T, Rc<dyn Fn(T)>) {
(&self.0.current_val, self.setter())
}
pub fn setter(&self) -> Rc<dyn Fn(T)> {
let slot = self.0.wip.clone();
Rc::new(move |new| *slot.borrow_mut() = Some(new))
}
pub fn wtih(&self, f: impl FnOnce(&mut T)) {
let mut val = self.0.wip.borrow_mut();
if let Some(inner) = val.as_mut() {
f(inner);
}
}
pub fn for_async(&self) -> UseStateOwned<T> {
let UseStateOwned {
current_val,
wip,
update_callback,
update_scheuled,
} = self.0;
UseStateOwned {
current_val: current_val.clone(),
wip: wip.clone(),
update_callback: update_callback.clone(),
update_scheuled: update_scheuled.clone(),
}
}
}
impl<'a, T: 'static + ToOwned<Owned = T>> UseState<'a, T> {
/// Gain mutable access to the new value via [`RefMut`].
///
/// If `modify` is called, then the component will re-render.
///
/// This method is only available when the value is a `ToOwned` type.
///
/// Mutable access is derived by calling "ToOwned" (IE cloning) on the current value.
///
/// To get a reference to the current value, use `.get()`
pub fn modify(self) -> RefMut<'a, T> {
// make sure we get processed
self.needs_update();
// Bring out the new value, cloning if it we need to
// "get_mut" is locked behind "ToOwned" to make it explicit that cloning occurs to use this
RefMut::map(self.0.wip.borrow_mut(), |slot| {
if slot.is_none() {
*slot = Some(self.0.current_val.as_ref().to_owned());
}
slot.as_mut().unwrap()
})
}
pub fn inner(self) -> T {
self.0.current_val.as_ref().to_owned()
}
}
impl<'a, T> std::ops::Deref for UseState<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.get()
}
}
// enable displaty for the handle
impl<'a, T: 'static + Display> std::fmt::Display for UseState<'a, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.current_val)
}
}
impl<'a, V, T: PartialEq<V>> PartialEq<V> for UseState<'a, T> {
fn eq(&self, other: &V) -> bool {
self.get() == other
}
}
impl<'a, O, T: std::ops::Not<Output = O> + Copy> std::ops::Not for UseState<'a, T> {
type Output = O;
fn not(self) -> Self::Output {
!*self.get()
}
}
/*
Convenience methods for UseState.
Note!
This is not comprehensive.
This is *just* meant to make common operations easier.
*/
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
impl<'a, T: Copy + Add<T, Output = T>> Add<T> for UseState<'a, T> {
type Output = T;
fn add(self, rhs: T) -> Self::Output {
self.0.current_val.add(rhs)
}
}
impl<'a, T: Copy + Add<T, Output = T>> AddAssign<T> for UseState<'a, T> {
fn add_assign(&mut self, rhs: T) {
self.set(self.0.current_val.add(rhs));
}
}
/// Sub
impl<'a, T: Copy + Sub<T, Output = T>> Sub<T> for UseState<'a, T> {
type Output = T;
fn sub(self, rhs: T) -> Self::Output {
self.0.current_val.sub(rhs)
}
}
impl<'a, T: Copy + Sub<T, Output = T>> SubAssign<T> for UseState<'a, T> {
fn sub_assign(&mut self, rhs: T) {
self.set(self.0.current_val.sub(rhs));
}
}
/// MUL
impl<'a, T: Copy + Mul<T, Output = T>> Mul<T> for UseState<'a, T> {
type Output = T;
fn mul(self, rhs: T) -> Self::Output {
self.0.current_val.mul(rhs)
}
}
impl<'a, T: Copy + Mul<T, Output = T>> MulAssign<T> for UseState<'a, T> {
fn mul_assign(&mut self, rhs: T) {
self.set(self.0.current_val.mul(rhs));
}
}
/// DIV
impl<'a, T: Copy + Div<T, Output = T>> Div<T> for UseState<'a, T> {
type Output = T;
fn div(self, rhs: T) -> Self::Output {
self.0.current_val.div(rhs)
}
}
impl<'a, T: Copy + Div<T, Output = T>> DivAssign<T> for UseState<'a, T> {
fn div_assign(&mut self, rhs: T) {
self.set(self.0.current_val.div(rhs));
}
}

View file

@ -0,0 +1,78 @@
mod handle;
mod owned;
pub use handle::*;
pub use owned::*;
use dioxus_core::prelude::*;
use std::{
cell::{Cell, RefCell},
rc::Rc,
};
/// Store state between component renders!
///
/// ## Dioxus equivalent of useState, designed for Rust
///
/// The Dioxus version of `useState` for state management inside components. It allows you to ergonomically store and
/// modify state between component renders. When the state is updated, the component will re-render.
///
/// Dioxus' use_state basically wraps a RefCell with helper methods and integrates it with the VirtualDOM update system.
///
/// [`use_state`] exposes a few helper methods to modify the underlying state:
/// - `.set(new)` allows you to override the "work in progress" value with a new value
/// - `.get_mut()` allows you to modify the WIP value
/// - `.get_wip()` allows you to access the WIP value
/// - `.deref()` provides the previous value (often done implicitly, though a manual dereference with `*` might be required)
///
/// Additionally, a ton of std::ops traits are implemented for the `UseState` wrapper, meaning any mutative type operations
/// will automatically be called on the WIP value.
///
/// ## Combinators
///
/// On top of the methods to set/get state, `use_state` also supports fancy combinators to extend its functionality:
/// - `.classic()` and `.split()` convert the hook into the classic React-style hook
/// ```rust
/// let (state, set_state) = use_state(&cx, || 10).split()
/// ```
///
///
/// Usage:
///
/// ```ignore
/// const Example: Component = |cx| {
/// let counter = use_state(&cx, || 0);
///
/// cx.render(rsx! {
/// div {
/// h1 { "Counter: {counter}" }
/// button { onclick: move |_| counter += 1, "Increment" }
/// button { onclick: move |_| counter -= 1, "Decrement" }
/// }
/// ))
/// }
/// ```
pub fn use_state<'a, T: 'static>(
cx: &'a ScopeState,
initial_state_fn: impl FnOnce() -> T,
) -> UseState<'a, T> {
let hook = cx.use_hook(move |_| UseStateOwned {
current_val: Rc::new(initial_state_fn()),
update_callback: cx.schedule_update(),
wip: Rc::new(RefCell::new(None)),
update_scheuled: Cell::new(false),
});
hook.update_scheuled.set(false);
let mut new_val = hook.wip.borrow_mut();
if new_val.is_some() {
// if there's only one reference (weak or otherwise), we can just swap the values
if let Some(val) = Rc::get_mut(&mut hook.current_val) {
*val = new_val.take().unwrap();
} else {
hook.current_val = Rc::new(new_val.take().unwrap());
}
}
UseState(hook)
}

View file

@ -0,0 +1,102 @@
use std::{
cell::{Cell, Ref, RefCell, RefMut},
fmt::{Debug, Display},
rc::Rc,
};
pub struct UseStateOwned<T: 'static> {
// this will always be outdated
pub(crate) current_val: Rc<T>,
pub(crate) wip: Rc<RefCell<Option<T>>>,
pub(crate) update_callback: Rc<dyn Fn()>,
pub(crate) update_scheuled: Cell<bool>,
}
impl<T> UseStateOwned<T> {
pub fn get(&self) -> Ref<T> {
Ref::map(self.wip.borrow(), |x| x.as_ref().unwrap())
}
pub fn set(&self, new_val: T) {
*self.wip.borrow_mut() = Some(new_val);
(self.update_callback)();
}
pub fn modify(&self) -> RefMut<T> {
RefMut::map(self.wip.borrow_mut(), |x| x.as_mut().unwrap())
}
pub fn with(&self, f: impl FnOnce(&mut T)) {
//
}
}
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
impl<T: Debug> Debug for UseStateOwned<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.current_val)
}
}
// enable displaty for the handle
impl<'a, T: 'static + Display> std::fmt::Display for UseStateOwned<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.current_val)
}
}
impl<'a, T: Copy + Add<T, Output = T>> Add<T> for UseStateOwned<T> {
type Output = T;
fn add(self, rhs: T) -> Self::Output {
self.current_val.add(rhs)
}
}
impl<'a, T: Copy + Add<T, Output = T>> AddAssign<T> for UseStateOwned<T> {
fn add_assign(&mut self, rhs: T) {
self.set(self.current_val.add(rhs));
}
}
/// Sub
impl<'a, T: Copy + Sub<T, Output = T>> Sub<T> for UseStateOwned<T> {
type Output = T;
fn sub(self, rhs: T) -> Self::Output {
self.current_val.sub(rhs)
}
}
impl<'a, T: Copy + Sub<T, Output = T>> SubAssign<T> for UseStateOwned<T> {
fn sub_assign(&mut self, rhs: T) {
self.set(self.current_val.sub(rhs));
}
}
/// MUL
impl<'a, T: Copy + Mul<T, Output = T>> Mul<T> for UseStateOwned<T> {
type Output = T;
fn mul(self, rhs: T) -> Self::Output {
self.current_val.mul(rhs)
}
}
impl<'a, T: Copy + Mul<T, Output = T>> MulAssign<T> for UseStateOwned<T> {
fn mul_assign(&mut self, rhs: T) {
self.set(self.current_val.mul(rhs));
}
}
/// DIV
impl<'a, T: Copy + Div<T, Output = T>> Div<T> for UseStateOwned<T> {
type Output = T;
fn div(self, rhs: T) -> Self::Output {
self.current_val.div(rhs)
}
}
impl<'a, T: Copy + Div<T, Output = T>> DivAssign<T> for UseStateOwned<T> {
fn div_assign(&mut self, rhs: T) {
self.set(self.current_val.div(rhs));
}
}