Merge pull request #21 from jkelleyrtp/jk/portals

Feat: portals
This commit is contained in:
Jonathan Kelley authored and GitHub committed 2021-10-14 14:53:31 -04:00
commit 7dedad4c26
71 files changed
+873 -4697

No files matched your search

+5 -5
View File
@@ -19,7 +19,7 @@ dioxus-mobile = { path = "./packages/mobile", optional = true }
[features]
# core
default = ["core", "ssr", "web"]
default = ["core"]
core = ["macro", "hooks", "html"]
macro = ["dioxus-core-macro"]
hooks = ["dioxus-hooks"]
@@ -55,9 +55,6 @@ env_logger = "0.9.0"
tokio = { version = "1.12.0", features = ["full"] }
rand = { version = "0.8.4", features = ["small_rng"] }
gloo-timers = "0.2.1"
# surf = { version = "2.3.1", default-features = false, features = [
# "wasm-client",
# ] }
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
gloo-timers = "0.2.1"
@@ -66,6 +63,9 @@ console_error_panic_hook = "0.1.6"
rand = { version = "0.8.4", features = ["small_rng"] }
wasm-bindgen = { version = "0.2.71", features = ["enable-interning"] }
# surf = { version = "2.3.1", default-features = false, features = [
# "wasm-client",
# ] }
# surf = { version = "2.2.0", default-features = false, features = [
# "wasm-client",
# ], git = "https://github.com/jkelleyrtp/surf/", branch = "jk/fix-the-wasm" }
@@ -86,7 +86,7 @@ members = [
"packages/ssr",
"packages/desktop",
"packages/mobile",
"packages/webview-client"
"packages/webview-client",
]
+3 -17
View File
@@ -89,23 +89,9 @@ If you know React, then you already know Dioxus.
## Examples:
| File Navigator (Desktop) | Bluetooth scanner (Desktop) | TodoMVC (All platforms) | Widget Gallery |
| ---------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- |
| ![asd](https://sixtyfps.io/resources/printerdemo_screenshot.png) | ![asd](https://sixtyfps.io/resources/printerdemo_screenshot.png) | ![asd](https://sixtyfps.io/resources/printerdemo_screenshot.png) | ![asd](https://sixtyfps.io/resources/printerdemo_screenshot.png) |
| E-Commerce Site | Doxie Documentation Generator | Chatroom | Widget Gallery |
| ---------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- |
| ![asd](https://sixtyfps.io/resources/printerdemo_screenshot.png) | ![asd](https://sixtyfps.io/resources/printerdemo_screenshot.png) | ![asd](https://sixtyfps.io/resources/printerdemo_screenshot.png) | ![asd](https://sixtyfps.io/resources/printerdemo_screenshot.png) |
| Printer Demo | Slide Puzzle | Todo | Widget Gallery |
| ---------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- |
| ![asd](https://sixtyfps.io/resources/printerdemo_screenshot.png) | ![asd](https://sixtyfps.io/resources/printerdemo_screenshot.png) | ![asd](https://sixtyfps.io/resources/printerdemo_screenshot.png) | ![asd](https://sixtyfps.io/resources/printerdemo_screenshot.png) |
| File Navigator (Desktop) | Bluetooth scanner (Desktop) | TodoMVC (All platforms) | Widget Gallery |
| --------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| [![asd](https://github.com/DioxusLabs/file-explorer-example/raw/master/image.png)](https://github.com/DioxusLabs/file-explorer-example) | ![asd](https://sixtyfps.io/resources/printerdemo_screenshot.png) | [![asd](https://github.com/DioxusLabs/todomvc/blob/master/example.png)](https://github.com/dioxusLabs/todomvc/) | ![asd](https://sixtyfps.io/resources/printerdemo_screenshot.png) |
See the awesome-dioxus page for a curated list of content in the Dioxus Ecosystem.
+54
View File
@@ -0,0 +1,54 @@
# Subtrees
One way of extending the Dioxus VirtualDom is through the use of "Subtrees." Subtrees are chunks of the VirtualDom tree distinct from the rest of the tree. They still participate in event bubbling, diffing, etc, but will have a separate set of edits generated during the diff phase.
For a VirtualDom that has a root tree with two subtrees, the edits follow a pattern of:
Root
-> Tree 1
-> Tree 2
-> Original root tree
- Root edits
- Tree 1 Edits
- Tree 2 Edits
- Root Edits
The goal of this functionality is to enable things like Portals, Windows, and inline alternative renderers without needing to spin up a new VirtualDom.
With the right renderer plugins, a subtree could be rendered as anything - a 3D scene, SVG, or even as the contents of a new window or modal. This functionality is similar to "Portals" in React, but much more "renderer agnostic." Portals, by nature, are not necessarily cross-platform and rely on renderer functionality, so it makes sense to abstract their purpose into the subtree concept.
The desktop renderer comes pre-loaded with the window and notification subtree plugins, making it possible to render subtrees into entirely different windows.
Subtrees also solve the "bridging" issues in React where two different renderers need two different VirtualDoms to work properly. In Dioxus, you only ever need one VirtualDom and the right renderer plugins.
## API
Due to their importance in the hierarchy, Components - not nodes - are treated as subtree roots.
```rust
fn Subtree<P>(cx: Context, props: P) -> DomTree {
}
fn Window() -> DomTree {
Subtree {
onassign: move |e| {
// create window
}
children()
}
}
fn 3dRenderer -> DomTree {
Subtree {
onassign: move |e| {
// initialize bevy
}
}
}
```
-49
View File
@@ -1,49 +0,0 @@
fn main() {
// render the
let transition = move |cx, (width, height)| {};
cx.render(rsx! {
div {
Transition {
start: (0, 5),
stop: (10, 10),
render: transition
}
Transition {
start: (0, 5),
stop: (10, 10),
render: move |cx, (width, height)| {
//
cx.render(rsx!{
div {
style {
width: width,
width: height
}
}
})
}
}
}
})
}
// Animations with signals
fn signal_based(cx: ()) {
const InitPos: (i32, i32) = (0, 0);
const EndPos: (i32, i32) = (100, 200);
let spring = use_spring(cx, move |spring| spring.from(InitPos).to(EndPos));
cx.render(rsx! {
div {
style: [
width: spring.0,
height: spring.1
]
button { onclick: move |_| spring.set(InitPos), "Reset" }
button { onclick: move |_| spring.set(EndPos), "Animate" }
}
})
}
-57
View File
@@ -1,57 +0,0 @@
//! Basic example that renders a simple VNode to the browser.
use dioxus::events::on::MouseEvent;
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_html as dioxus_elements;
use dioxus_web::*;
fn main() {
// Setup logging
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
console_error_panic_hook::set_once();
// Run the app
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
}
static App: FC<()> = |cx, props| {
let (state, set_state) = use_state_classic(cx, || 0);
cx.render(rsx! {
div {
section { class: "py-12 px-4 text-center"
div { class: "w-full max-w-2xl mx-auto"
span { class: "text-sm font-semibold"
"count: {state}"
}
div {
C1 {
onclick: move |_| set_state(state + 1)
"incr"
}
C1 {
onclick: move |_| set_state(state - 1)
"decr"
}
}
}
}
}
})
};
#[derive(Props)]
struct IncrementerProps<'a> {
onclick: &'a dyn Fn(MouseEvent),
}
fn C1<'a, 'b>(cx: Context<'a, IncrementerProps<'b>>) -> DomTree<'a> {
cx.render(rsx! {
button {
class: "inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
onclick: {cx.onclick}
"becr"
{cx.children()}
}
})
}
-188
View File
@@ -1,188 +0,0 @@
//! Example: Calculator
//! ------------------
//!
//! This example showcases a basic iOS-style calculator app using classic react-style `use_state` hooks. A counterpart of
//! this example is in `model.rs` which shows the same calculator app implemented using the `model` paradigm. We've found
//! that the most natural Rust code is more "model-y" than "React-y", but opt to keep this example to show the different
//! flavors of programming you can use in Dioxus.
use dioxus::events::on::*;
use dioxus::prelude::*;
use dioxus_core as dioxus;
use dioxus_html as dioxus_elements;
use dioxus_web::WebsysRenderer;
const STYLE: &str = include_str!("../../../examples/assets/calculator.css");
fn main() {
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
console_error_panic_hook::set_once();
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
}
enum Operator {
Add,
Sub,
Mul,
Div,
}
static App: FC<()> = |cx, props| {
let (cur_val, set_cur_val) = use_state_classic(cx, || 0.0_f64);
let (operator, set_operator) = use_state_classic(cx, || None as Option<Operator>);
let (display_value, set_display_value) = use_state_classic(cx, || "0".to_string());
let clear_display = display_value.eq("0");
let clear_text = if clear_display { "C" } else { "AC" };
let input_digit = move |num: u8| {
let mut new = if operator.is_some() {
String::new()
} else if display_value == "0" {
String::new()
} else {
display_value.clone()
};
if operator.is_some() {
let val = display_value.parse::<f64>().unwrap();
set_cur_val(val);
}
new.push_str(num.to_string().as_str());
set_display_value(new);
};
let input_dot = move || {
let mut new = display_value.clone();
new.push_str(".");
set_display_value(new);
};
let perform_operation = move || {
if let Some(op) = operator.as_ref() {
let rhs = display_value.parse::<f64>().unwrap();
let new_val = match op {
Operator::Add => *cur_val + rhs,
Operator::Sub => *cur_val - rhs,
Operator::Mul => *cur_val * rhs,
Operator::Div => *cur_val / rhs,
};
set_cur_val(new_val);
set_display_value(new_val.to_string());
set_operator(None);
}
};
let toggle_sign = move |_| {
if display_value.starts_with("-") {
set_display_value(display_value.trim_start_matches("-").to_string())
} else {
set_display_value(format!("-{}", *display_value))
}
};
let toggle_percent = move |_| todo!();
let clear_key = move |_| {
set_display_value("0".to_string());
if !clear_display {
set_operator(None);
set_cur_val(0.0);
}
};
let keydownhandler = move |evt: KeyboardEvent| match evt.key_code() {
KeyCode::Backspace => {
let mut new = display_value.clone();
if !new.as_str().eq("0") {
new.pop();
}
set_display_value(new);
}
KeyCode::_0 => input_digit(0),
KeyCode::_1 => input_digit(1),
KeyCode::_2 => input_digit(2),
KeyCode::_3 => input_digit(3),
KeyCode::_4 => input_digit(4),
KeyCode::_5 => input_digit(5),
KeyCode::_6 => input_digit(6),
KeyCode::_7 => input_digit(7),
KeyCode::_8 => input_digit(8),
KeyCode::_9 => input_digit(9),
KeyCode::Add => set_operator(Some(Operator::Add)),
KeyCode::Subtract => set_operator(Some(Operator::Sub)),
KeyCode::Divide => set_operator(Some(Operator::Div)),
KeyCode::Multiply => set_operator(Some(Operator::Mul)),
_ => {}
};
cx.render(rsx! {
div {
id: "wrapper"
div { class: "app" onkeydown: {keydownhandler}
style { "{STYLE}" }
div { class: "calculator",
CalculatorDisplay { val: &display_value}
div { class: "calculator-keypad"
div { class: "input-keys"
div { class: "function-keys"
CalculatorKey { name: "key-clear", onclick: {clear_key} "{clear_text}" }
CalculatorKey { name: "key-sign", onclick: {toggle_sign}, "±"}
CalculatorKey { name: "key-percent", onclick: {toggle_percent} "%"}
}
div { class: "digit-keys"
CalculatorKey { name: "key-0", onclick: move |_| input_digit(0), "0" }
CalculatorKey { name: "key-dot", onclick: move |_| input_dot(), "●" }
{(1..10).map(move |k| rsx!{
CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| input_digit(k), "{k}" }
})}
}
}
div { class: "operator-keys"
CalculatorKey { name: "key-divide", onclick: move |_| set_operator(Some(Operator::Div)) "÷" }
CalculatorKey { name: "key-multiply", onclick: move |_| set_operator(Some(Operator::Mul)) "×" }
CalculatorKey { name: "key-subtract", onclick: move |_| set_operator(Some(Operator::Sub)) "−" }
CalculatorKey { name: "key-add", onclick: move |_| set_operator(Some(Operator::Add)) "+" }
CalculatorKey { name: "key-equals", onclick: move |_| perform_operation() "=" }
}
}
}
}
}
})
};
#[derive(Props)]
struct CalculatorKeyProps<'a> {
/// Name!
name: &'static str,
/// Click!
onclick: &'a dyn Fn(MouseEvent),
}
fn CalculatorKey<'a, 'r>(cx: Context<'a, CalculatorKeyProps<'r>>) -> DomTree<'a> {
cx.render(rsx! {
button {
class: "calculator-key {cx.name}"
onclick: {cx.onclick}
{cx.children()}
}
})
}
#[derive(Props, PartialEq)]
struct CalculatorDisplayProps<'a> {
val: &'a str,
}
fn CalculatorDisplay<'a>(cx: Context<'a, CalculatorDisplayProps>) -> DomTree<'a> {
use separator::Separatable;
// Todo, add float support to the num-format crate
let formatted = cx.val.parse::<f64>().unwrap().separated_string();
// TODO: make it autoscaling with css
cx.render(rsx! {
div { class: "calculator-display"
div { class: "auto-scaling-text", "{formatted}" }
}
})
}
-181
View File
@@ -1,181 +0,0 @@
use dioxus::events::on::*;
use dioxus::prelude::*;
use dioxus_core as dioxus;
const STYLE: &str = include_str!("../../../examples/assets/calculator.css");
fn main() {
dioxus_desktop::launch(
|builder| {
builder
.title("Test Dioxus App")
.size(340, 560)
.resizable(false)
.debug(true)
},
(),
App,
)
.expect("Webview finished");
}
static App: FC<()> = |cx, props| {
let state = use_model(&cx, || Calculator::new());
let clear_display = state.display_value.eq("0");
let clear_text = if clear_display { "C" } else { "AC" };
let formatted = state.formatted_display();
cx.render(rsx! {
div { id: "wrapper"
div { class: "app", style { "{STYLE}" }
div { class: "calculator", onkeypress: move |evt| state.get_mut().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.get_mut().clear_display(), "{clear_text}" }
CalculatorKey { name: "key-sign", onclick: move |_| state.get_mut().toggle_sign(), "±"}
CalculatorKey { name: "key-percent", onclick: move |_| state.get_mut().toggle_percent(), "%"}
}
div { class: "digit-keys"
CalculatorKey { name: "key-0", onclick: move |_| state.get_mut().input_digit(0), "0" }
CalculatorKey { name: "key-dot", onclick: move |_| state.get_mut().input_dot(), "●" }
{(1..10).map(move |k| rsx!{
CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| state.get_mut().input_digit(k), "{k}" }
})}
}
}
div { class: "operator-keys"
CalculatorKey { name:"key-divide", onclick: move |_| state.get_mut().set_operator(Operator::Div), "÷" }
CalculatorKey { name:"key-multiply", onclick: move |_| state.get_mut().set_operator(Operator::Mul), "×" }
CalculatorKey { name:"key-subtract", onclick: move |_| state.get_mut().set_operator(Operator::Sub), "−" }
CalculatorKey { name:"key-add", onclick: move |_| state.get_mut().set_operator(Operator::Add), "+" }
CalculatorKey { name:"key-equals", onclick: move |_| state.get_mut().perform_operation(), "=" }
}
}
}
}
}
})
};
#[derive(Props)]
struct CalculatorKeyProps<'a> {
name: &'static str,
onclick: &'a dyn Fn(MouseEvent),
}
fn CalculatorKey<'a, 'r>(cx: Context<'a, CalculatorKeyProps<'r>>) -> DomTree<'a> {
cx.render(rsx! {
button {
class: "calculator-key {cx.name}"
onclick: {cx.onclick}
{cx.children()}
}
})
}
#[derive(Clone)]
struct Calculator {
display_value: String,
operator: Option<Operator>,
waiting_for_operand: bool,
cur_val: f64,
}
#[derive(Clone)]
enum Operator {
Add,
Sub,
Mul,
Div,
}
impl Calculator {
fn new() -> Self {
Calculator {
display_value: "0".to_string(),
operator: None,
waiting_for_operand: false,
cur_val: 0.0,
}
}
fn formatted_display(&self) -> String {
// use separator::Separatable;
self.display_value.clone()
// .parse::<f64>()
// .unwrap()
// .separated_string()
}
fn clear_display(&mut self) {
self.display_value = "0".to_string();
}
fn input_digit(&mut self, digit: u8) {
let content = digit.to_string();
if self.waiting_for_operand || self.display_value == "0" {
self.waiting_for_operand = false;
self.display_value = content;
} else {
self.display_value.push_str(content.as_str());
}
}
fn input_dot(&mut self) {
if self.display_value.find(".").is_none() {
self.display_value.push_str(".");
}
}
fn perform_operation(&mut self) {
if let Some(op) = &self.operator {
let rhs = self.display_value.parse::<f64>().unwrap();
let new_val = match op {
Operator::Add => self.cur_val + rhs,
Operator::Sub => self.cur_val - rhs,
Operator::Mul => self.cur_val * rhs,
Operator::Div => self.cur_val / rhs,
};
self.cur_val = new_val;
self.display_value = new_val.to_string();
self.operator = None;
}
}
fn toggle_sign(&mut self) {
if self.display_value.starts_with("-") {
self.display_value = self.display_value.trim_start_matches("-").to_string();
} else {
self.display_value = format!("-{}", self.display_value);
}
}
fn toggle_percent(&mut self) {
self.display_value = (self.display_value.parse::<f64>().unwrap() / 100.0).to_string();
}
fn backspace(&mut self) {
if !self.display_value.as_str().eq("0") {
self.display_value.pop();
}
}
fn set_operator(&mut self, operator: Operator) {
self.operator = Some(operator);
self.cur_val = self.display_value.parse::<f64>().unwrap();
self.waiting_for_operand = true;
}
fn handle_keydown(&mut self, evt: KeyboardEvent) {
match evt.key_code() {
KeyCode::Backspace => self.backspace(),
KeyCode::_0 => self.input_digit(0),
KeyCode::_1 => self.input_digit(1),
KeyCode::_2 => self.input_digit(2),
KeyCode::_3 => self.input_digit(3),
KeyCode::_4 => self.input_digit(4),
KeyCode::_5 => self.input_digit(5),
KeyCode::_6 => self.input_digit(6),
KeyCode::_7 => self.input_digit(7),
KeyCode::_8 => self.input_digit(8),
KeyCode::_9 => self.input_digit(9),
KeyCode::Add => self.operator = Some(Operator::Add),
KeyCode::Subtract => self.operator = Some(Operator::Sub),
KeyCode::Divide => self.operator = Some(Operator::Div),
KeyCode::Multiply => self.operator = Some(Operator::Mul),
_ => {}
}
}
}
-37
View File
@@ -1,37 +0,0 @@
//! Basic example that renders a simple VNode to the browser.
use dioxus_core::prelude::*;
use dioxus_html as dioxus_elements;
use dioxus_web::*;
fn main() {
// Setup logging
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
console_error_panic_hook::set_once();
// Run the app
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
}
static App: FC<()> = |cx, props| {
cx.render(rsx! {
Calcier {
h2 {"abc 1"}
h2 {"abc 2"}
h2 {"abc 3"}
h2 {"abc 4"}
h2 {"abc 5"}
}
})
};
static Calcier: FC<()> = |cx, props| {
cx.render(rsx! {
div {
h1 {
"abc 0"
}
{cx.children()}
}
})
};
-65
View File
@@ -1,65 +0,0 @@
//! Example: Context API
//! --------------------
//! This example demonstrates how to use the raw context api for sharing state throughout the VirtualDOM Tree.
//! A custom context must be its own unique type - otherwise use_context will fail. A context may be c
//!
//!
//!
//!
//!
//!
//!
//!
use dioxus_core::prelude::*;
use dioxus_core as dioxus;
use dioxus_web::WebsysRenderer;
use dioxus_html as dioxus_elements;
fn main() {
wasm_logger::init(wasm_logger::Config::new(log::Level::Trace));
console_error_panic_hook::set_once();
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
}
#[derive(Debug)]
struct CustomContext([&'static str; 3]);
pub static Example: FC<()> = |cx, props|{
cx.use_create_context(|| CustomContext(["Jack", "Jill", "Bob"]));
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"
}
CustomButton { id: 0 }
CustomButton { id: 1 }
CustomButton { id: 2 }
}
})
};
#[derive(Props, PartialEq)]
struct ButtonProps {
id: u8,
}
fn CustomButton(cx: Context<ButtonProps>) -> DomTree {
let names = cx.use_context::<CustomContext>();
let name = names.0[cx.id as usize];
cx.render(rsx!{
button {
class: "inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
"{name}"
}
})
}
-75
View File
@@ -1,75 +0,0 @@
use std::rc::Rc;
use dioxus::prelude::*;
use dioxus_core as dioxus;
use dioxus_html as dioxus_elements;
fn main() {
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
console_error_panic_hook::set_once();
wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(CustomA))
}
fn CustomA(cx: Context<()>) -> DomTree {
let (val, set_val) = use_state_classic(cx, || "abcdef".to_string() as String);
cx.render(rsx! {
div {
class: "m-8"
"CustomA {val}"
button {
"Upper"
onclick: move |_| set_val(val.to_ascii_uppercase())
}
button {
"Lower"
onclick: move |_| set_val(val.to_ascii_lowercase())
}
components::CustomB {
val: val.clone()
}
}
})
}
mod components {
use std::rc::Rc;
use super::*;
#[derive(Debug, Props, PartialEq)]
pub struct PropsB {
val: String,
}
pub fn CustomB(cx: Context<PropsB>) -> DomTree {
let (first, last) = cx.val.split_at(3);
cx.render(rsx! {
div {
class: "m-8"
"CustomB {cx.val}"
CustomC {
val: first.to_string()
}
CustomC {
val: last.to_string()
}
}
})
}
#[derive(Debug, Props, PartialEq)]
struct PropsC {
val: String,
}
fn CustomC(cx: Context<PropsC>) -> DomTree {
cx.render(rsx! {
div {
class: "m-8"
"CustomC {cx.val}"
}
})
}
}
-71
View File
@@ -1,71 +0,0 @@
//! An example where the dioxus vdom is running in a native thread, interacting with webview
//! Content is passed from the native thread into the webview
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
fn main() {
dioxus_desktop::launch(
|builder| {
builder
.title("Test Dioxus App")
.size(320, 480)
.resizable(false)
.debug(true)
},
(),
Example,
)
.expect("Webview finished");
}
// pub static Example: FC<()> = |cx, props|{
// cx.render(html! {
// <div>
// <svg class="octicon octicon-star v-align-text-bottom"
// viewBox="0 0 14 16" version="1.1"
// width="14" height="16"
// xmlns="http://www.w3.org/2000/svg"
// >
// <path
// d="M14 6l-4.9-.64L7 1 4.9 5.36 0 6l3.6 3.26L2.67 14"
// xmlns="http://www.w3.org/2000/svg"
// >
// </path>
// </svg>
// </div>
// })
// };
pub static Example: FC<()> = |cx, props| {
cx.render(rsx! {
div {
class: "flex items-center justify-center flex-col"
div {
class: "flex items-center justify-center"
div {
class: "flex flex-col bg-white rounded p-4 w-full max-w-xs"
div { class: "font-bold text-xl", "Example desktop app" }
div { class: "text-sm text-gray-500", "This is running natively" }
div {
class: "flex flex-row items-center justify-center mt-6"
div { class: "font-medium text-6xl", "100%" }
}
div {
class: "flex flex-row justify-between mt-6"
a {
href: "https://www.dioxuslabs.com"
class: "underline"
"Made with dioxus"
}
}
ul {
{(0..10).map(|f| rsx!(li {
key: "{f}"
"{f}"
}))}
}
}
}
}
})
};
-1
View File
@@ -1 +0,0 @@
fn main() {}
-89
View File
@@ -1,89 +0,0 @@
use dioxus_core as dioxus;
use dioxus_html as dioxus_elements;
use dioxus_html::*;
use dioxus_web::{dioxus::prelude::*, WebsysRenderer};
fn main() {
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
console_error_panic_hook::set_once();
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App))
}
fn App(cx: Context<()>) -> DomTree {
cx.render(rsx! {
div { class: "dark:bg-gray-800 bg-white relative h-screen"
NavBar {}
{(0..10).map(|f| rsx!(Landing { key: "{f}" }))}
}
})
}
fn NavBar(cx: Context<()>) -> DomTree {
cx.render(rsx!{
header { class: "h-24 sm:h-32 flex items-center z-30 w-full"
div { class: "container mx-auto px-6 flex items-center justify-between"
div { class: "uppercase text-gray-800 dark:text-white font-black text-3xl"
svg { focusable:"false" width:"100" height:"100" viewBox: "0 0 512 309"
path { fill: "#000"
d: "M120.81 80.561h96.568v7.676h-87.716v57.767h82.486v7.675h-82.486v63.423h88.722v7.675H120.81V80.561zm105.22 0h10.26l45.467 63.423L328.23 80.56L391.441 0l-103.85 150.65l53.515 74.127h-10.663l-48.686-67.462l-48.888 67.462h-10.461l53.917-74.128l-50.296-70.088zm118.898 7.676V80.56h110.048v7.676h-50.699v136.54h-8.852V88.237h-50.497zM0 80.56h11.065l152.58 228.323l-63.053-84.107L9.254 91.468l-.402 133.31H0V80.56zm454.084 134.224c-1.809 0-3.165-1.4-3.165-3.212c0-1.81 1.356-3.212 3.165-3.212c1.83 0 3.165 1.401 3.165 3.212c0 1.811-1.335 3.212-3.165 3.212zm8.698-8.45h4.737c.064 2.565 1.937 4.29 4.693 4.29c3.079 0 4.823-1.854 4.823-5.325v-21.99h4.823v22.011c0 6.252-3.617 9.853-9.603 9.853c-5.62 0-9.473-3.493-9.473-8.84zm25.384-.28h4.78c.409 2.953 3.294 4.828 7.45 4.828c3.875 0 6.717-2.005 6.717-4.764c0-2.371-1.809-3.794-5.921-4.764l-4.005-.97c-5.62-1.316-8.181-4.032-8.181-8.602c0-5.54 4.521-9.227 11.303-9.227c6.308 0 10.916 3.686 11.196 8.925h-4.694c-.452-2.867-2.95-4.657-6.567-4.657c-3.81 0-6.35 1.833-6.35 4.635c0 2.22 1.635 3.493 5.683 4.441l3.423.841c6.373 1.488 9 4.075 9 8.753c0 5.95-4.607 9.68-11.97 9.68c-6.89 0-11.52-3.558-11.864-9.12z"
}
}
}
div { class:"flex items-center"
nav { class: "font-sen text-gray-800 dark:text-white uppercase text-lg lg:flex items-center hidden"
a { href: "#", class:"py-2 px-6 flex text-indigo-500 border-b-2 border-indigo-500"
"Home"
}
a { href: "#", class: "py-2 px-6 flex hover:text-indigo-500"
"Watch"
}
a { href: "#", class: "py-2 px-6 flex hover:text-indigo-500"
"Product"
}
a { href: "#", class: "py-2 px-6 flex hover:text-indigo-500"
"Contact"
}
a { href: "#", class: "py-2 px-6 flex hover:text-indigo-500"
"Career"
}
}
button { class: "lg:hidden flex flex-col ml-4"
span { class: "w-6 h-1 bg-gray-800 dark:bg-white mb-1" }
span { class: "w-6 h-1 bg-gray-800 dark:bg-white mb-1" }
span { class: "w-6 h-1 bg-gray-800 dark:bg-white mb-1" }
}
}
}
}
})
}
fn Landing(cx: Context<()>) -> DomTree {
cx.render(rsx!{
div { class: "bg-white dark:bg-gray-800 flex relative z-20 items-center"
div { class: "container mx-auto px-6 flex flex-col justify-between items-center relative py-8"
div { class: "flex flex-col"
h1 { class: "font-light w-full uppercase text-center text-4xl sm:text-5xl dark:text-white text-gray-800"
"The Dioxus Framework for Production"
}
h2{ class: "font-light max-w-2xl mx-auto w-full text-xl dark:text-white text-gray-500 text-center py-8"
"Next.js gives you the best developer experience with all the features you need for production: \n
hybrid static &amp; server rendering, TypeScript support, smart bundling, route pre-fetching, and \n
more. No config needed."
}
div { class: "flex items-center justify-center mt-4"
a { href: "#" class: "uppercase py-2 px-4 bg-gray-800 border-2 border-transparent text-white text-md mr-4 hover:bg-gray-900"
"Get started"
}
a{ href: "#" class: "uppercase py-2 px-4 bg-transparent border-2 border-gray-800 text-gray-800 dark:text-white hover:bg-gray-800 hover:text-white text-md"
"Documentation"
}
}
}
div { class: "block w-full mx-auto mt-6 md:mt-0 relative"
img { src: "/images/object/12.svg" class: "max-w-xs md:max-w-2xl m-auto" }
}
}
}
})
}
-46
View File
@@ -1,46 +0,0 @@
use dioxus::{events::on::MouseEvent, prelude::*};
use dioxus_core as dioxus;
use dioxus_html as dioxus_elements;
use dioxus_web::WebsysRenderer;
fn main() {
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
console_error_panic_hook::set_once();
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
}
fn App(cx: Context<()>) -> DomTree {
let cansee = use_state(cx, || false);
rsx! { in cx,
div {
"Shadow of the child:"
button { onclick: move |_| cansee.set(!**cansee)
"Gaze into the void"
}
{cansee.then(|| rsx!{ Child {} })}
}
}
}
fn Child(cx: Context<()>) -> DomTree {
rsx! { in cx,
section { class: "py-6 bg-coolGray-100 text-coolGray-900"
div { class: "container mx-auto flex flex-col items-center justify-center p-4 space-y-8 md:p-10 md:px-24 xl:px-48"
h1 { class: "text-5xl font-bold leading-none text-center",
"Sign up now"
}
p { class: "text-xl font-medium text-center",
"At a assumenda quas cum earum ut itaque commodi saepe rem aspernatur quam natus quis nihil quod, hic explicabo doloribus magnam neque, exercitationem eius sunt!"
}
div { class: "flex flex-col space-y-4 sm:space-y-0 sm:flex-row sm:space-x-8"
button { class: "px-8 py-3 text-lg font-semibold rounded bg-violet-600 text-coolGray-50",
"Get started"
}
button { class: "px-8 py-3 text-lg font-normal border rounded bg-coolGray-800 text-coolGray-50 border-coolGray-700",
"Learn more"
}
}
}
}
}
}
-21
View File
@@ -1,21 +0,0 @@
use dioxus_core as dioxus;
use dioxus_core::events::on::*;
use dioxus_core::prelude::*;
fn main() {}
fn autocomplete() {
// let handler = move |evt| {
// let r = evt.alt_key();
// if evt.alt_key() {}
// };
// let g = rsx! {
// button {
// button {
// onclick: {handler}
// }
// }
// };
}
-26
View File
@@ -1,26 +0,0 @@
//! Basic example that renders a simple VNode to the browser.
use dioxus_core::prelude::*;
use dioxus_html as dioxus_elements;
use dioxus_web::*;
fn main() {
// Setup logging
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
console_error_panic_hook::set_once();
// Run the app
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
}
static App: FC<()> = |cx, props| {
cx.render(rsx! {
h2 { "abc 1" }
div {
"hello world!"
}
Fragment {
h2 { "abc 2"}
}
})
};
-33
View File
@@ -1,33 +0,0 @@
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_html as dioxus_elements;
use dioxus_web::WebsysRenderer;
fn main() {
// wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
// console_error_panic_hook::set_once();
// log::info!("hello world");
dioxus_web::intern_cache();
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
}
pub static Example: FC<()> = |cx, props| {
let nodes = (0..500).map(|f| rsx! (li {"div"}));
cx.render(rsx! {
div {
span {
class: "px-2 py-1 flex w-36 mt-4 items-center text-xs rounded-md font-semibold text-yellow-500 bg-yellow-100"
"DUE DATE : 189 JUN"
}
p {
"these"
"are"
"text"
"nodes"
}
{nodes}
}
})
};
-13
View File
@@ -1,13 +0,0 @@
use dioxus_core as dioxus;
use dioxus_html as dioxus_elements;
use dioxus_web::prelude::*;
fn main() {
wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(App))
}
fn App(cx: Context<()>) -> DomTree {
cx.render(rsx! {
div { "Hello, world!" }
})
}
-28
View File
@@ -1,28 +0,0 @@
//! An example where the dioxus vdom is running in a native thread, interacting with webview
//! Content is passed from the native thread into the webview
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
fn main() {
dioxus_desktop::launch(
|builder| {
builder
.title("Test Dioxus App")
.size(320, 480)
.resizable(false)
.debug(true)
},
(),
App,
)
.expect("Webview finished");
}
static App: FC<()> = |cx, props| {
let hifives = use_model(&cx, || 0);
cx.render(rsx! {
div {
h1 { "Hi-fives collected: {hifives}" }
button { "Hi five me!", onclick: move |_| *hifives.get_mut() += 1 }
}
})
};
-59
View File
@@ -1,59 +0,0 @@
<div class="shadow-lg rounded-2xl p-4 bg-white dark:bg-gray-700 w-full">
<p class="font-bold text-md text-black dark:text-white">
"Messages"
</p>
<ul>
<li class="flex items-center my-6 space-x-2">
<a href="#" class="block relative">
<img alt="profil" src="/images/person/1.jpg" class="mx-auto object-cover rounded-full h-10 w-10 " />
</a>
<div class="flex flex-col">
<span class="text-sm text-gray-900 font-semibold dark:text-white ml-2">
{title}
</span>
<span class="text-sm text-gray-400 dark:text-gray-300 ml-2">
"Hey John ! Do you read the NextJS doc ?"
</span>
</div>
</li>
<li class="flex items-center my-6 space-x-2">
<a href="#" class="block relative">
<img alt="profil" src="/images/person/5.jpg" class="mx-auto object-cover rounded-full h-10 w-10 " />
</a>
<div class="flex flex-col">
<span class="text-sm text-gray-900 font-semibold dark:text-white ml-2">
"Marie Lou"
</span>
<span class="text-sm text-gray-400 dark:text-gray-300 ml-2">
"No I think the dog is better..."
</span>
</div>
</li>
<li class="flex items-center my-6 space-x-2">
<a href="#" class="block relative">
<img alt="profil" src="/images/person/6.jpg" class="mx-auto object-cover rounded-full h-10 w-10 " />
</a>
<div class="flex flex-col">
<span class="text-sm text-gray-900 font-semibold dark:text-white ml-2">
"Ivan Buck"
</span>
<span class="text-sm text-gray-400 dark:text-gray-300 ml-2">
"Seriously ? haha Bob is not a children !"
</span>
</div>
</li>
<li class="flex items-center my-6 space-x-2">
<a href="#" class="block relative">
<img alt="profil" src="/images/person/7.jpg" class="mx-auto object-cover rounded-full h-10 w-10 " />
</a>
<div class="flex flex-col">
<span class="text-sm text-gray-900 font-semibold dark:text-white ml-2">
"Marina Farga"
</span>
<span class="text-sm text-gray-400 dark:text-gray-300 ml-2">
"Do you need that deisgn ?"
</span>
</div>
</li>
</ul>
</div>
-59
View File
@@ -1,59 +0,0 @@
use dioxus_core as dioxus;
use dioxus_core::{events::on::MouseEvent, prelude::*};
use dioxus_web::WebsysRenderer;
use dioxus_html as dioxus_elements;
fn main() {
// Setup logging
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
console_error_panic_hook::set_once();
log::info!("hello world");
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
}
// this is a component
pub static Example: FC<()> = |cx, props|{
let (event, set_event) = use_state_classic(cx, || None);
let handler = move |evt| {
set_event(Some(evt));
};
log::info!("hello world");
cx.render(rsx! {
div {
class: "py-12 px-4 w-full max-w-2xl mx-auto bg-red-100"
span {
class: "text-sm font-semibold"
"Dioxus Example: Synthetic Events"
}
button {
class: "inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
"press me"
}
pre {
onmousemove: {handler}
id: "json"
"Hello world"
}
Example2 { name: "Blah".into() }
}
})
};
#[derive(Debug, PartialEq, Props)]
struct ExampleProps {
name: String,
}
pub static Example2: FC<ExampleProps> = |cx, props|{
cx.render(rsx! {
div {
h1 {"hello {cx.name}"}
}
})
};
-67
View File
@@ -1,67 +0,0 @@
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_web::WebsysRenderer;
use dioxus_html as dioxus_elements;
fn main() {
// wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
console_error_panic_hook::set_once();
log::info!("hello world");
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
}
static App: FC<()> = |cx, props|{
let (val, set_val) = use_state_classic(cx, || "asd".to_string());
cx.render(rsx! {
div { class: "max-w-lg max-w-xs bg-blue-800 shadow-2xl rounded-lg mx-auto text-center py-12 mt-4 rounded-xl"
div { class: "container py-5 max-w-md mx-auto"
h1 { class: "text-gray-200 text-center font-extrabold -mt-3 text-3xl",
"Text Input Example"
}
div { class: "mb-4"
input {
placeholder: "Username"
class: "shadow appearance-none rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
id: "username"
r#type: "text"
value: "{val}"
oninput: move |evet| set_val(evet.value())
}
p { "Val is: {val}" }
}
}
}
})
};
pub static Example: FC<()> = |cx, props|{
cx.render(rsx! {
div { class: "max-w-lg max-w-xs bg-blue-800 shadow-2xl rounded-lg mx-auto text-center py-12 mt-4 rounded-xl"
div { class: "container py-5 max-w-md mx-auto"
h1 { class: "text-gray-200 text-center font-extrabold -mt-3 text-3xl",
"Text Input Example"
}
UserInput {}
}
}
})
};
static UserInput: FC<()> = |cx, props|{
let (val, set_val) = use_state_classic(cx, || "asd".to_string());
rsx! { in cx,
div { class: "mb-4"
input { class: "shadow appearance-none rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
placeholder: "Username"
id: "username"
r#type: "text"
oninput: move |evet| set_val(evet.value())
}
p { "Val is: {val}" }
}
}
};
-49
View File
@@ -1,49 +0,0 @@
use dioxus::prelude::*;
use dioxus_core as dioxus;
use dioxus_web::WebsysRenderer;
fn main() {
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
console_error_panic_hook::set_once();
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example))
}
pub static Example: FC<()> = |cx, props| {
let (name, set_name) = use_state_classic(cx, || "...?");
log::debug!("Running component....");
cx.render(html! {
<div>
<section class="py-12 px-4 text-center">
<div class="w-full max-w-2xl mx-auto">
// Tagline
<span class="text-sm font-semibold">
"Dioxus Example: Jack and Jill"
</span>
// Header
<h2 class="text-5xl mt-2 mb-6 leading-tight font-semibold font-heading">
"Hello, {name}"
</h2>
// Control buttons
<div>
<button
class="inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
onclick={move |_| set_name("jack")}>
"Jack!"
</button>
<button
class="inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
onclick={move |_| set_name("jill")}>
"Jill!"
</button>
</div>
</div>
</section>
</div>
})
};
-38
View File
@@ -1,38 +0,0 @@
use dioxus::prelude::*;
use dioxus_core as dioxus;
use dioxus_html as dioxus_elements;
use dioxus_web::WebsysRenderer;
fn main() {
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
console_error_panic_hook::set_once();
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example))
}
pub static Example: FC<()> = |cx, props| {
let (name, set_name) = use_state_classic(cx, || "...?");
cx.render(rsx! {
section { class: "py-12 px-4 text-center"
div { class: "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, {name}"
}
div {
button {
class:"inline-block py-4 px-8 m-2 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
onclick: move |_| set_name("jack")
"Jack!"
}
button {
class:"inline-block py-4 px-8 m-2 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
onclick: move |_| set_name("jill")
"Jill!"
}
}
}
}
})
};
-45
View File
@@ -1,45 +0,0 @@
//! Basic example that renders a simple VNode to the browser.
use std::rc::Rc;
use dioxus_core::prelude::*;
use dioxus_html as dioxus_elements;
use dioxus_web::*;
fn main() {
// Setup logging
// wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
console_error_panic_hook::set_once();
// Run the app
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
}
static App: FC<()> = |cx, props| {
let (contents, set_contents) = use_state_classic(cx, || "asd");
cx.render(rsx! {
div {
class: "flex items-center justify-center flex-col"
div {
class: "flex items-center justify-center"
div {
class: "flex flex-col bg-white rounded p-4 w-full max-w-xs"
div { class: "font-bold text-xl", "Example cloud app" }
div { class: "text-sm text-gray-500", "This is running in the cloud!!" }
div {
class: "flex flex-row items-center justify-center mt-6"
div { class: "font-medium text-6xl", "100%" }
}
div {
class: "flex flex-row justify-between mt-6"
a {
href: "https://www.dioxuslabs.com"
class: "underline"
"Made with dioxus"
}
}
}
}
}
})
};
-37
View File
@@ -1,37 +0,0 @@
//! Basic example that renders a simple VNode to the browser.
use dioxus_core::prelude::*;
use dioxus_html as dioxus_elements;
use dioxus_web::*;
fn main() {
// Setup logging and panic handling
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
console_error_panic_hook::set_once();
// Run the app
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
}
static App: FC<()> = |cx, props| {
let (contents, set_contents) = use_state_classic(cx, || "asd");
cx.render(rsx! {
div { class: "flex items-center justify-center flex-col"
div { class: "flex items-center justify-center"
div { class: "flex flex-col bg-white rounded p-4 w-full max-w-xs"
div { class: "font-bold text-xl", "Example Web app" }
div { class: "text-sm text-gray-500", "This is running in your browser!" }
div { class: "flex flex-row items-center justify-center mt-6"
div { class: "font-medium text-6xl", "100%" }
}
div { class: "flex flex-row justify-between mt-6"
a { href: "https://www.dioxuslabs.com", class: "underline"
"Made with dioxus"
}
}
}
}
}
})
};
-174
View File
@@ -1,174 +0,0 @@
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_html as dioxus_elements;
use dioxus_web::WebsysRenderer;
use std::collections::BTreeMap;
fn main() {
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
console_error_panic_hook::set_once();
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
}
static APP_STYLE: &'static str = include_str!("./todomvc/style.css");
#[derive(PartialEq, Clone, Copy)]
pub enum FilterState {
All,
Active,
Completed,
}
#[derive(Debug, PartialEq, Clone)]
pub struct TodoItem {
pub id: uuid::Uuid,
pub checked: bool,
pub contents: String,
}
static App: FC<()> = |cx, props| {
let (draft, set_draft) = use_state_classic(cx, || "".to_string());
let (filter, set_filter) = use_state_classic(cx, || FilterState::All);
let todos = use_state(cx, || BTreeMap::<uuid::Uuid, TodoItem>::new());
cx.render(rsx!(
div {
id: "app"
div {
header {
class: "header"
h1 {"todos"}
button {
"press me"
onclick: move |evt| {
let contents = draft.clone();
todos.modify(|f| {
let id = uuid::Uuid::new_v4();
f.insert(id.clone(), TodoItem {
id,
checked: false,
contents
});
})
}
}
input {
class: "new-todo"
placeholder: "What needs to be done?"
oninput: move |evt| set_draft(evt.value())
}
}
{ // list
todos
.iter()
.filter(|(id, item)| match filter {
FilterState::All => true,
FilterState::Active => !item.checked,
FilterState::Completed => item.checked,
})
.map(|(id, todo)| {
rsx!{
li {
key: "{id}"
"{todo.contents}"
input {
class: "toggle"
r#type: "checkbox"
"{todo.checked}"
}
}
}
})
}
// filter toggle (show only if the list isn't empty)
{(!todos.is_empty()).then(||
rsx!{
footer {
span {
strong {"10"}
span {"0 items left"}
}
ul {
class: "filters"
{[
("All", "", FilterState::All),
("Active", "active", FilterState::Active),
("Completed", "completed", FilterState::Completed),
]
.iter()
.map(|(name, path, filter)| {
rsx!(
li {
class: "{name}"
a {
href: "{path}"
onclick: move |_| set_filter(filter.clone())
"{name}"
}
}
)
})
}}
}
}
)}
}
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" }
}
}
}
))
};
pub fn FilterToggles(cx: Context<()>) -> DomTree {
// let reducer = recoil::use_callback(&cx, || ());
// let items_left = recoil::use_atom_family(&cx, &TODOS, uuid::Uuid::new_v4());
let toggles = [
("All", "", FilterState::All),
("Active", "active", FilterState::Active),
("Completed", "completed", FilterState::Completed),
]
.iter()
.map(|(name, path, _filter)| {
rsx!(
li {
class: "{name}"
a {
href: "{path}"
// onclick: move |_| reducer.set_filter(&filter)
"{name}"
}
}
)
});
// todo
let item_text = "";
let items_left = "";
cx.render(rsx! {
footer {
span {
strong {"{items_left}"}
span {"{item_text} left"}
}
ul {
class: "filters"
{toggles}
}
}
})
}
-29
View File
@@ -1,29 +0,0 @@
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_html as dioxus_elements;
use dioxus_web::WebsysRenderer;
fn main() {
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
console_error_panic_hook::set_once();
log::info!("hello world");
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(JonsFavoriteCustomApp));
}
fn JonsFavoriteCustomApp(cx: Context<()>) -> DomTree {
let items = (0..20).map(|f| {
rsx! {
li {"{f}"}
}
});
cx.render(rsx! {
div {
"list"
ul {
{items}
}
}
})
}
-23
View File
@@ -1,23 +0,0 @@
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_html as dioxus_elements;
use dioxus_web::WebsysRenderer;
fn main() {
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
console_error_panic_hook::set_once();
log::info!("hello world");
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
}
pub static Example: FC<()> = |cx, props| {
cx.render(rsx! {
div {
span {
class: "px-2 py-1 flex w-36 mt-4 items-center text-xs rounded-md font-semibold text-yellow-500 bg-yellow-100"
"DUE DATE : 18 JUN"
}
}
})
};
-193
View File
@@ -1,193 +0,0 @@
//! Example: Calculator
//! -------------------
//!
//! Some components benefit through the use of "Models". Models are a single block of encapsulated state that allow mutative
//! methods to be performed on them. Dioxus exposes the ability to use the model pattern through the "use_model" hook.
//!
//! Models are commonly used in the "Model-View-Component" approach for building UI state.
//!
//! `use_model` is basically just a fancy wrapper around set_state, but saves a "working copy" of the new state behind a
//! RefCell. To modify the working copy, you need to call "get_mut" which returns the RefMut. This makes it easy to write
//! fully encapsulated apps that retain a certain feel of native Rusty-ness. A calculator app is a good example of when this
//! is useful.
//!
//! Do note that "get_mut" returns a `RefMut` (a lock over a RefCell). If two `RefMut`s are held at the same time (ie in a loop)
//! the RefCell will panic and crash. You can use `try_get_mut` or `.modify` to avoid this problem, or just not hold two
//! RefMuts at the same time.
use dioxus::events::on::*;
use dioxus::prelude::*;
use dioxus_core as dioxus;
use dioxus_html as dioxus_elements;
use dioxus_web::WebsysRenderer;
const STYLE: &str = include_str!("../../../examples/assets/calculator.css");
fn main() {
// Setup logging
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
console_error_panic_hook::set_once();
// Run the app
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
}
static App: FC<()> = |cx, props| {
let state = use_model(&cx, || Calculator::new());
let clear_display = state.display_value.eq("0");
let clear_text = if clear_display { "C" } else { "AC" };
let formatted = state.formatted_display();
cx.render(rsx! {
div { id: "wrapper"
div { class: "app", style { "{STYLE}" }
div { class: "calculator", onkeypress: move |evt| state.get_mut().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.get_mut().clear_display(), "{clear_text}" }
CalculatorKey { name: "key-sign", onclick: move |_| state.get_mut().toggle_sign(), "±"}
CalculatorKey { name: "key-percent", onclick: move |_| state.get_mut().toggle_percent(), "%"}
}
div { class: "digit-keys"
CalculatorKey { name: "key-0", onclick: move |_| state.get_mut().input_digit(0), "0" }
CalculatorKey { name: "key-dot", onclick: move |_| state.get_mut().input_dot(), "●" }
{(1..10).map(move |k| rsx!{
CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| state.get_mut().input_digit(k), "{k}" }
})}
}
}
div { class: "operator-keys"
CalculatorKey { name:"key-divide", onclick: move |_| state.get_mut().set_operator(Operator::Div), "÷" }
CalculatorKey { name:"key-multiply", onclick: move |_| state.get_mut().set_operator(Operator::Mul), "×" }
CalculatorKey { name:"key-subtract", onclick: move |_| state.get_mut().set_operator(Operator::Sub), "−" }
CalculatorKey { name:"key-add", onclick: move |_| state.get_mut().set_operator(Operator::Add), "+" }
CalculatorKey { name:"key-equals", onclick: move |_| state.get_mut().perform_operation(), "=" }
}
}
}
}
}
})
};
#[derive(Props)]
struct CalculatorKeyProps<'a> {
name: &'static str,
onclick: &'a dyn Fn(MouseEvent),
}
fn CalculatorKey<'a, 'r>(cx: Context<'a, CalculatorKeyProps<'r>>) -> DomTree<'a> {
cx.render(rsx! {
button {
class: "calculator-key {cx.name}"
onclick: {cx.onclick}
{cx.children()}
}
})
}
#[derive(Clone)]
struct Calculator {
display_value: String,
operator: Option<Operator>,
waiting_for_operand: bool,
cur_val: f64,
}
#[derive(Clone)]
enum Operator {
Add,
Sub,
Mul,
Div,
}
impl Calculator {
fn new() -> Self {
Calculator {
display_value: "0".to_string(),
operator: None,
waiting_for_operand: false,
cur_val: 0.0,
}
}
fn formatted_display(&self) -> String {
use separator::Separatable;
self.display_value
.parse::<f64>()
.unwrap()
.separated_string()
}
fn clear_display(&mut self) {
self.display_value = "0".to_string();
}
fn input_digit(&mut self, digit: u8) {
let content = digit.to_string();
if self.waiting_for_operand || self.display_value == "0" {
self.waiting_for_operand = false;
self.display_value = content;
} else {
self.display_value.push_str(content.as_str());
}
}
fn input_dot(&mut self) {
if self.display_value.find(".").is_none() {
self.display_value.push_str(".");
}
}
fn perform_operation(&mut self) {
if let Some(op) = &self.operator {
let rhs = self.display_value.parse::<f64>().unwrap();
let new_val = match op {
Operator::Add => self.cur_val + rhs,
Operator::Sub => self.cur_val - rhs,
Operator::Mul => self.cur_val * rhs,
Operator::Div => self.cur_val / rhs,
};
self.cur_val = new_val;
self.display_value = new_val.to_string();
self.operator = None;
}
}
fn toggle_sign(&mut self) {
if self.display_value.starts_with("-") {
self.display_value = self.display_value.trim_start_matches("-").to_string();
} else {
self.display_value = format!("-{}", self.display_value);
}
}
fn toggle_percent(&mut self) {
self.display_value = (self.display_value.parse::<f64>().unwrap() / 100.0).to_string();
}
fn backspace(&mut self) {
if !self.display_value.as_str().eq("0") {
self.display_value.pop();
}
}
fn set_operator(&mut self, operator: Operator) {
self.operator = Some(operator);
self.cur_val = self.display_value.parse::<f64>().unwrap();
self.waiting_for_operand = true;
}
fn handle_keydown(&mut self, evt: KeyboardEvent) {
match evt.key_code() {
KeyCode::Backspace => self.backspace(),
KeyCode::_0 => self.input_digit(0),
KeyCode::_1 => self.input_digit(1),
KeyCode::_2 => self.input_digit(2),
KeyCode::_3 => self.input_digit(3),
KeyCode::_4 => self.input_digit(4),
KeyCode::_5 => self.input_digit(5),
KeyCode::_6 => self.input_digit(6),
KeyCode::_7 => self.input_digit(7),
KeyCode::_8 => self.input_digit(8),
KeyCode::_9 => self.input_digit(9),
KeyCode::Add => self.operator = Some(Operator::Add),
KeyCode::Subtract => self.operator = Some(Operator::Sub),
KeyCode::Divide => self.operator = Some(Operator::Div),
KeyCode::Multiply => self.operator = Some(Operator::Mul),
_ => {}
}
}
}
-13
View File
@@ -1,13 +0,0 @@
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
#[derive(Props)]
struct MyProps<'a> {
blah: u128,
b: &'a (),
}
fn main() {
// let p = unsafe { MyProps {}.memoize(&MyProps {}) };
// dbg!(p);
}
-20
View File
@@ -1,20 +0,0 @@
// use dioxus_core::prelude::*;
// use dioxus_web::WebsysRenderer;
// fn main() {
// wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
// }
// fn Example(cx: Context, props: ()) -> DomTree {
// let user_data = use_sql_query(&cx, USER_DATA_QUERY);
// cx.render(rsx! {
// h1 { "Hello, {username}"}
// button {
// "Delete user"
// onclick: move |_| user_data.delete()
// }
// })
// }
fn main() {}
-86
View File
@@ -1,86 +0,0 @@
#![allow(non_snake_case)]
use std::rc::Rc;
use dioxus::{events::on::MouseEvent, prelude::*};
use dioxus_core as dioxus;
use dioxus_web::WebsysRenderer;
use dioxus_html as dioxus_elements;
fn main() {
wasm_logger::init(wasm_logger::Config::new(log::Level::Trace));
console_error_panic_hook::set_once();
wasm_bindgen_futures::spawn_local(async {
let props = ExampleProps {
initial_name: "..?",
};
WebsysRenderer::new_with_props(Example, props)
.run()
.await
.unwrap()
});
}
#[derive(PartialEq, Props)]
struct ExampleProps {
initial_name: &'static str,
}
pub static Example: FC<ExampleProps> = |cx, props|{
let name = use_state(cx, move || cx.initial_name);
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, {name}"
}
CustomButton { name: "Jack!", handler: move |_| name.set("Jack") }
CustomButton { name: "Jill!", handler: move |_| name.set("Jill") }
CustomButton { name: "Bob!", handler: move |_| name.set("Bob")}
Placeholder {val: name}
Placeholder {val: name}
}
})
};
#[derive(Props)]
struct ButtonProps<'src, F: Fn(MouseEvent)> {
name: &'src str,
handler: F,
}
fn CustomButton<'a, F: Fn(MouseEvent)>(cx: Context<'a, ButtonProps<'a, F>>) -> DomTree {
cx.render(rsx!{
button {
class: "inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
onmouseover: {&cx.handler}
"{cx.name}"
}
})
}
impl<F: Fn(MouseEvent)> PartialEq for ButtonProps<'_, F> {
fn eq(&self, other: &Self) -> bool {
false
}
}
#[derive(Props, PartialEq)]
struct PlaceholderProps {
val: &'static str,
}
fn Placeholder(cx: Context<PlaceholderProps>) -> DomTree {
cx.render(rsx! {
div {
"child: {cx.val}"
}
})
}
-126
View File
@@ -1,126 +0,0 @@
use std::{collections::HashMap, rc::Rc};
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_html as dioxus_elements;
use dioxus_web::WebsysRenderer;
static APP_STYLE: &'static str = include_str!("./todomvc/style.css");
fn main() {
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
}
#[derive(PartialEq)]
pub enum FilterState {
All,
Active,
Completed,
}
#[derive(Debug, PartialEq, Clone)]
pub struct TodoItem {
pub id: uuid::Uuid,
pub checked: bool,
pub contents: String,
}
pub fn App(cx: Context<()>) -> DomTree {
let (draft, set_draft) = use_state_classic(cx, || "".to_string());
let (todos, set_todos) = use_state_classic(cx, || HashMap::<uuid::Uuid, Rc<TodoItem>>::new());
let (filter, set_filter) = use_state_classic(cx, || FilterState::All);
let filtered_todos = todos.iter().filter(move |(id, item)| match filter {
FilterState::All => true,
FilterState::Active => !item.checked,
FilterState::Completed => item.checked,
});
let items_left = filtered_todos.clone().count();
let item_text = match items_left {
1 => "item",
_ => "items",
};
cx.render(rsx! {
div { id: "app"
div {
header { class: "header"
h1 {"todos"}
input {
class: "new-todo"
placeholder: "What needs to be done?"
value: "{draft}"
oninput: move |evt| set_draft(evt.value())
}
}
{filtered_todos.map(|(id, item)| {
rsx!(TodoEntry {
key: "{id}",
item: item.clone()
})
})}
// filter toggle (show only if the list isn't empty)
{(!todos.is_empty()).then(|| rsx!(
footer {
span {
strong {"{items_left}"}
span {"{item_text} left"}
}
ul {
class: "filters"
li { class: "All", a { href: "", onclick: move |_| set_filter(FilterState::All), "All" }}
li { class: "Active", a { href: "active", onclick: move |_| set_filter(FilterState::Active), "Active" }}
li { class: "Completed", a { href: "completed", onclick: move |_| set_filter(FilterState::Completed), "Completed" }}
}
}
))}
}
// footer
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 {
item: Rc<TodoItem>,
}
pub fn TodoEntry(cx: Context<TodoEntryProps>) -> DomTree {
let (is_editing, set_is_editing) = use_state_classic(cx, || false);
let contents = "";
let todo = TodoItem {
checked: false,
contents: "asd".to_string(),
id: uuid::Uuid::new_v4(),
};
cx.render(rsx! (
li {
"{todo.id}"
input {
class: "toggle"
r#type: "checkbox"
"{todo.checked}"
}
{is_editing.then(|| rsx!{
input {
value: "{contents}"
}
})}
}
))
}
@@ -1,42 +0,0 @@
use crate::recoil;
use crate::state::{FilterState, TODOS};
use dioxus_core::prelude::*;
pub fn FilterToggles(cx: Context<()>) -> DomTree {
let reducer = recoil::use_callback(&cx, || ());
let items_left = recoil::use_atom_family(&cx, &TODOS, uuid::Uuid::new_v4());
let toggles = [
("All", "", FilterState::All),
("Active", "active", FilterState::Active),
("Completed", "completed", FilterState::Completed),
]
.iter()
.map(|(name, path, filter)| {
rsx!(li {
class: "{name}"
a {
"{name}"
href: "{path}"
onclick: move |_| reducer.set_filter(&filter)
}
})
});
// todo
let item_text = "";
let items_left = "";
cx.render(rsx! {
footer {
span {
strong {"{items_left}"}
span {"{item_text} left"}
}
ul {
class: "filters"
{toggles}
}
}
})
}
-39
View File
@@ -1,39 +0,0 @@
use dioxus_core as dioxus;
use dioxus_html as dioxus_elements;
use dioxus_web::{prelude::*, WebsysRenderer};
// mod filtertoggles;
// mod recoil;
// mod state;
// mod todoitem;
// mod todolist;
static APP_STYLE: &'static str = include_str!("./style.css");
fn main() {
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(|cx, props| {
cx.render(rsx! {
div {
id: "app"
// style { "{APP_STYLE}" }
// list
// todolist::TodoList {}
// footer
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" }
}
}
}
})
}))
}
-102
View File
@@ -1,102 +0,0 @@
use dioxus_core::context::Context;
pub struct RecoilContext<T: 'static> {
_inner: T,
}
impl<T: 'static> RecoilContext<T> {
/// Get the value of an atom. Returns a reference to the underlying data.
pub fn get(&self) {}
/// Replace an existing value with a new value
///
/// This does not replace the value instantly, and all calls to "get" within the current scope will return
pub fn set(&self) {}
// Modify lets you modify the value in place. However, because there's no previous value around to compare
// the new one with, we are unable to memoize the change. As such, all downsteam users of this Atom will
// be updated, causing all subsrcibed components to re-render.
//
// This is fine for most values, but might not be performant when dealing with collections. For collections,
// use the "Family" variants as these will stay memoized for inserts, removals, and modifications.
//
// Note - like "set" this won't propogate instantly. Once all "gets" are dropped, only then will we run the
pub fn modify(&self) {}
}
pub fn use_callback<'a, G>(c: &Context<'a>, f: impl Fn() -> G) -> &'a RecoilContext<G> {
todo!()
}
pub fn use_atom<T: PartialEq, O>(c: &Context, t: &'static Atom<T>) -> O {
todo!()
}
pub fn use_batom<T: PartialEq, O>(c: &Context, t: impl Readable) -> O {
todo!()
}
pub trait Readable {}
impl<T: PartialEq> Readable for &'static Atom<T> {}
impl<K: PartialEq, V: PartialEq> Readable for &'static AtomFamily<K, V> {}
pub fn use_atom_family<'a, K: PartialEq, V: PartialEq>(
c: &Context<'a>,
t: &'static AtomFamily<K, V>,
g: K,
) -> &'a V {
todo!()
}
pub use atoms::{atom, Atom};
pub use atoms::{atom_family, AtomFamily};
mod atoms {
use super::*;
pub struct AtomBuilder<T: PartialEq> {
pub key: String,
pub manual_init: Option<Box<dyn Fn() -> T>>,
_never: std::marker::PhantomData<T>,
}
impl<T: PartialEq> AtomBuilder<T> {
pub fn new() -> Self {
Self {
key: uuid::Uuid::new_v4().to_string(),
manual_init: None,
_never: std::marker::PhantomData {},
}
}
pub fn init<A: Fn() -> T + 'static>(&mut self, f: A) {
self.manual_init = Some(Box::new(f));
}
pub fn set_key(&mut self, _key: &'static str) {}
}
pub struct atom<T: PartialEq>(pub fn(&mut AtomBuilder<T>) -> T);
pub type Atom<T: PartialEq> = atom<T>;
pub struct AtomFamilyBuilder<K, V> {
_never: std::marker::PhantomData<(K, V)>,
}
pub struct atom_family<K: PartialEq, V: PartialEq>(pub fn(&mut AtomFamilyBuilder<K, V>));
pub type AtomFamily<K: PartialEq, V: PartialEq> = atom_family<K, V>;
}
pub use selectors::selector;
mod selectors {
pub struct SelectorBuilder<Out, const Built: bool> {
_p: std::marker::PhantomData<Out>,
}
impl<O> SelectorBuilder<O, false> {
pub fn getter(self, f: impl Fn(()) -> O) -> SelectorBuilder<O, true> {
todo!()
// std::rc::Rc::pin(value)
// todo!()
}
}
pub struct selector<O>(pub fn(SelectorBuilder<O, false>) -> SelectorBuilder<O, true>);
}
-43
View File
@@ -1,43 +0,0 @@
use crate::recoil::*;
pub static TODOS: AtomFamily<uuid::Uuid, TodoItem> = atom_family(|_| {});
pub static FILTER: Atom<FilterState> = atom(|_| FilterState::All);
pub static SHOW_ALL_TODOS: selector<bool> = selector(|g| g.getter(|f| false));
#[derive(PartialEq)]
pub enum FilterState {
All,
Active,
Completed,
}
#[derive(Debug, PartialEq, Clone)]
pub struct TodoItem {
pub id: uuid::Uuid,
pub checked: bool,
pub contents: String,
}
impl RecoilContext<()> {
pub fn add_todo(&self, contents: String) {}
pub fn remove_todo(&self, id: &uuid::Uuid) {
// TODOS.with().remove(id)
}
pub fn select_all_todos(&self) {}
pub fn toggle_todo(&self, id: &uuid::Uuid) {}
pub fn clear_completed(&self) {
// let (set, get) = (self.set, self.get);
// TOODS
// .get(&cx)
// .iter()
// .filter(|(k, v)| v.checked)
// .map(|(k, v)| TODOS.remove(&cx, k));
}
pub fn set_filter(&self, filter: &FilterState) {}
}
-376
View File
@@ -1,376 +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;
}
}
-29
View File
@@ -1,29 +0,0 @@
use super::state::TODOS;
use crate::recoil::use_atom_family;
use dioxus_core::prelude::*;
#[derive(PartialEq, Props)]
pub struct TodoEntryProps {
id: uuid::Uuid,
}
pub fn TodoEntry(cx: Context, props: &TodoEntryProps) -> DomTree {
let (is_editing, set_is_editing) = use_state(cx, || false);
let todo = use_atom_family(&cx, &TODOS, cx.id);
cx.render(rsx! (
li {
"{todo.id}"
input {
class: "toggle"
type: "checkbox"
"{todo.checked}"
}
{is_editing.then(|| rsx!(
input {
value: "{todo.contents}"
}
))}
}
))
}
-49
View File
@@ -1,49 +0,0 @@
use crate::{
filtertoggles,
recoil::use_atom,
state::{FilterState, TodoItem, FILTER, TODOS},
todoitem::TodoEntry,
};
use dioxus_core::prelude::*;
pub fn TodoList(cx: Context<()>) -> DomTree {
let (draft, set_draft) = use_state(cx, || "".to_string());
let (todos, _) = use_state(cx, || Vec::<TodoItem>::new());
let filter = use_atom(&cx, &FILTER);
cx.render(rsx! {
div {
header {
class: "header"
h1 {"todos"}
input {
class: "new-todo"
placeholder: "What needs to be done?"
value: "{draft}"
oninput: move |evt| set_draft(evt.value)
}
}
{ // list
todos
.iter()
.filter(|item| match filter {
FilterState::All => true,
FilterState::Active => !item.checked,
FilterState::Completed => item.checked,
})
.map(|item| {
rsx!(TodoEntry {
key: "{order}",
id: item.id,
})
})
}
// filter toggle (show only if the list isn't empty)
{(!todos.is_empty()).then(||
rsx!( filtertoggles::FilterToggles {})
)}
}
})
}
-167
View File
@@ -1,167 +0,0 @@
use std::{collections::HashMap, rc::Rc};
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_html as dioxus_elements;
use dioxus_web::WebsysRenderer;
static APP_STYLE: &'static str = include_str!("./todomvc/style.css");
fn main() {
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
}
#[derive(PartialEq)]
pub enum FilterState {
All,
Active,
Completed,
}
#[derive(Debug, PartialEq, Clone)]
pub struct TodoItem {
pub id: uuid::Uuid,
pub checked: bool,
pub contents: String,
}
// =======================
// Components
// =======================
pub fn App(cx: Context<()>) -> DomTree {
cx.render(rsx! {
div {
id: "app"
// list
TodoList {}
// footer
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" }
}
}
}
})
}
pub fn TodoList(cx: Context<()>) -> DomTree {
let (draft, set_draft) = use_state_classic(cx, || "".to_string());
let (todos, set_todos) = use_state_classic(cx, || HashMap::<uuid::Uuid, Rc<TodoItem>>::new());
let (filter, set_filter) = use_state_classic(cx, || FilterState::All);
cx.render(rsx! {
div {
header {
class: "header"
h1 {"todos"}
input {
class: "new-todo"
placeholder: "What needs to be done?"
value: "{draft}"
oninput: move |evt| set_draft(evt.value())
}
}
{ // list
todos
.iter()
.filter(|(id, item)| match filter {
FilterState::All => true,
FilterState::Active => !item.checked,
FilterState::Completed => item.checked,
})
.map(|(id, item)| {
// TodoEntry!();
todo!()
// rsx!(TodoEntry {
// key: "{order}",
// item: item.clone()
// })
})
}
// filter toggle (show only if the list isn't empty)
{(!todos.is_empty()).then(||
rsx!( FilterToggles {})
)}
}
})
}
#[derive(PartialEq, Props)]
pub struct TodoEntryProps {
item: Rc<TodoItem>,
}
pub fn TodoEntry(cx: Context<TodoEntryProps>) -> DomTree {
let (is_editing, set_is_editing) = use_state_classic(cx, || false);
let contents = "";
let todo = TodoItem {
checked: false,
contents: "asd".to_string(),
id: uuid::Uuid::new_v4(),
};
cx.render(rsx! (
li {
"{todo.id}"
input {
class: "toggle"
r#type: "checkbox"
"{todo.checked}"
}
{is_editing.then(|| rsx!{
input {
value: "{contents}"
}
})}
}
))
}
pub fn FilterToggles(cx: Context<()>) -> DomTree {
let toggles = [
("All", "", FilterState::All),
("Active", "active", FilterState::Active),
("Completed", "completed", FilterState::Completed),
]
.iter()
.map(|(name, path, filter)| {
rsx!(
li {
class: "{name}"
a {
href: "{path}"
// onclick: move |_| reducer.set_filter(&filter)
"{name}"
}
}
)
});
// todo
let item_text = "";
let items_left = "";
cx.render(rsx! {
footer {
span {
strong {"{items_left}"}
span {"{item_text} left"}
}
ul {
class: "filters"
{toggles}
}
}
})
}
-200
View File
@@ -1,200 +0,0 @@
//! Example: TODOVMC - One file
//! ---------------------------
//! This example shows how to build a one-file TODO MVC app with Dioxus and Recoil.
//! This project is confined to a single file to showcase the suggested patterns
//! for building a small but mighty UI with Dioxus without worrying about project structure.
//!
//! If you want an example on recommended project structure, check out the TodoMVC folder
//!
//! Here, we show to use Dioxus' Recoil state management solution to simplify app logic
#![allow(non_snake_case)]
use dioxus_core as dioxus;
use dioxus_web::dioxus::prelude::*;
use std::collections::HashMap;
use uuid::Uuid;
#[derive(PartialEq, Clone, Copy)]
pub enum FilterState {
All,
Active,
Completed,
}
#[derive(Debug, PartialEq, Clone)]
pub struct TodoItem {
pub id: Uuid,
pub checked: bool,
pub contents: String,
}
// Declare our global app state
const TODO_LIST: AtomHashMap<Uuid, TodoItem> = |_| {};
const FILTER: Atom<FilterState> = |_| FilterState::All;
const TODOS_LEFT: Selector<usize> = |api| api.get(&TODO_LIST).len();
// Implement a simple abstraction over sets/gets of multiple atoms
struct TodoManager(RecoilApi);
impl TodoManager {
fn add_todo(&self, contents: String) {
let item = TodoItem {
checked: false,
contents,
id: Uuid::new_v4(),
};
self.0.modify(&TODO_LIST, move |list| {
list.insert(item.id, item);
});
}
fn remove_todo(&self, id: &Uuid) {
self.0.modify(&TODO_LIST, move |list| {
list.remove(id);
})
}
fn select_all_todos(&self) {
self.0.modify(&TODO_LIST, move |list| {
for item in list.values_mut() {
item.checked = true;
}
})
}
fn toggle_todo(&self, id: &Uuid) {
self.0.modify(&TODO_LIST, move |list| {
list.get_mut(id).map(|item| item.checked = !item.checked)
});
}
fn clear_completed(&self) {
self.0.modify(&TODO_LIST, move |list| {
*list = list.drain().filter(|(_, item)| !item.checked).collect();
})
}
fn set_filter(&self, filter: &FilterState) {
self.0.modify(&FILTER, move |f| *f = *filter);
}
}
pub fn TodoList(cx: Context<()>) -> DomTree {
let draft = use_state(cx, || "".to_string());
let todos = use_read(&cx, &TODO_LIST);
let filter = use_read(&cx, &FILTER);
let todolist = todos
.values()
.filter(|item| match filter {
FilterState::All => true,
FilterState::Active => !item.checked,
FilterState::Completed => item.checked,
})
.map(|item| {
rsx!(TodoEntry {
key: "{order}",
id: item.id,
})
});
rsx! { in cx,
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)
}
}
{todolist}
// rsx! accepts optionals, so we suggest the `then` method in place of ternary
{(!todos.is_empty()).then(|| rsx!( FilterToggles {}) )}
}
}
}
#[derive(PartialEq, Props)]
pub struct TodoEntryProps {
id: Uuid,
}
pub fn TodoEntry(cx: Context, props: &TodoEntryProps) -> DomTree {
let (is_editing, set_is_editing) = use_state_classic(cx, || false);
let todo = use_read(&cx, &TODO_LIST).get(&cx.id).unwrap();
cx.render(rsx! (
li {
"{todo.id}"
input {
class: "toggle"
type: "checkbox"
"{todo.checked}"
}
{is_editing.then(|| rsx!(
input {
value: "{todo.contents}"
}
))}
}
))
}
pub fn FilterToggles(cx: Context<()>) -> DomTree {
let reducer = TodoManager(use_recoil_api(cx));
let items_left = use_read(cx, &TODOS_LEFT);
let item_text = match items_left {
1 => "item",
_ => "items",
};
let toggles = rsx! {
ul {
class: "filters"
li { class: "All", a { href: "", onclick: move |_| reducer.set_filter(&FilterState::All), "All" }}
li { class: "Active", a { href: "active", onclick: move |_| reducer.set_filter(&FilterState::Active), "Active" }}
li { class: "Completed", a { href: "completed", onclick: move |_| reducer.set_filter(&FilterState::Completed), "Completed" }}
}
};
rsx! { in cx,
footer {
span {
strong {"{items_left}"}
span { "{item_text} left" }
}
{toggles}
}
}
}
pub fn Footer(cx: Context<()>) -> DomTree {
rsx! { in cx,
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" }
}
}
}
}
const APP_STYLE: &'static str = include_str!("./todomvc/style.css");
fn App(cx: Context<()>) -> DomTree {
use_init_recoil_root(cx, |_| {});
rsx! { in cx,
div { id: "app"
TodoList {}
Footer {}
}
}
}
fn main() {
wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(App));
}
-47
View File
@@ -1,47 +0,0 @@
//! basic example that renders a simple VNode to the page :)
//!
//!
//!
use dioxus_core::prelude::*;
use dioxus_web::*;
fn main() {
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
console_error_panic_hook::set_once();
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(|cx, props| {
cx.render(html! {
<div>
<div class="flex items-center justify-center flex-col">
<div class="flex items-center justify-center">
<div class="flex flex-col bg-white rounded p-4 w-full max-w-xs">
// Title
<div class="font-bold text-xl">
"Jon's awesome site!!"
</div>
// Subtext / description
<div class="text-sm text-gray-500">
"He worked so hard on it :)"
</div>
<div class="flex flex-row items-center justify-center mt-6">
// Main number
<div class="font-medium text-6xl">
"1337"
</div>
</div>
// Try another
<div class="flex flex-row justify-between mt-6">
// <a href=format!("http://localhost:8080/fib/{}", other_fib_to_try) class="underline">
"Legit made my own React"
// </a>
</div>
</div>
</div>
</div>
</div>
})
}));
}
+2 -2
View File
@@ -3,8 +3,8 @@ use dioxus::prelude::*;
use im_rc::HashMap;
use std::rc::Rc;
fn main() -> anyhow::Result<()> {
dioxus::desktop::launch(App, |c| c)
fn main() {
dioxus::desktop::launch(App, |c| c);
}
#[derive(PartialEq)]
-1
View File
@@ -1,5 +1,4 @@
use dioxus::events::on::MouseEvent;
use dioxus::events::DioxusEvent;
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_core_macro::*;
+59 -47
View File
@@ -102,6 +102,10 @@ impl<'src> Context<'src> {
self.scope.memoized_updater.clone()
}
pub fn needs_update(&self) {
(self.scope.memoized_updater)()
}
/// Schedule an update for any component given its ScopeId.
///
/// A component's ScopeId can be obtained from `use_hook` or the [`Context::scope_id`] method.
@@ -178,65 +182,69 @@ impl<'src> Context<'src> {
/// struct SharedState(&'static str);
///
/// static App: FC<()> = |cx, props|{
/// cx.use_provide_state(|| SharedState("world"));
/// cx.provide_state(SharedState("world"));
/// rsx!(cx, Child {})
/// }
///
/// static Child: FC<()> = |cx, props|{
/// let state = cx.use_consume_state::<SharedState>();
/// let state = cx.consume_state::<SharedState>();
/// rsx!(cx, div { "hello {state.0}" })
/// }
/// ```
pub fn use_provide_state<T, F>(self, init: F) -> &'src Rc<T>
pub fn provide_state<T>(self, value: T)
where
T: 'static,
F: FnOnce() -> T,
{
let is_initialized = self.use_hook(
|_| false,
|s| {
let i = *s;
*s = true;
i
},
|_| {},
);
if !is_initialized {
let existing = self
.scope
.shared_contexts
.borrow_mut()
.insert(TypeId::of::<T>(), Rc::new(init()));
if existing.is_some() {
log::warn!(
"A shared state was replaced with itself. \
This is does not result in a panic, but is probably not what you are trying to do"
);
}
}
self.use_consume_state().unwrap()
self.scope
.shared_contexts
.borrow_mut()
.insert(TypeId::of::<T>(), Rc::new(value))
.map(|f| f.downcast::<T>().ok())
.flatten();
}
/// Uses a context, storing the cached value around
/// Try to retrive a SharedState with type T from the any parent Scope.
pub fn consume_state<T: 'static>(self) -> Option<Rc<T>> {
let getter = &self.scope.shared.get_shared_context;
let ty = TypeId::of::<T>();
let idx = self.scope.our_arena_idx;
getter(idx, ty).map(|f| f.downcast().unwrap())
}
/// Create a new subtree with this scope as the root of the subtree.
///
/// If a context is not found on the first search, then this call will be "dud", always returning "None" even if a
/// context was added later. This allows using another hook as a fallback
/// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
/// the mutations to the correct window/portal/subtree.
///
pub fn use_consume_state<T: 'static>(self) -> Option<&'src Rc<T>> {
struct UseContextHook<C>(Option<Rc<C>>);
self.use_hook(
move |_| {
let getter = &self.scope.shared.get_shared_context;
let ty = TypeId::of::<T>();
let idx = self.scope.our_arena_idx;
UseContextHook(getter(idx, ty).map(|f| f.downcast().unwrap()))
},
move |hook| hook.0.as_ref(),
|_| {},
)
/// This method
///
/// # Example
///
/// ```rust
/// static App: FC<()> = |cx, props| {
/// todo!();
/// rsx!(cx, div { "Subtree {id}"})
/// };
/// ```
pub fn create_subtree(self) -> Option<u32> {
self.scope.new_subtree()
}
/// Get the subtree ID that this scope belongs to.
///
/// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
/// the mutations to the correct window/portal/subtree.
///
/// # Example
///
/// ```rust
/// static App: FC<()> = |cx, props| {
/// let id = cx.get_current_subtree();
/// rsx!(cx, div { "Subtree {id}"})
/// };
/// ```
pub fn get_current_subtree(self) -> u32 {
self.scope.subtree()
}
/// Store a value between renders
@@ -271,13 +279,17 @@ impl<'src> Context<'src> {
Output: 'src,
Init: FnOnce(usize) -> State,
Run: FnOnce(&'src mut State) -> Output,
Cleanup: FnOnce(&mut State) + 'static,
Cleanup: FnOnce(Box<State>) + 'static,
{
// If the idx is the same as the hook length, then we need to add the current hook
if self.scope.hooks.at_end() {
self.scope.hooks.push_hook(
initializer(self.scope.hooks.len()),
Box::new(|raw| cleanup(raw.downcast_mut::<State>().unwrap())),
Box::new(|raw| {
//
let s = raw.downcast::<State>().unwrap();
cleanup(s);
}),
);
}
+114 -71
View File
@@ -221,18 +221,12 @@ impl<'bump> DiffMachine<'bump> {
MountType::Replace { old } => {
if let Some(old_id) = old.try_mounted_id() {
self.mutations.replace_with(old_id, nodes_created as u32);
self.remove_nodes(Some(old));
} else {
let mut iter = RealChildIterator::new(old, self.vdom);
let first = iter.next().unwrap();
self.mutations
.replace_with(first.mounted_id(), nodes_created as u32);
self.remove_nodes(iter);
}
}
MountType::ReplaceByElementId { el } => {
if let Some(old) = el {
self.mutations.replace_with(old, nodes_created as u32);
if let Some(id) = self.find_first_element_id(old) {
self.mutations.replace_with(id, nodes_created as u32);
}
self.remove_nodes(Some(old));
}
}
@@ -326,13 +320,10 @@ impl<'bump> DiffMachine<'bump> {
}
for attr in *attributes {
self.mutations.set_attribute(attr);
self.mutations.set_attribute(attr, real_id.as_u64());
}
if !children.is_empty() {
// self.stack.element_id_stack.push(real_id);
// push our element_id onto the stack
// drop our element off the stack
self.stack.create_children(children, MountType::Append);
}
}
@@ -352,12 +343,12 @@ impl<'bump> DiffMachine<'bump> {
let parent_scope = self.vdom.get_scope(parent_idx).unwrap();
let new_idx = self.vdom.insert_scope_with_key(|new_idx| {
let height = parent_scope.height + 1;
Scope::new(
caller,
new_idx,
Some(parent_idx),
height,
parent_scope.height + 1,
parent_scope.subtree(),
ScopeChildren(vcomponent.children),
shared,
)
@@ -379,11 +370,24 @@ impl<'bump> DiffMachine<'bump> {
let new_component = self.vdom.get_scope_mut(new_idx).unwrap();
log::debug!("initializing component {:?}", new_idx);
// Run the scope for one iteration to initialize it
if new_component.run_scope(self.vdom) {
// Take the node that was just generated from running the component
let nextnode = new_component.frames.fin_head();
self.stack.create_component(new_idx, nextnode);
//
/*
tree_item {
}
*/
if new_component.is_subtree_root.get() {
self.stack.push_subtree();
}
}
// Finally, insert this scope as a seen node.
@@ -405,7 +409,7 @@ impl<'bump> DiffMachine<'bump> {
(Fragment(old), Fragment(new)) => self.diff_fragment_nodes(old, new),
(Anchor(old), Anchor(new)) => new.dom_id.set(old.dom_id.get()),
(Suspended(old), Suspended(new)) => self.diff_suspended_nodes(old, new),
(Element(old), Element(new)) => self.diff_element_nodes(old, new, new_node),
(Element(old), Element(new)) => self.diff_element_nodes(old, new, old_node, new_node),
// Anything else is just a basic replace and create
(
@@ -420,9 +424,7 @@ impl<'bump> DiffMachine<'bump> {
fn diff_text_nodes(&mut self, old: &'bump VText<'bump>, new: &'bump VText<'bump>) {
if let Some(root) = old.dom_id.get() {
if old.text != new.text {
self.mutations.push_root(root);
self.mutations.set_text(new.text);
self.mutations.pop();
self.mutations.set_text(new.text, root.as_u64());
}
new.dom_id.set(Some(root));
@@ -433,9 +435,10 @@ impl<'bump> DiffMachine<'bump> {
&mut self,
old: &'bump VElement<'bump>,
new: &'bump VElement<'bump>,
old_node: &'bump VNode<'bump>,
new_node: &'bump VNode<'bump>,
) {
let root = old.dom_id.get();
let root = old.dom_id.get().unwrap();
// If the element type is completely different, the element needs to be re-rendered completely
// This is an optimization React makes due to how users structure their code
@@ -446,32 +449,19 @@ impl<'bump> DiffMachine<'bump> {
// issue is that we need the "vnode" but this method only has the velement
self.stack.push_nodes_created(0);
self.stack.push(DiffInstruction::Mount {
and: MountType::ReplaceByElementId {
el: old.dom_id.get(),
},
and: MountType::Replace { old: old_node },
});
self.create_element_node(new, new_node);
return;
}
new.dom_id.set(root);
new.dom_id.set(Some(root));
// todo: attributes currently rely on the element on top of the stack, but in theory, we only need the id of the
// element to modify its attributes.
// it would result in fewer instructions if we just set the id directly.
// it would also clean up this code some, but that's not very important anyways
// Don't push the root if we don't have to
let mut has_comitted = false;
let mut please_commit = |edits: &mut Vec<DomEdit>| {
if !has_comitted {
has_comitted = true;
edits.push(PushRoot {
root: root.unwrap().as_u64(),
});
}
};
// Diff Attributes
//
// It's extraordinarily rare to have the number/order of attributes change
@@ -481,20 +471,17 @@ impl<'bump> DiffMachine<'bump> {
if old.attributes.len() == new.attributes.len() {
for (old_attr, new_attr) in old.attributes.iter().zip(new.attributes.iter()) {
if old_attr.value != new_attr.value {
please_commit(&mut self.mutations.edits);
self.mutations.set_attribute(new_attr);
self.mutations.set_attribute(new_attr, root.as_u64());
} else if new_attr.is_volatile {
please_commit(&mut self.mutations.edits);
self.mutations.set_attribute(new_attr);
self.mutations.set_attribute(new_attr, root.as_u64());
}
}
} else {
please_commit(&mut self.mutations.edits);
for attribute in old.attributes {
self.mutations.remove_attribute(attribute);
self.mutations.remove_attribute(attribute, root.as_u64());
}
for attribute in new.attributes {
self.mutations.set_attribute(attribute)
self.mutations.set_attribute(attribute, root.as_u64())
}
}
@@ -512,31 +499,34 @@ impl<'bump> DiffMachine<'bump> {
if old.listeners.len() == new.listeners.len() {
for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
if old_l.event != new_l.event {
please_commit(&mut self.mutations.edits);
self.mutations.remove_event_listener(old_l.event);
self.mutations
.remove_event_listener(old_l.event, root.as_u64());
self.mutations.new_event_listener(new_l, cur_scope_id);
}
new_l.mounted_node.set(old_l.mounted_node.get());
self.attach_listener_to_scope(new_l, scope);
}
} else {
please_commit(&mut self.mutations.edits);
for listener in old.listeners {
self.mutations.remove_event_listener(listener.event);
self.mutations
.remove_event_listener(listener.event, root.as_u64());
}
for listener in new.listeners {
listener.mounted_node.set(root);
listener.mounted_node.set(Some(root));
self.mutations.new_event_listener(listener, cur_scope_id);
self.attach_listener_to_scope(listener, scope);
}
}
}
if has_comitted {
self.mutations.pop();
if old.children.len() == 0 && new.children.len() != 0 {
self.mutations.edits.push(PushRoot {
root: root.as_u64(),
});
self.stack.create_children(new.children, MountType::Append);
} else {
self.diff_children(old.children, new.children);
}
self.diff_children(old.children, new.children);
}
fn diff_component_nodes(
@@ -551,6 +541,7 @@ impl<'bump> DiffMachine<'bump> {
// Make sure we're dealing with the same component (by function pointer)
if old.user_fc == new.user_fc {
log::debug!("Diffing component {:?} - {:?}", new.user_fc, scope_addr);
//
self.stack.scope_stack.push(scope_addr);
@@ -559,7 +550,6 @@ impl<'bump> DiffMachine<'bump> {
// make sure the component's caller function is up to date
let scope = self.vdom.get_scope_mut(scope_addr).unwrap();
scope.update_scope_dependencies(new.caller, ScopeChildren(new.children));
// React doesn't automatically memoize, but we do.
@@ -574,8 +564,6 @@ impl<'bump> DiffMachine<'bump> {
}
self.stack.scope_stack.pop();
self.seen_scopes.insert(scope_addr);
} else {
self.stack
.create_node(new_node, MountType::Replace { old: old_node });
@@ -590,6 +578,9 @@ impl<'bump> DiffMachine<'bump> {
return;
}
debug_assert!(old.children.len() != 0);
debug_assert!(new.children.len() != 0);
self.diff_children(old.children, new.children);
}
@@ -622,20 +613,18 @@ impl<'bump> DiffMachine<'bump> {
match (old, new) {
([], []) => {}
([], _) => {
// we need to push the
self.stack.create_children(new, MountType::Append);
}
(_, []) => {
for node in old {
self.remove_nodes(Some(node));
}
self.remove_nodes(old);
}
([VNode::Anchor(old_anchor)], [VNode::Anchor(new_anchor)]) => {
old_anchor.dom_id.set(new_anchor.dom_id.get());
}
([VNode::Anchor(anchor)], _) => {
let el = anchor.dom_id.get();
([VNode::Anchor(_)], _) => {
self.stack
.create_children(new, MountType::ReplaceByElementId { el });
.create_children(new, MountType::Replace { old: &old[0] });
}
(_, [VNode::Anchor(_)]) => {
self.replace_and_create_many_with_one(old, &new[0]);
@@ -744,19 +733,63 @@ impl<'bump> DiffMachine<'bump> {
Some(count) => count,
None => return,
};
log::debug!(
"Left offset, right offset, {}, {}",
left_offset,
right_offset,
);
// log::debug!(
// "Left offset, right offset, {}, {}",
// left_offset,
// right_offset,
// );
// log::debug!("stack before lo is {:#?}", self.stack.instructions);
// Ok, we now hopefully have a smaller range of children in the middle
// within which to re-order nodes with the same keys, remove old nodes with
// now-unused keys, and create new nodes with fresh keys.
self.diff_keyed_middle(
&old[left_offset..(old.len() - right_offset)],
&new[left_offset..(new.len() - right_offset)],
let old_middle = &old[left_offset..(old.len() - right_offset)];
let new_middle = &new[left_offset..(new.len() - right_offset)];
debug_assert!(
!((old_middle.len() == new_middle.len()) && old_middle.len() == 0),
"keyed children must have the same number of children"
);
if new_middle.len() == 0 {
// remove the old elements
self.remove_nodes(old_middle);
} else if old_middle.len() == 0 {
// there were no old elements, so just create the new elements
// we need to find the right "foothold" though - we shouldnt use the "append" at all
if left_offset == 0 {
// insert at the beginning of the old list
let foothold = &old[old.len() - right_offset];
self.stack.create_children(
new_middle,
MountType::InsertBefore {
other_node: foothold,
},
);
} else if right_offset == 0 {
// insert at the end the old list
let foothold = old.last().unwrap();
self.stack.create_children(
new_middle,
MountType::InsertAfter {
other_node: foothold,
},
);
} else {
// inserting in the middle
let foothold = &old[left_offset - 1];
self.stack.create_children(
new_middle,
MountType::InsertAfter {
other_node: foothold,
},
);
}
} else {
self.diff_keyed_middle(old_middle, new_middle);
}
log::debug!("stack after km is {:#?}", self.stack.instructions);
}
/// Diff both ends of the children that share keys.
@@ -882,6 +915,14 @@ impl<'bump> DiffMachine<'bump> {
// If none of the old keys are reused by the new children, then we remove all the remaining old children and
// create the new children afresh.
if shared_keys.is_empty() {
log::debug!(
"no shared keys, replacing and creating many with many, {:#?}, {:#?}",
old,
new
);
log::debug!("old_key_to_old_index, {:#?}", old_key_to_old_index);
log::debug!("new_index_to_old_index, {:#?}", new_index_to_old_index);
log::debug!("shared_keys, {:#?}", shared_keys);
self.replace_and_create_many_with_many(old, new);
return;
}
@@ -1075,9 +1116,11 @@ impl<'bump> DiffMachine<'bump> {
VNode::Component(c) => {
let scope_id = c.associated_scope.get().unwrap();
let scope = self.vdom.get_scope(scope_id).unwrap();
let scope = self.vdom.get_scope_mut(scope_id).unwrap();
let root = scope.root_node();
self.remove_nodes(Some(root));
let mut s = self.vdom.try_remove(scope_id).unwrap();
s.hooks.clear_hooks();
}
}
}
+8 -2
View File
@@ -30,13 +30,12 @@ pub enum MountType<'a> {
Absorb,
Append,
Replace { old: &'a VNode<'a> },
ReplaceByElementId { el: Option<ElementId> },
InsertAfter { other_node: &'a VNode<'a> },
InsertBefore { other_node: &'a VNode<'a> },
}
pub(crate) struct DiffStack<'bump> {
instructions: Vec<DiffInstruction<'bump>>,
pub(crate) instructions: Vec<DiffInstruction<'bump>>,
nodes_created_stack: SmallVec<[usize; 10]>,
pub scope_stack: SmallVec<[ScopeId; 5]>,
}
@@ -76,6 +75,13 @@ impl<'bump> DiffStack<'bump> {
}
}
pub fn push_subtree(&mut self) {
self.nodes_created_stack.push(0);
self.instructions.push(DiffInstruction::Mount {
and: MountType::Append,
});
}
pub fn push_nodes_created(&mut self, count: usize) {
self.nodes_created_stack.push(count);
}
File diff suppressed because it is too large. Load diff
+62 -93
View File
@@ -31,27 +31,33 @@ pub struct UserEvent {
pub name: &'static str,
/// The type of event
pub event: SyntheticEvent,
pub event: Box<dyn Any + Send>,
}
#[derive(Debug)]
pub enum SyntheticEvent {
AnimationEvent(on::AnimationEvent),
ClipboardEvent(on::ClipboardEvent),
CompositionEvent(on::CompositionEvent),
FocusEvent(on::FocusEvent),
FormEvent(on::FormEvent),
KeyboardEvent(on::KeyboardEvent),
GenericEvent(DioxusEvent<()>),
TouchEvent(on::TouchEvent),
ToggleEvent(on::ToggleEvent),
MediaEvent(on::MediaEvent),
MouseEvent(on::MouseEvent),
WheelEvent(on::WheelEvent),
SelectionEvent(on::SelectionEvent),
TransitionEvent(on::TransitionEvent),
PointerEvent(on::PointerEvent),
}
struct EventInner {}
// trait SyntheticEvent: Send + Debug {
// fn downcast(self: Box<Self>) -> Option<Box<dyn Any>>;
// }
// #[derive(Debug)]
// pub enum SyntheticEvent {
// AnimationEvent(on::AnimationEvent),
// ClipboardEvent(on::ClipboardEvent),
// CompositionEvent(on::CompositionEvent),
// FocusEvent(on::FocusEvent),
// FormEvent(on::FormEvent),
// KeyboardEvent(on::KeyboardEvent),
// GenericEvent(DioxusEvent<()>),
// TouchEvent(on::TouchEvent),
// ToggleEvent(on::ToggleEvent),
// MediaEvent(on::MediaEvent),
// MouseEvent(on::MouseEvent),
// WheelEvent(on::WheelEvent),
// SelectionEvent(on::SelectionEvent),
// TransitionEvent(on::TransitionEvent),
// PointerEvent(on::PointerEvent),
// }
/// Priority of Event Triggers.
///
@@ -105,23 +111,7 @@ pub enum EventPriority {
Low = 0,
}
#[derive(Debug)]
pub struct DioxusEvent<T: Send + Sync> {
inner: T,
raw: Box<dyn Any + Send>,
}
impl<T: Send + Sync> DioxusEvent<T> {
pub fn new<F: Send + 'static>(inner: T, raw: F) -> Self {
let raw = Box::new(raw);
Self { inner, raw }
}
/// Return a reference to the raw event. User will need to downcast the event to the right platform-specific type.
pub fn native<E: 'static>(&self) -> Option<&E> {
self.raw.downcast_ref()
}
impl EventInner {
/*
Not implemented!
@@ -200,20 +190,13 @@ impl<T: Send + Sync> DioxusEvent<T> {
}
}
impl<T: Send + Sync> std::ops::Deref for DioxusEvent<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
pub mod on {
use super::*;
macro_rules! event_directory {
( $(
$( #[$attr:meta] )*
$eventdata:ident($wrapper:ident): [
$wrapper:ident: [
// $eventdata:ident($wrapper:ident): [
$(
$( #[$method_attr:meta] )*
$name:ident
@@ -221,20 +204,6 @@ pub mod on {
];
)* ) => {
$(
$(#[$attr])*
#[derive(Debug)]
pub struct $wrapper(pub DioxusEvent<$eventdata>);
// todo: derefing to the event is fine (and easy) but breaks some IDE stuff like (go to source)
// going to source in fact takes you to the source of Rc which is... less than useful
// Either we ask for this to be changed in Rust-analyzer or manually implement the trait
impl Deref for $wrapper {
type Target = DioxusEvent<$eventdata>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
$(
$(#[$method_attr])*
pub fn $name<'a, F>(
@@ -248,12 +217,12 @@ pub mod on {
// we can't allocate unsized in bumpalo's box, so we need to craft the box manually
// safety: this is essentially the same as calling Box::new() but manually
// The box is attached to the lifetime of the bumpalo allocator
let cb: &mut dyn FnMut(SyntheticEvent) = bump.alloc(move |evt: SyntheticEvent| match evt {
SyntheticEvent::$wrapper(event) => callback(event),
_ => unreachable!("Downcasted SyntheticEvent to wrong spcific event type - this is an internal bug!")
let cb: &mut dyn FnMut(Box<dyn Any + Send>) = bump.alloc(move |evt: Box<dyn Any + Send>| {
let event = evt.downcast::<$wrapper>().unwrap();
callback(*event)
});
let callback: BumpBox<dyn FnMut(SyntheticEvent) + 'a> = unsafe { BumpBox::from_raw(cb) };
let callback: BumpBox<dyn FnMut(Box<dyn Any + Send>) + 'a> = unsafe { BumpBox::from_raw(cb) };
// ie oncopy
let event_name = stringify!($name);
@@ -274,7 +243,7 @@ pub mod on {
// The Dioxus Synthetic event system
event_directory! {
ClipboardEventInner(ClipboardEvent): [
ClipboardEvent: [
/// Called when "copy"
oncopy
@@ -285,7 +254,7 @@ pub mod on {
onpaste
];
CompositionEventInner(CompositionEvent): [
CompositionEvent: [
/// oncompositionend
oncompositionend
@@ -296,7 +265,7 @@ pub mod on {
oncompositionupdate
];
KeyboardEventInner(KeyboardEvent): [
KeyboardEvent: [
/// onkeydown
onkeydown
@@ -307,7 +276,7 @@ pub mod on {
onkeyup
];
FocusEventInner(FocusEvent): [
FocusEvent: [
/// onfocus
onfocus
@@ -315,7 +284,7 @@ pub mod on {
onblur
];
FormEventInner(FormEvent): [
FormEvent: [
/// onchange
onchange
@@ -373,7 +342,7 @@ pub mod on {
/// - [`onmouseout`]
/// - [`onmouseover`]
/// - [`onmouseup`]
MouseEventInner(MouseEvent): [
MouseEvent: [
/// Execute a callback when a button is clicked.
///
/// ## Description
@@ -456,7 +425,7 @@ pub mod on {
onmouseup
];
PointerEventInner(PointerEvent): [
PointerEvent: [
/// pointerdown
onpointerdown
@@ -488,12 +457,12 @@ pub mod on {
onpointerout
];
SelectionEventInner(SelectionEvent): [
SelectionEvent: [
/// onselect
onselect
];
TouchEventInner(TouchEvent): [
TouchEvent: [
/// ontouchcancel
ontouchcancel
@@ -507,12 +476,12 @@ pub mod on {
ontouchstart
];
WheelEventInner(WheelEvent): [
WheelEvent: [
///
onwheel
];
MediaEventInner(MediaEvent): [
MediaEvent: [
///abort
onabort
@@ -583,7 +552,7 @@ pub mod on {
onwaiting
];
AnimationEventInner(AnimationEvent): [
AnimationEvent: [
/// onanimationstart
onanimationstart
@@ -594,12 +563,12 @@ pub mod on {
onanimationiteration
];
TransitionEventInner(TransitionEvent): [
TransitionEvent: [
///
ontransitionend
];
ToggleEventInner(ToggleEvent): [
ToggleEvent: [
///
ontoggle
];
@@ -607,19 +576,19 @@ pub mod on {
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
pub struct ClipboardEventInner(
pub struct ClipboardEvent(
// DOMDataTransfer clipboardData
);
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
pub struct CompositionEventInner {
pub struct CompositionEvent {
pub data: String,
}
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
pub struct KeyboardEventInner {
pub struct KeyboardEvent {
pub char_code: u32,
/// Identify which "key" was entered.
@@ -687,18 +656,18 @@ pub mod on {
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
pub struct FocusEventInner {/* DOMEventInner: Send + SyncTarget relatedTarget */}
pub struct FocusEvent {/* DOMEventInner: Send + SyncTarget relatedTarget */}
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
pub struct FormEventInner {
pub struct FormEvent {
pub value: String,
/* DOMEventInner: Send + SyncTarget relatedTarget */
/* DOMEvent: Send + SyncTarget relatedTarget */
}
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
pub struct MouseEventInner {
pub struct MouseEvent {
pub alt_key: bool,
pub button: i16,
pub buttons: u16,
@@ -716,7 +685,7 @@ pub mod on {
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
pub struct PointerEventInner {
pub struct PointerEvent {
// Mouse only
pub alt_key: bool,
pub button: i16,
@@ -745,11 +714,11 @@ pub mod on {
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
pub struct SelectionEventInner {}
pub struct SelectionEvent {}
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
pub struct TouchEventInner {
pub struct TouchEvent {
pub alt_key: bool,
pub ctrl_key: bool,
pub meta_key: bool,
@@ -762,7 +731,7 @@ pub mod on {
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
pub struct WheelEventInner {
pub struct WheelEvent {
pub delta_mode: u32,
pub delta_x: f64,
pub delta_y: f64,
@@ -771,17 +740,17 @@ pub mod on {
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
pub struct MediaEventInner {}
pub struct MediaEvent {}
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
pub struct ImageEventInner {
pub struct ImageEvent {
pub load_error: bool,
}
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
pub struct AnimationEventInner {
pub struct AnimationEvent {
pub animation_name: String,
pub pseudo_element: String,
pub elapsed_time: f32,
@@ -789,7 +758,7 @@ pub mod on {
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
pub struct TransitionEventInner {
pub struct TransitionEvent {
pub property_name: String,
pub pseudo_element: String,
pub elapsed_time: f32,
@@ -797,7 +766,7 @@ pub mod on {
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
pub struct ToggleEventInner {}
pub struct ToggleEvent {}
}
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+9 -7
View File
@@ -12,7 +12,7 @@ use std::{
/// Todo: this could use its very own bump arena, but that might be a tad overkill
#[derive(Default)]
pub(crate) struct HookList {
vals: RefCell<Vec<(UnsafeCell<Box<dyn Any>>, Box<dyn FnOnce(&mut dyn Any)>)>>,
vals: RefCell<Vec<(UnsafeCell<Box<dyn Any>>, Box<dyn FnOnce(Box<dyn Any>)>)>>,
idx: Cell<usize>,
}
@@ -35,7 +35,7 @@ impl HookList {
self.idx.set(0);
}
pub(crate) fn push_hook<T: 'static>(&self, new: T, cleanup: Box<dyn FnOnce(&mut dyn Any)>) {
pub(crate) fn push_hook<T: 'static>(&self, new: T, cleanup: Box<dyn FnOnce(Box<dyn Any>)>) {
self.vals
.borrow_mut()
.push((UnsafeCell::new(Box::new(new)), cleanup))
@@ -52,14 +52,16 @@ impl HookList {
pub(crate) fn at_end(&self) -> bool {
self.cur_idx() >= self.len()
}
}
// When the scope is dropped, we want to call the cleanup function for each of the hooks
impl Drop for HookList {
fn drop(&mut self) {
pub fn clear_hooks(&mut self) {
self.vals
.borrow_mut()
.drain(..)
.for_each(|(mut state, cleanup)| cleanup(state.get_mut()));
.for_each(|(mut state, cleanup)| {
//
let s: Box<dyn Any> = state.into_inner();
cleanup(s)
//
});
}
}
+2 -2
View File
@@ -62,8 +62,8 @@ pub(crate) mod innerlude {
pub use crate::innerlude::{
Context, DioxusElement, DomEdit, DomTree, ElementId, EventPriority, LazyNodes, MountType,
Mutations, NodeFactory, Properties, SchedulerMsg, ScopeId, SuspendedContext, SyntheticEvent,
TaskHandle, TestDom, ThreadsafeVirtualDom, UserEvent, VNode, VirtualDom, FC,
Mutations, NodeFactory, Properties, SchedulerMsg, ScopeId, SuspendedContext, TaskHandle,
TestDom, ThreadsafeVirtualDom, UserEvent, VNode, VirtualDom, FC,
};
pub mod prelude {
+12 -8
View File
@@ -90,16 +90,16 @@ impl<'a> Mutations<'a> {
root: element_id,
});
}
pub(crate) fn remove_event_listener(&mut self, event: &'static str) {
self.edits.push(RemoveEventListener { event });
pub(crate) fn remove_event_listener(&mut self, event: &'static str, root: u64) {
self.edits.push(RemoveEventListener { event, root });
}
// modify
pub(crate) fn set_text(&mut self, text: &'a str) {
self.edits.push(SetText { text });
pub(crate) fn set_text(&mut self, text: &'a str, root: u64) {
self.edits.push(SetText { text, root });
}
pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute) {
pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute, root: u64) {
let Attribute {
name,
value,
@@ -111,12 +111,13 @@ impl<'a> Mutations<'a> {
field: name,
value,
ns: *namespace,
root,
});
}
pub(crate) fn remove_attribute(&mut self, attribute: &Attribute) {
pub(crate) fn remove_attribute(&mut self, attribute: &Attribute, root: u64) {
let name = attribute.name;
self.edits.push(RemoveAttribute { name });
self.edits.push(RemoveAttribute { name, root });
}
}
@@ -184,7 +185,6 @@ pub enum DomEdit<'bump> {
Remove {
root: u64,
},
CreateTextNode {
text: &'bump str,
root: u64,
@@ -207,17 +207,21 @@ pub enum DomEdit<'bump> {
root: u64,
},
RemoveEventListener {
root: u64,
event: &'static str,
},
SetText {
root: u64,
text: &'bump str,
},
SetAttribute {
root: u64,
field: &'static str,
value: &'bump str,
ns: Option<&'bump str>,
},
RemoveAttribute {
root: u64,
name: &'static str,
},
}
+3 -3
View File
@@ -4,8 +4,7 @@
//! cheap and *very* fast to construct - building a full tree should be quick.
use crate::innerlude::{
empty_cell, Context, DomTree, ElementId, Properties, Scope, ScopeId, SuspendedContext,
SyntheticEvent, FC,
empty_cell, Context, DomTree, ElementId, Properties, Scope, ScopeId, SuspendedContext, FC,
};
use bumpalo::{boxed::Box as BumpBox, Bump};
use std::{
@@ -271,7 +270,8 @@ pub struct Listener<'bump> {
pub event: &'static str,
/// The actual callback that the user specified
pub(crate) callback: RefCell<Option<BumpBox<'bump, dyn FnMut(SyntheticEvent) + 'bump>>>,
pub(crate) callback:
RefCell<Option<BumpBox<'bump, dyn FnMut(Box<dyn std::any::Any + Send>) + 'bump>>>,
}
/// Virtual Components for custom user-defined components
+2 -2
View File
@@ -63,8 +63,8 @@ impl ResourcePool {
/// return the id, freeing the space of the original node
pub fn collect_garbage(&self, id: ElementId) {
todo!("garabge collection currently WIP")
// self.raw_elements.remove(id.0);
let els = unsafe { &mut *self.raw_elements.get() };
els.remove(id.0);
}
pub fn insert_scope_with_key(&self, f: impl FnOnce(ScopeId) -> Scope) -> ScopeId {
+40 -13
View File
@@ -86,6 +86,7 @@ use std::{
#[derive(Clone)]
pub(crate) struct EventChannel {
pub task_counter: Rc<Cell<u64>>,
pub cur_subtree: Rc<Cell<u32>>,
pub sender: UnboundedSender<SchedulerMsg>,
pub schedule_any_immediate: Rc<dyn Fn(ScopeId)>,
pub submit_task: Rc<dyn Fn(FiberTask) -> TaskHandle>,
@@ -106,8 +107,6 @@ pub enum SchedulerMsg {
}
pub enum TaskMsg {
// SubmitTask(FiberTask, u64),
// SubmitTask(FiberTask, u64),
ToggleTask(u64),
PauseTask(u64),
ResumeTask(u64),
@@ -178,13 +177,19 @@ impl Scheduler {
let heuristics = HeuristicsEngine::new();
let task_counter = Rc::new(Cell::new(0));
let cur_subtree = Rc::new(Cell::new(0));
let channel = EventChannel {
cur_subtree,
task_counter: task_counter.clone(),
sender: sender.clone(),
schedule_any_immediate: {
let sender = sender.clone();
Rc::new(move |id| sender.unbounded_send(SchedulerMsg::Immediate(id)).unwrap())
Rc::new(move |id| {
//
log::debug!("scheduling immedate! {:?}", id);
sender.unbounded_send(SchedulerMsg::Immediate(id)).unwrap()
})
},
// todo: we want to get the futures out of the scheduler message
// the scheduler message should be send/sync
@@ -319,6 +324,9 @@ impl Scheduler {
unsafe fn load_work(&mut self) -> SavedDiffWork<'static> {
self.saved_state.take().unwrap().extend()
}
pub fn handle_task(&mut self, evt: TaskMsg) {
//
}
pub fn handle_channel_msg(&mut self, msg: SchedulerMsg) {
match msg {
@@ -370,23 +378,28 @@ impl Scheduler {
if machine.stack.is_empty() {
let shared = self.pool.clone();
self.dirty_scopes
.retain(|id| shared.get_scope(*id).is_some());
self.dirty_scopes.sort_by(|a, b| {
let h1 = shared.get_scope(*a).unwrap().height;
let h2 = shared.get_scope(*b).unwrap().height;
h1.cmp(&h2)
h1.cmp(&h2).reverse()
});
if let Some(scopeid) = self.dirty_scopes.pop() {
log::info!("handlng dirty scope {:#?}", scopeid);
log::info!("handlng dirty scope {:?}", scopeid);
if !ran_scopes.contains(&scopeid) {
ran_scopes.insert(scopeid);
log::debug!("about to run scope {:?}", scopeid);
let mut component = self.pool.get_scope_mut(scopeid).unwrap();
if component.run_scope(&self.pool) {
let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
// let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
machine.stack.scope_stack.push(scopeid);
machine.stack.push(DiffInstruction::Diff { new, old });
if let Some(component) = self.pool.get_scope_mut(scopeid) {
if component.run_scope(&self.pool) {
let (old, new) =
(component.frames.wip_head(), component.frames.fin_head());
// let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
machine.stack.scope_stack.push(scopeid);
machine.stack.push(DiffInstruction::Diff { new, old });
}
}
}
}
@@ -477,6 +490,18 @@ impl Scheduler {
let mut committed_mutations = Vec::<Mutations<'static>>::new();
while self.has_any_work() {
while let Ok(Some(msg)) = self.receiver.try_next() {
match msg {
SchedulerMsg::Task(t) => todo!(),
SchedulerMsg::Immediate(im) => {
self.dirty_scopes.insert(im);
}
SchedulerMsg::UiEvent(evt) => {
self.ui_events.push_back(evt);
}
}
}
// switch our priority, pop off any work
while let Some(event) = self.ui_events.pop_front() {
if let Some(scope) = self.pool.get_scope_mut(event.scope) {
@@ -506,8 +531,6 @@ impl Scheduler {
}
}
log::debug!("work with deadline completed: {:#?}", committed_mutations);
committed_mutations
}
@@ -546,6 +569,8 @@ impl Scheduler {
.get_scope_mut(base_scope)
.expect("The base scope should never be moved");
log::debug!("rebuild {:?}", base_scope);
// We run the component. If it succeeds, then we can diff it and add the changes to the dom.
if cur_component.run_scope(&self.pool) {
diff_machine
@@ -572,6 +597,8 @@ impl Scheduler {
.get_scope_mut(base_scope)
.expect("The base scope should never be moved");
log::debug!("hard diff {:?}", base_scope);
if cur_component.run_scope(&self.pool) {
let mut diff_machine = DiffMachine::new(Mutations::new(), &mut self.pool);
diff_machine.cfg.force_diff = true;
+37 -1
View File
@@ -2,7 +2,7 @@ use crate::innerlude::*;
use fxhash::FxHashMap;
use std::{
any::{Any, TypeId},
cell::RefCell,
cell::{Cell, RefCell},
collections::HashMap,
future::Future,
pin::Pin,
@@ -23,6 +23,8 @@ pub struct Scope {
pub(crate) parent_idx: Option<ScopeId>,
pub(crate) our_arena_idx: ScopeId,
pub(crate) height: u32,
pub(crate) subtree: Cell<u32>,
pub(crate) is_subtree_root: Cell<bool>,
// Nodes
pub(crate) frames: ActiveFrame,
@@ -71,6 +73,36 @@ impl Scope {
self.frames.fin_head()
}
/// Get the subtree ID that this scope belongs to.
///
/// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
/// the mutations to the correct window/portal/subtree.
///
///
/// # Example
///
/// ```rust
/// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} }));
/// dom.rebuild();
///
/// let base = dom.base_scope();
///
/// assert_eq!(base.subtree(), 0);
/// ```
pub fn subtree(&self) -> u32 {
self.subtree.get()
}
pub(crate) fn new_subtree(&self) -> Option<u32> {
if self.is_subtree_root.get() {
None
} else {
let cur = self.shared.cur_subtree.get();
self.shared.cur_subtree.set(cur + 1);
Some(cur)
}
}
/// Get the height of this Scope - IE the number of scopes above it.
///
/// A Scope with a height of `0` is the root scope - there are no other scopes above it.
@@ -146,6 +178,7 @@ impl Scope {
our_arena_idx: ScopeId,
parent_idx: Option<ScopeId>,
height: u32,
subtree: u32,
child_nodes: ScopeChildren,
shared: EventChannel,
) -> Self {
@@ -168,6 +201,8 @@ impl Scope {
parent_idx,
our_arena_idx,
height,
subtree: Cell::new(subtree),
is_subtree_root: Cell::new(false),
frames: ActiveFrame::new(),
hooks: Default::default(),
@@ -183,6 +218,7 @@ impl Scope {
caller: &'creator_node dyn for<'b> Fn(&'b Scope) -> DomTree<'b>,
child_nodes: ScopeChildren,
) {
log::debug!("Updating scope dependencies {:?}", self.our_arena_idx);
let caller = caller as *const _;
self.caller = unsafe { std::mem::transmute(caller) };
+5 -20
View File
@@ -156,6 +156,7 @@ impl VirtualDom {
myidx,
None,
0,
0,
ScopeChildren(&[]),
scheduler.pool.channel.clone(),
)
@@ -359,25 +360,21 @@ impl VirtualDom {
/// Waits for the scheduler to have work
/// This lets us poll async tasks during idle periods without blocking the main thread.
pub async fn wait_for_work(&mut self) {
// todo: poll the events once even if there is work to do to prevent starvation
if self.scheduler.has_any_work() {
log::debug!("No need to wait for work, we already have some");
return;
}
log::debug!("No active work.... waiting for some...");
use futures_util::StreamExt;
// right now this won't poll events if there is ongoing work
// in the future we want to prioritize some events over ongoing work
// this is coming in the "priorities" PR
// Wait for any new events if we have nothing to do
// todo: poll the events once even if there is work to do to prevent starvation
futures_util::select! {
_ = self.scheduler.async_tasks.next() => {}
msg = self.scheduler.receiver.next() => {
match msg.unwrap() {
SchedulerMsg::Task(t) => todo!(),
SchedulerMsg::Task(t) => {
self.scheduler.handle_task(t);
},
SchedulerMsg::Immediate(im) => {
self.scheduler.dirty_scopes.insert(im);
}
@@ -387,18 +384,6 @@ impl VirtualDom {
}
},
}
while let Ok(Some(msg)) = self.scheduler.receiver.try_next() {
match msg {
SchedulerMsg::Task(t) => todo!(),
SchedulerMsg::Immediate(im) => {
self.scheduler.dirty_scopes.insert(im);
}
SchedulerMsg::UiEvent(evt) => {
self.scheduler.ui_events.push_back(evt);
}
}
}
}
}
+4 -7
View File
@@ -45,13 +45,10 @@ fn html_and_rsx_generate_the_same_output() {
assert_eq!(
change.edits,
[
PushRoot { root: 1 },
SetText {
text: "Goodbye world"
},
PopRoot
]
[SetText {
text: "Goodbye world",
root: 1
},]
);
}
+2 -2
View File
@@ -18,12 +18,12 @@ fn shared_state_test() {
struct MySharedState(&'static str);
static App: FC<()> = |cx, props| {
cx.use_provide_state(|| MySharedState("world!"));
cx.provide_state(MySharedState("world!"));
rsx!(cx, Child {})
};
static Child: FC<()> = |cx, props| {
let shared = cx.use_consume_state::<MySharedState>()?;
let shared = cx.consume_state::<MySharedState>()?;
rsx!(cx, "Hello, {shared.0}")
};
+1 -1
View File
@@ -54,7 +54,7 @@ pub struct WebviewWindowProps<'a> {
///
///
pub fn WebviewWindow<'a>(cx: Context<'a>, props: &'a WebviewWindowProps) -> DomTree<'a> {
let dtcx = cx.use_consume_state::<RefCell<DesktopContext>>()?;
let dtcx = cx.consume_state::<RefCell<DesktopContext>>()?;
cx.use_hook(
|_| {
+36 -57
View File
@@ -1,16 +1,10 @@
//! Convert a serialized event to an event Trigger
//!
use std::rc::Rc;
use std::sync::Arc;
use std::{any::Any, rc::Rc};
use dioxus_core::{
events::{
on::{MouseEvent, MouseEventInner},
SyntheticEvent,
},
ElementId, EventPriority, ScopeId, UserEvent,
};
use dioxus_core::{events::on::MouseEvent, ElementId, EventPriority, ScopeId, UserEvent};
#[derive(serde::Serialize, serde::Deserialize)]
struct ImEvent {
@@ -43,85 +37,70 @@ pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
}
}
fn make_synthetic_event(name: &str, val: serde_json::Value) -> SyntheticEvent {
fn make_synthetic_event(name: &str, val: serde_json::Value) -> Box<dyn Any + Send> {
use dioxus_core::events::on::*;
use dioxus_core::events::DioxusEvent;
match name {
"copy" | "cut" | "paste" => SyntheticEvent::ClipboardEvent(ClipboardEvent(
DioxusEvent::new(ClipboardEventInner(), ()),
)),
"copy" | "cut" | "paste" => {
//
Box::new(ClipboardEvent {})
}
"compositionend" | "compositionstart" | "compositionupdate" => {
SyntheticEvent::CompositionEvent(CompositionEvent(DioxusEvent::new(
serde_json::from_value(val).unwrap(),
(),
)))
Box::new(serde_json::from_value::<CompositionEvent>(val).unwrap())
}
"keydown" | "keypress" | "keyup" => {
Box::new(serde_json::from_value::<KeyboardEvent>(val).unwrap())
}
"keydown" | "keypress" | "keyup" => SyntheticEvent::KeyboardEvent(KeyboardEvent(
DioxusEvent::new(serde_json::from_value(val).unwrap(), ()),
)),
"focus" | "blur" => {
SyntheticEvent::FocusEvent(FocusEvent(DioxusEvent::new(FocusEventInner {}, ())))
//
Box::new(FocusEvent {})
}
"change" => SyntheticEvent::GenericEvent(DioxusEvent::new((), ())),
// todo: these handlers might get really slow if the input box gets large and allocation pressure is heavy
// don't have a good solution with the serialized event problem
"input" | "invalid" | "reset" | "submit" => SyntheticEvent::FormEvent(FormEvent(
DioxusEvent::new(serde_json::from_value(val).unwrap(), ()),
)),
"change" | "input" | "invalid" | "reset" | "submit" => {
Box::new(serde_json::from_value::<FormEvent>(val).unwrap())
}
"click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
| "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter"
| "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
SyntheticEvent::MouseEvent(MouseEvent(DioxusEvent::new(
serde_json::from_value(val).unwrap(),
(),
)))
Box::new(serde_json::from_value::<MouseEvent>(val).unwrap())
}
"pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
| "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
SyntheticEvent::PointerEvent(PointerEvent(DioxusEvent::new(
serde_json::from_value(val).unwrap(),
(),
)))
Box::new(serde_json::from_value::<PointerEvent>(val).unwrap())
}
"select" => {
//
Box::new(serde_json::from_value::<SelectionEvent>(val).unwrap())
}
"select" => SyntheticEvent::SelectionEvent(SelectionEvent(DioxusEvent::new(
SelectionEventInner {},
(),
))),
"touchcancel" | "touchend" | "touchmove" | "touchstart" => SyntheticEvent::TouchEvent(
TouchEvent(DioxusEvent::new(serde_json::from_value(val).unwrap(), ())),
),
"touchcancel" | "touchend" | "touchmove" | "touchstart" => {
Box::new(serde_json::from_value::<TouchEvent>(val).unwrap())
}
"scroll" => SyntheticEvent::GenericEvent(DioxusEvent::new((), ())),
"scroll" => Box::new(()),
"wheel" => SyntheticEvent::WheelEvent(WheelEvent(DioxusEvent::new(
serde_json::from_value(val).unwrap(),
(),
))),
"wheel" => Box::new(serde_json::from_value::<WheelEvent>(val).unwrap()),
"animationstart" | "animationend" | "animationiteration" => SyntheticEvent::AnimationEvent(
AnimationEvent(DioxusEvent::new(serde_json::from_value(val).unwrap(), ())),
),
"animationstart" | "animationend" | "animationiteration" => {
Box::new(serde_json::from_value::<AnimationEvent>(val).unwrap())
}
"transitionend" => SyntheticEvent::TransitionEvent(TransitionEvent(DioxusEvent::new(
serde_json::from_value(val).unwrap(),
(),
))),
"transitionend" => Box::new(serde_json::from_value::<TransitionEvent>(val).unwrap()),
"abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
| "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
| "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
| "timeupdate" | "volumechange" | "waiting" => {
SyntheticEvent::MediaEvent(MediaEvent(DioxusEvent::new(MediaEventInner {}, ())))
//
Box::new(MediaEvent {})
}
"toggle" => {
SyntheticEvent::ToggleEvent(ToggleEvent(DioxusEvent::new(ToggleEventInner {}, ())))
}
"toggle" => Box::new(ToggleEvent {}),
_ => SyntheticEvent::GenericEvent(DioxusEvent::new((), ())),
_ => Box::new(()),
}
}
+56 -57
View File
@@ -50,6 +50,18 @@ class Interpreter {
root.replaceWith(...els);
}
InsertAfter(edit) {
let old = this.nodes[edit.root];
let new_nodes = this.stack.splice(this.stack.length - edit.n);
old.after(...new_nodes);
}
InsertBefore(edit) {
let old = this.nodes[edit.root];
let new_nodes = this.stack.splice(this.stack.length - edit.n);
old.before(...new_nodes);
}
Remove(edit) {
let node = this.nodes[edit.root];
if (node !== undefined) {
@@ -78,74 +90,18 @@ class Interpreter {
CreatePlaceholder(edit) {
let el = document.createElement("pre");
// let el = document.createComment("vroot");
this.stack.push(el);
this.nodes[edit.root] = el;
}
RemoveEventListener(edit) { }
SetText(edit) {
this.top().textContent = edit.text;
}
SetAttribute(edit) {
const name = edit.field;
const value = edit.value;
const ns = edit.ns;
const node = this.top(this.stack);
if (ns == "style") {
node.style[name] = value;
} else if (ns !== undefined) {
node.setAttributeNS(ns, name, value);
} else {
node.setAttribute(name, value);
}
if (name === "value") {
node.value = value;
}
if (name === "checked") {
node.checked = true;
}
if (name === "selected") {
node.selected = true;
}
}
RemoveAttribute(edit) {
const name = edit.field;
const node = this.top(this.stack);
node.removeAttribute(name);
if (name === "value") {
node.value = null;
}
if (name === "checked") {
node.checked = false;
}
if (name === "selected") {
node.selected = false;
}
}
InsertAfter(edit) {
let old = this.nodes[edit.root];
let new_nodes = this.stack.splice(this.stack.length - edit.n);
// console.log("inserting nodes after", new_nodes, old);
old.after(...new_nodes);
}
InsertBefore(edit) {
let old = this.nodes[edit.root];
let new_nodes = this.stack.splice(this.stack.length - edit.n);
old.before(...new_nodes);
}
NewEventListener(edit) {
const event_name = edit.event_name;
const mounted_node_id = edit.root;
const scope = edit.scope;
const element = this.top();
const element = this.nodes[edit.root]
element.setAttribute(`dioxus-event-${event_name}`, `${scope}.${mounted_node_id}`);
if (this.listeners[event_name] === undefined) {
@@ -191,6 +147,49 @@ class Interpreter {
});
}
}
SetText(edit) {
this.nodes[edit.root].textContent = edit.text;
}
SetAttribute(edit) {
const name = edit.field;
const value = edit.value;
const ns = edit.ns;
const node = this.nodes[edit.root]
if (ns == "style") {
node.style[name] = value;
} else if (ns !== undefined) {
node.setAttributeNS(ns, name, value);
} else {
node.setAttribute(name, value);
}
if (name === "value") {
node.value = value;
}
if (name === "checked") {
node.checked = true;
}
if (name === "selected") {
node.selected = true;
}
}
RemoveAttribute(edit) {
const name = edit.field;
const node = this.nodes[edit.root];
node.removeAttribute(name);
if (name === "value") {
node.value = null;
}
if (name === "checked") {
node.checked = false;
}
if (name === "selected") {
node.selected = false;
}
}
}
async function initialize() {
+3
View File
@@ -3,3 +3,6 @@ pub use usestate::{use_state, AsyncUseState, UseState};
mod useref;
pub use useref::*;
mod use_shared_state;
pub use use_shared_state::*;
+177
View File
@@ -0,0 +1,177 @@
use dioxus_core::{prelude::Context, ScopeId};
use std::{
cell::{Cell, Ref, RefCell, RefMut},
collections::HashSet,
rc::Rc,
};
type ProvidedState<T> = RefCell<ProvidedStateInner<T>>;
// Tracks all the subscribers to a shared State
pub(crate) struct ProvidedStateInner<T> {
value: Rc<RefCell<T>>,
notify_any: Rc<dyn Fn(ScopeId)>,
consumers: HashSet<ScopeId>,
}
impl<T> ProvidedStateInner<T> {
pub(crate) fn notify_consumers(&mut self) {
for consumer in self.consumers.iter() {
(self.notify_any)(*consumer);
}
}
}
/// This hook provides some relatively light ergonomics around shared state.
///
/// It is not a substitute for a proper state management system, but it is capable enough to provide use_state - type
/// ergonimics in a pinch, with zero cost.
///
/// # Example
///
/// ## Provider
///
/// ```rust
///
///
/// ```
///
/// ## Consumer
///
/// ```rust
///
///
/// ```
///
/// # How it works
///
/// Any time a component calls `write`, every consumer of the state will be notified - excluding the provider.
///
/// Right now, there is not a distinction between read-only and write-only, so every consumer will be notified.
///
///
///
pub fn use_shared_state<'a, T: 'static>(cx: Context<'a>) -> Option<UseSharedState<'a, T>> {
cx.use_hook(
|_| {
let scope_id = cx.scope_id();
let root = cx.consume_state::<ProvidedState<T>>();
if let Some(root) = root.as_ref() {
root.borrow_mut().consumers.insert(scope_id);
}
let value = root.as_ref().map(|f| f.borrow().value.clone());
SharedStateInner {
root,
value,
scope_id,
needs_notification: Cell::new(false),
}
},
|f| {
//
f.needs_notification.set(false);
match (&f.value, &f.root) {
(Some(value), Some(root)) => Some(UseSharedState {
cx,
value,
root,
needs_notification: &f.needs_notification,
}),
_ => None,
}
},
|f| {
// we need to unsubscribe when our component is unounted
if let Some(root) = &f.root {
let mut root = root.borrow_mut();
root.consumers.remove(&f.scope_id);
}
},
)
}
struct SharedStateInner<T: 'static> {
root: Option<Rc<ProvidedState<T>>>,
value: Option<Rc<RefCell<T>>>,
scope_id: ScopeId,
needs_notification: Cell<bool>,
}
pub struct UseSharedState<'a, T: 'static> {
pub(crate) cx: Context<'a>,
pub(crate) value: &'a Rc<RefCell<T>>,
pub(crate) root: &'a Rc<RefCell<ProvidedStateInner<T>>>,
pub(crate) needs_notification: &'a Cell<bool>,
}
impl<'a, T: 'static> UseSharedState<'a, T> {
pub fn read(&self) -> Ref<'_, T> {
self.value.borrow()
}
pub fn notify_consumers(self) {
// if !self.needs_notification.get() {
self.root.borrow_mut().notify_consumers();
// self.needs_notification.set(true);
// }
}
pub fn read_write(&self) -> (Ref<'_, T>, &Self) {
(self.read(), self)
}
/// Calling "write" will force the component to re-render
///
///
/// TODO: We prevent unncessary notifications only in the hook, but we should figure out some more global lock
pub fn write(&self) -> RefMut<'_, T> {
self.cx.needs_update();
self.notify_consumers();
self.value.borrow_mut()
}
/// Allows the ability to write the value without forcing a re-render
pub fn write_silent(&self) -> RefMut<'_, T> {
self.value.borrow_mut()
}
}
impl<T> Copy for UseSharedState<'_, T> {}
impl<'a, T> Clone for UseSharedState<'a, T>
where
T: 'static,
{
fn clone(&self) -> Self {
UseSharedState {
cx: self.cx,
value: self.value,
root: self.root,
needs_notification: self.needs_notification,
}
}
}
/// Provide some state for components down the hierarchy to consume without having to drill props.
///
///
///
///
///
///
///
pub fn use_provide_state<'a, T: 'static>(cx: Context<'a>, f: impl FnOnce() -> T) {
cx.use_hook(
|_| {
let state: ProvidedState<T> = RefCell::new(ProvidedStateInner {
value: Rc::new(RefCell::new(f())),
notify_any: cx.schedule_update_any(),
consumers: HashSet::new(),
});
cx.provide_state(state)
},
|inner| {},
|_| {},
)
}
+3 -3
View File
@@ -2,7 +2,7 @@ use dioxus_core::prelude::Context;
use std::{
cell::{Cell, Ref, RefCell, RefMut},
fmt::Display,
ops::{Deref, DerefMut, Not},
ops::Not,
rc::Rc,
};
@@ -121,8 +121,8 @@ impl<'a, T: 'static> UseState<'a, T> {
self.inner.wip.borrow_mut()
}
pub fn classic(self) -> (&'a T, &'a Rc<dyn Fn(T)>) {
todo!()
pub fn classic(self) -> (&'a T, Rc<dyn Fn(T)>) {
(&self.inner.current_val, self.setter())
}
pub fn setter(&self) -> Rc<dyn Fn(T)> {
+25 -7
View File
@@ -479,6 +479,7 @@ pub trait GlobalAttributes {
/// Specifies the treatment of content that overflows the element's box.
overflow: "overflow",
/// Specifies the treatment of content that overflows the element's box horizontally.
overflow_x: "overflow-x",
@@ -518,6 +519,12 @@ pub trait GlobalAttributes {
/// Specifies how an element is positioned.
position: "position",
/// The pointer-events CSS property sets under what circumstances (if any) a particular graphic element can
/// become the target of pointer events.
///
/// MDN: [`pointer_events`](https://developer.mozilla.org/en-US/docs/Web/CSS/pointer-events)
pointer_events: "pointer-events",
/// Specifies quotation marks for embedded quotations.
quotes: "quotes",
@@ -540,6 +547,7 @@ pub trait GlobalAttributes {
/// Specifies the decoration added to text.
text_decoration: "text-decoration",
/// Specifies the color of the text_decoration_line.
text_decoration_color: "text-decoration-color",
@@ -1527,19 +1535,19 @@ builder_constructors! {
step: String,
tabindex: usize,
// This has a manual implementation below
// r#type: InputType,
value: String,
width: isize,
// Manual implementations below...
// r#type: InputType,
// value: String,
};
/// Build a
/// [`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label)
/// element.
label {
r#for: Id,
form: Id,
// r#for: Id,
};
/// Build a
@@ -1575,10 +1583,11 @@ builder_constructors! {
disabled: Bool,
label: String,
// defined below
// selected: Bool,
value: String,
// defined below
// selected: Bool,
};
/// Build a
@@ -1694,6 +1703,10 @@ impl input {
pub fn r#type<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
cx.attr("type", val, None, false)
}
pub fn value<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
cx.attr("value", val, None, true)
}
}
/*
@@ -1717,6 +1730,11 @@ impl textarea {
cx.attr("value", val, None, true)
}
}
impl label {
pub fn r#for<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
cx.attr("for", val, None, false)
}
}
pub trait SvgAttributes {
aria_trait_methods! {
+1 -1
View File
@@ -1,9 +1,9 @@
//! Basic example that renders a simple VNode to the browser.
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_core_macro::*;
use dioxus_hooks::*;
use dioxus_html as dioxus_elements;
use dioxus_core_macro::*;
fn main() {
console_error_panic_hook::set_once();
+147 -152
View File
@@ -8,13 +8,13 @@
//! - Partial delegation?>
use dioxus_core::{
events::{DioxusEvent, KeyCode, SyntheticEvent, UserEvent},
events::{KeyCode, UserEvent},
mutations::NodeRefMutation,
scheduler::SchedulerMsg,
DomEdit, ElementId, ScopeId,
};
use fxhash::FxHashMap;
use std::{fmt::Debug, rc::Rc, sync::Arc};
use std::{any::Any, fmt::Debug, rc::Rc, sync::Arc};
use wasm_bindgen::{closure::Closure, JsCast};
use web_sys::{
Attr, CssStyleDeclaration, Document, Element, Event, HtmlElement, HtmlInputElement,
@@ -122,11 +122,18 @@ impl WebsysDom {
root: mounted_node_id,
} => self.new_event_listener(event_name, scope, mounted_node_id),
DomEdit::RemoveEventListener { event } => self.remove_event_listener(event),
DomEdit::RemoveEventListener { event, root } => {
self.remove_event_listener(event, root)
}
DomEdit::SetText { text } => self.set_text(text),
DomEdit::SetAttribute { field, value, ns } => self.set_attribute(field, value, ns),
DomEdit::RemoveAttribute { name } => self.remove_attribute(name),
DomEdit::SetText { text, root } => self.set_text(text, root),
DomEdit::SetAttribute {
field,
value,
ns,
root,
} => self.set_attribute(field, value, ns, root),
DomEdit::RemoveAttribute { name, root } => self.remove_attribute(name, root),
DomEdit::InsertAfter { n, root } => self.insert_after(n, root),
DomEdit::InsertBefore { n, root } => self.insert_before(n, root),
@@ -211,7 +218,7 @@ impl WebsysDom {
fn create_placeholder(&mut self, id: u64) {
self.create_element("pre", None, id);
self.set_attribute("hidden", "", None);
// self.set_attribute("hidden", "", None);
}
fn create_text_node(&mut self, text: &str, id: u64) {
@@ -244,6 +251,9 @@ impl WebsysDom {
.unwrap(),
};
let el2 = el.dyn_ref::<Element>().unwrap();
el2.set_attribute("dioxus-id", &format!("{}", id)).unwrap();
self.stack.push(el.clone());
self.nodes[(id as usize)] = Some(el);
}
@@ -299,16 +309,17 @@ impl WebsysDom {
}
}
fn remove_event_listener(&mut self, event: &str) {
fn remove_event_listener(&mut self, event: &str, root: u64) {
// todo!()
}
fn set_text(&mut self, text: &str) {
self.stack.top().set_text_content(Some(text))
fn set_text(&mut self, text: &str, root: u64) {
let el = self.nodes[root as usize].as_ref().unwrap();
el.set_text_content(Some(text))
}
fn set_attribute(&mut self, name: &str, value: &str, ns: Option<&str>) {
let node = self.stack.top();
fn set_attribute(&mut self, name: &str, value: &str, ns: Option<&str>, root: u64) {
let node = self.nodes[root as usize].as_ref().unwrap();
if ns == Some("style") {
if let Some(el) = node.dyn_ref::<Element>() {
let el = el.dyn_ref::<HtmlElement>().unwrap();
@@ -342,7 +353,11 @@ impl WebsysDom {
}
"checked" => {
if let Some(input) = node.dyn_ref::<HtmlInputElement>() {
input.set_checked(true);
match value {
"true" => input.set_checked(true),
"false" => input.set_checked(false),
_ => fallback(),
}
} else {
fallback();
}
@@ -359,8 +374,8 @@ impl WebsysDom {
}
}
fn remove_attribute(&mut self, name: &str) {
let node = self.stack.top();
fn remove_attribute(&mut self, name: &str, root: u64) {
let node = self.nodes[root as usize].as_ref().unwrap();
if let Some(node) = node.dyn_ref::<web_sys::Element>() {
node.remove_attribute(name).unwrap();
}
@@ -400,18 +415,16 @@ impl WebsysDom {
}
fn insert_before(&mut self, n: u32, root: u64) {
let after = self.nodes[root as usize].as_ref().unwrap();
let anchor = self.nodes[root as usize].as_ref().unwrap();
if n == 1 {
let before = self.stack.pop();
after
anchor
.parent_node()
.unwrap()
.insert_before(&before, Some(&after))
.insert_before(&before, Some(&anchor))
.unwrap();
after.insert_before(&before, None).unwrap();
} else {
let arr: js_sys::Array = self
.stack
@@ -419,11 +432,11 @@ impl WebsysDom {
.drain((self.stack.list.len() - n as usize)..)
.collect();
if let Some(el) = after.dyn_ref::<Element>() {
if let Some(el) = anchor.dyn_ref::<Element>() {
el.before_with_node(&arr).unwrap();
} else if let Some(el) = after.dyn_ref::<web_sys::CharacterData>() {
} else if let Some(el) = anchor.dyn_ref::<web_sys::CharacterData>() {
el.before_with_node(&arr).unwrap();
} else if let Some(el) = after.dyn_ref::<web_sys::DocumentType>() {
} else if let Some(el) = anchor.dyn_ref::<web_sys::DocumentType>() {
el.before_with_node(&arr).unwrap();
}
}
@@ -470,56 +483,61 @@ unsafe impl Sync for DioxusWebsysEvent {}
// todo: some of these events are being casted to the wrong event type.
// We need tests that simulate clicks/etc and make sure every event type works.
fn virtual_event_from_websys_event(event: web_sys::Event) -> SyntheticEvent {
fn virtual_event_from_websys_event(event: web_sys::Event) -> Box<dyn Any + Send> {
use crate::events::*;
use dioxus_core::events::on::*;
match event.type_().as_str() {
"copy" | "cut" | "paste" => SyntheticEvent::ClipboardEvent(ClipboardEvent(
DioxusEvent::new(ClipboardEventInner(), DioxusWebsysEvent(event)),
)),
"copy" | "cut" | "paste" => Box::new(ClipboardEvent {}),
"compositionend" | "compositionstart" | "compositionupdate" => {
let evt: &web_sys::CompositionEvent = event.dyn_ref().unwrap();
SyntheticEvent::CompositionEvent(CompositionEvent(DioxusEvent::new(
CompositionEventInner {
data: evt.data().unwrap_or_default(),
},
DioxusWebsysEvent(event),
)))
Box::new(CompositionEvent {
data: evt.data().unwrap_or_default(),
})
}
"keydown" | "keypress" | "keyup" => {
let evt: &web_sys::KeyboardEvent = event.dyn_ref().unwrap();
SyntheticEvent::KeyboardEvent(KeyboardEvent(DioxusEvent::new(
KeyboardEventInner {
alt_key: evt.alt_key(),
char_code: evt.char_code(),
key: evt.key(),
key_code: KeyCode::from_raw_code(evt.key_code() as u8),
ctrl_key: evt.ctrl_key(),
locale: "not implemented".to_string(),
location: evt.location() as usize,
meta_key: evt.meta_key(),
repeat: evt.repeat(),
shift_key: evt.shift_key(),
which: evt.which() as usize,
},
DioxusWebsysEvent(event),
)))
Box::new(KeyboardEvent {
alt_key: evt.alt_key(),
char_code: evt.char_code(),
key: evt.key(),
key_code: KeyCode::from_raw_code(evt.key_code() as u8),
ctrl_key: evt.ctrl_key(),
locale: "not implemented".to_string(),
location: evt.location() as usize,
meta_key: evt.meta_key(),
repeat: evt.repeat(),
shift_key: evt.shift_key(),
which: evt.which() as usize,
})
}
"focus" | "blur" => SyntheticEvent::FocusEvent(FocusEvent(DioxusEvent::new(
FocusEventInner {},
DioxusWebsysEvent(event),
))),
"change" => SyntheticEvent::GenericEvent(DioxusEvent::new((), DioxusWebsysEvent(event))),
"focus" | "blur" => {
//
Box::new(FocusEvent {})
}
// "change" => SyntheticEvent::GenericEvent(DioxusEvent::new((), DioxusWebsysEvent(event))),
// todo: these handlers might get really slow if the input box gets large and allocation pressure is heavy
// don't have a good solution with the serialized event problem
"input" | "invalid" | "reset" | "submit" => {
"change" | "input" | "invalid" | "reset" | "submit" => {
let evt: &web_sys::Event = event.dyn_ref().unwrap();
let target: web_sys::EventTarget = evt.target().unwrap();
let value: String = (&target)
.dyn_ref()
.map(|input: &web_sys::HtmlInputElement| input.value())
.map(|input: &web_sys::HtmlInputElement| {
// todo: special case more input types
match input.type_().as_str() {
"checkbox" => {
match input.checked() {
true => "true".to_string(),
false => "false".to_string(),
}
},
_ => {
input.value()
}
}
})
.or_else(|| {
target
.dyn_ref()
@@ -539,135 +557,112 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> SyntheticEvent {
})
.expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener");
SyntheticEvent::FormEvent(FormEvent(DioxusEvent::new(
FormEventInner { value },
DioxusWebsysEvent(event),
)))
Box::new(FormEvent { value })
}
"click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
| "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter"
| "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
let evt: &web_sys::MouseEvent = event.dyn_ref().unwrap();
SyntheticEvent::MouseEvent(MouseEvent(DioxusEvent::new(
MouseEventInner {
alt_key: evt.alt_key(),
button: evt.button(),
buttons: evt.buttons(),
client_x: evt.client_x(),
client_y: evt.client_y(),
ctrl_key: evt.ctrl_key(),
meta_key: evt.meta_key(),
screen_x: evt.screen_x(),
screen_y: evt.screen_y(),
shift_key: evt.shift_key(),
page_x: evt.page_x(),
page_y: evt.page_y(),
},
DioxusWebsysEvent(event),
)))
Box::new(MouseEvent {
alt_key: evt.alt_key(),
button: evt.button(),
buttons: evt.buttons(),
client_x: evt.client_x(),
client_y: evt.client_y(),
ctrl_key: evt.ctrl_key(),
meta_key: evt.meta_key(),
screen_x: evt.screen_x(),
screen_y: evt.screen_y(),
shift_key: evt.shift_key(),
page_x: evt.page_x(),
page_y: evt.page_y(),
})
}
"pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
| "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
let evt: &web_sys::PointerEvent = event.dyn_ref().unwrap();
SyntheticEvent::PointerEvent(PointerEvent(DioxusEvent::new(
PointerEventInner {
alt_key: evt.alt_key(),
button: evt.button(),
buttons: evt.buttons(),
client_x: evt.client_x(),
client_y: evt.client_y(),
ctrl_key: evt.ctrl_key(),
meta_key: evt.meta_key(),
page_x: evt.page_x(),
page_y: evt.page_y(),
screen_x: evt.screen_x(),
screen_y: evt.screen_y(),
shift_key: evt.shift_key(),
pointer_id: evt.pointer_id(),
width: evt.width(),
height: evt.height(),
pressure: evt.pressure(),
tangential_pressure: evt.tangential_pressure(),
tilt_x: evt.tilt_x(),
tilt_y: evt.tilt_y(),
twist: evt.twist(),
pointer_type: evt.pointer_type(),
is_primary: evt.is_primary(),
// get_modifier_state: evt.get_modifier_state(),
},
DioxusWebsysEvent(event),
)))
Box::new(PointerEvent {
alt_key: evt.alt_key(),
button: evt.button(),
buttons: evt.buttons(),
client_x: evt.client_x(),
client_y: evt.client_y(),
ctrl_key: evt.ctrl_key(),
meta_key: evt.meta_key(),
page_x: evt.page_x(),
page_y: evt.page_y(),
screen_x: evt.screen_x(),
screen_y: evt.screen_y(),
shift_key: evt.shift_key(),
pointer_id: evt.pointer_id(),
width: evt.width(),
height: evt.height(),
pressure: evt.pressure(),
tangential_pressure: evt.tangential_pressure(),
tilt_x: evt.tilt_x(),
tilt_y: evt.tilt_y(),
twist: evt.twist(),
pointer_type: evt.pointer_type(),
is_primary: evt.is_primary(),
// get_modifier_state: evt.get_modifier_state(),
})
}
"select" => SyntheticEvent::SelectionEvent(SelectionEvent(DioxusEvent::new(
SelectionEventInner {},
DioxusWebsysEvent(event),
))),
"select" => Box::new(SelectionEvent {}),
"touchcancel" | "touchend" | "touchmove" | "touchstart" => {
let evt: &web_sys::TouchEvent = event.dyn_ref().unwrap();
SyntheticEvent::TouchEvent(TouchEvent(DioxusEvent::new(
TouchEventInner {
alt_key: evt.alt_key(),
ctrl_key: evt.ctrl_key(),
meta_key: evt.meta_key(),
shift_key: evt.shift_key(),
},
DioxusWebsysEvent(event),
)))
Box::new(TouchEvent {
alt_key: evt.alt_key(),
ctrl_key: evt.ctrl_key(),
meta_key: evt.meta_key(),
shift_key: evt.shift_key(),
})
}
"scroll" => SyntheticEvent::GenericEvent(DioxusEvent::new((), DioxusWebsysEvent(event))),
"scroll" => Box::new(()),
"wheel" => {
let evt: &web_sys::WheelEvent = event.dyn_ref().unwrap();
SyntheticEvent::WheelEvent(WheelEvent(DioxusEvent::new(
WheelEventInner {
delta_x: evt.delta_x(),
delta_y: evt.delta_y(),
delta_z: evt.delta_z(),
delta_mode: evt.delta_mode(),
},
DioxusWebsysEvent(event),
)))
Box::new(WheelEvent {
delta_x: evt.delta_x(),
delta_y: evt.delta_y(),
delta_z: evt.delta_z(),
delta_mode: evt.delta_mode(),
})
}
"animationstart" | "animationend" | "animationiteration" => {
let evt: &web_sys::AnimationEvent = event.dyn_ref().unwrap();
SyntheticEvent::AnimationEvent(AnimationEvent(DioxusEvent::new(
AnimationEventInner {
elapsed_time: evt.elapsed_time(),
animation_name: evt.animation_name(),
pseudo_element: evt.pseudo_element(),
},
DioxusWebsysEvent(event),
)))
Box::new(AnimationEvent {
elapsed_time: evt.elapsed_time(),
animation_name: evt.animation_name(),
pseudo_element: evt.pseudo_element(),
})
}
"transitionend" => {
let evt: &web_sys::TransitionEvent = event.dyn_ref().unwrap();
SyntheticEvent::TransitionEvent(TransitionEvent(DioxusEvent::new(
TransitionEventInner {
elapsed_time: evt.elapsed_time(),
property_name: evt.property_name(),
pseudo_element: evt.pseudo_element(),
},
DioxusWebsysEvent(event),
)))
Box::new(TransitionEvent {
elapsed_time: evt.elapsed_time(),
property_name: evt.property_name(),
pseudo_element: evt.pseudo_element(),
})
}
"abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
| "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
| "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
| "timeupdate" | "volumechange" | "waiting" => SyntheticEvent::MediaEvent(MediaEvent(
DioxusEvent::new(MediaEventInner {}, DioxusWebsysEvent(event)),
)),
| "timeupdate" | "volumechange" | "waiting" => {
//
Box::new(MediaEvent {})
}
"toggle" => SyntheticEvent::ToggleEvent(ToggleEvent(DioxusEvent::new(
ToggleEventInner {},
DioxusWebsysEvent(event),
))),
"toggle" => {
//
Box::new(ToggleEvent {})
}
_ => SyntheticEvent::GenericEvent(DioxusEvent::new((), DioxusWebsysEvent(event))),
_ => Box::new(()),
}
}
+1 -1
View File
@@ -176,7 +176,7 @@ pub async fn run_with_props<T: 'static + Send>(root: FC<T>, root_props: T, cfg:
work_loop.wait_for_raf().await;
for mut edit in mutations {
log::debug!("edits are {:#?}", edit);
// log::debug!("edits are {:#?}", edit);
// actually apply our changes during the animation frame
websys_dom.process_edits(&mut edit.edits);
}