Merge branch 'upstream' into make-widgets-cross-framework
This commit is contained in:
commit
8b5f2cb02f
22 files changed
+380
-122
No files matched your search
@@ -92,6 +92,8 @@ With Coroutines, we can centralize our async logic. The `rx` parameter is an Cha
|
|||||||
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use futures_util::stream::StreamExt;
|
||||||
|
|
||||||
enum ProfileUpdate {
|
enum ProfileUpdate {
|
||||||
SetUsername(String),
|
SetUsername(String),
|
||||||
SetAge(i32)
|
SetAge(i32)
|
||||||
@@ -117,6 +119,10 @@ cx.render(rsx!{
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
> Note: In order to use/run the `rx.next().await` statement you will need to extend the [`Stream`] trait (used by [`UnboundedReceiver`]) by adding 'futures_util' as a dependency to your project and adding the `use futures_util::stream::StreamExt;`.
|
||||||
|
|
||||||
|
|
||||||
For sufficiently complex apps, we could build a bunch of different useful "services" that loop on channels to update the app.
|
For sufficiently complex apps, we could build a bunch of different useful "services" that loop on channels to update the app.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@@ -164,6 +170,8 @@ fn Banner(cx: Scope) -> Element {
|
|||||||
Now, in our sync service, we can structure our state however we want. We only need to update the view values when ready.
|
Now, in our sync service, we can structure our state however we want. We only need to update the view values when ready.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use futures_util::stream::StreamExt;
|
||||||
|
|
||||||
enum SyncAction {
|
enum SyncAction {
|
||||||
SetUsername(String),
|
SetUsername(String),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,5 +22,5 @@ You can use [`cx.use_hook`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.
|
|||||||
|
|
||||||
Inside the initialization closure, you will typically make calls to other `cx` methods. For example:
|
Inside the initialization closure, you will typically make calls to other `cx` methods. For example:
|
||||||
|
|
||||||
- The `use_state` hook tracks state in the hook value, and uses [`cx.schedule_update`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.Scope.html#method.schedule_update) to make Dioxus re-render the component whenever it changes.
|
- The `use_state` hook tracks state in the hook value, and uses [`cx.schedule_update`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.ScopeState.html#method.schedule_update) to make Dioxus re-render the component whenever it changes.
|
||||||
- The `use_context` hook calls [`cx.consume_context`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.Scope.html#method.consume_context) (which would be expensive to call on every render) to get some context from the scope
|
- The `use_context` hook calls [`cx.consume_context`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.ScopeState.html#method.consume_context) (which would be expensive to call on every render) to get some context from the scope
|
||||||
+86
-39
@@ -7,7 +7,7 @@ fn main() {
|
|||||||
dioxus_desktop::launch(app);
|
dioxus_desktop::launch(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq)]
|
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||||
pub enum FilterState {
|
pub enum FilterState {
|
||||||
All,
|
All,
|
||||||
Active,
|
Active,
|
||||||
@@ -39,56 +39,99 @@ pub fn app(cx: Scope<()>) -> Element {
|
|||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
filtered_todos.sort_unstable();
|
filtered_todos.sort_unstable();
|
||||||
|
|
||||||
let show_clear_completed = todos.values().any(|todo| todo.checked);
|
let active_todo_count = todos.values().filter(|item| !item.checked).count();
|
||||||
let items_left = filtered_todos.len();
|
let active_todo_text = match active_todo_count {
|
||||||
let item_text = match items_left {
|
|
||||||
1 => "item",
|
1 => "item",
|
||||||
_ => "items",
|
_ => "items",
|
||||||
};
|
};
|
||||||
|
|
||||||
cx.render(rsx!{
|
let show_clear_completed = todos.values().any(|todo| todo.checked);
|
||||||
|
|
||||||
|
let selected = |state| {
|
||||||
|
if *filter == state {
|
||||||
|
"selected"
|
||||||
|
} else {
|
||||||
|
"false"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
cx.render(rsx! {
|
||||||
section { class: "todoapp",
|
section { class: "todoapp",
|
||||||
style { include_str!("./assets/todomvc.css") }
|
style { include_str!("./assets/todomvc.css") }
|
||||||
div {
|
header { class: "header",
|
||||||
header { class: "header",
|
h1 {"todos"}
|
||||||
h1 {"todos"}
|
input {
|
||||||
input {
|
class: "new-todo",
|
||||||
class: "new-todo",
|
placeholder: "What needs to be done?",
|
||||||
placeholder: "What needs to be done?",
|
value: "{draft}",
|
||||||
value: "{draft}",
|
autofocus: "true",
|
||||||
autofocus: "true",
|
oninput: move |evt| {
|
||||||
oninput: move |evt| {
|
draft.set(evt.value.clone());
|
||||||
draft.set(evt.value.clone());
|
},
|
||||||
},
|
onkeydown: move |evt| {
|
||||||
onkeydown: move |evt| {
|
if evt.key() == Key::Enter && !draft.is_empty() {
|
||||||
if evt.key() == Key::Enter && !draft.is_empty() {
|
todos.make_mut().insert(
|
||||||
todos.make_mut().insert(
|
**todo_id,
|
||||||
**todo_id,
|
TodoItem {
|
||||||
TodoItem {
|
id: **todo_id,
|
||||||
id: **todo_id,
|
checked: false,
|
||||||
checked: false,
|
contents: draft.to_string(),
|
||||||
contents: draft.to_string(),
|
},
|
||||||
},
|
);
|
||||||
);
|
*todo_id.make_mut() += 1;
|
||||||
*todo_id.make_mut() += 1;
|
draft.set("".to_string());
|
||||||
draft.set("".to_string());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
section {
|
||||||
|
class: "main",
|
||||||
|
if !todos.is_empty() {
|
||||||
|
rsx! {
|
||||||
|
input {
|
||||||
|
id: "toggle-all",
|
||||||
|
class: "toggle-all",
|
||||||
|
r#type: "checkbox",
|
||||||
|
onchange: move |_| {
|
||||||
|
let check = active_todo_count != 0;
|
||||||
|
for (_, item) in todos.make_mut().iter_mut() {
|
||||||
|
item.checked = check;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
checked: if active_todo_count == 0 { "true" } else { "false" },
|
||||||
|
}
|
||||||
|
label { r#for: "toggle-all" }
|
||||||
|
}
|
||||||
|
}
|
||||||
ul { class: "todo-list",
|
ul { class: "todo-list",
|
||||||
filtered_todos.iter().map(|id| rsx!(TodoEntry { key: "{id}", id: *id, todos: todos }))
|
filtered_todos.iter().map(|id| rsx!(TodoEntry {
|
||||||
|
key: "{id}",
|
||||||
|
id: *id,
|
||||||
|
todos: todos,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
(!todos.is_empty()).then(|| rsx!(
|
(!todos.is_empty()).then(|| rsx!(
|
||||||
footer { class: "footer",
|
footer { class: "footer",
|
||||||
span { class: "todo-count",
|
span { class: "todo-count",
|
||||||
strong {"{items_left} "}
|
strong {"{active_todo_count} "}
|
||||||
span {"{item_text} left"}
|
span {"{active_todo_text} left"}
|
||||||
}
|
}
|
||||||
ul { class: "filters",
|
ul { class: "filters",
|
||||||
li { class: "All", a { onclick: move |_| filter.set(FilterState::All), "All" }}
|
for (state, state_text, url) in [
|
||||||
li { class: "Active", a { onclick: move |_| filter.set(FilterState::Active), "Active" }}
|
(FilterState::All, "All", "#/"),
|
||||||
li { class: "Completed", a { onclick: move |_| filter.set(FilterState::Completed), "Completed" }}
|
(FilterState::Active, "Active", "#/active"),
|
||||||
|
(FilterState::Completed, "Completed", "#/completed"),
|
||||||
|
] {
|
||||||
|
li {
|
||||||
|
a {
|
||||||
|
href: url,
|
||||||
|
class: selected(state),
|
||||||
|
onclick: move |_| filter.set(state),
|
||||||
|
prevent_default: "onclick",
|
||||||
|
state_text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
show_clear_completed.then(|| rsx!(
|
show_clear_completed.then(|| rsx!(
|
||||||
button {
|
button {
|
||||||
@@ -102,8 +145,8 @@ pub fn app(cx: Scope<()>) -> Element {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
footer { class: "info",
|
footer { class: "info",
|
||||||
p {"Double-click to edit a todo"}
|
p { "Double-click to edit a todo" }
|
||||||
p { "Created by ", a { href: "http://github.com/jkelleyrtp/", "jkelleyrtp" }}
|
p { "Created by ", a { href: "http://github.com/jkelleyrtp/", "jkelleyrtp" }}
|
||||||
p { "Part of ", a { href: "http://todomvc.com", "TodoMVC" }}
|
p { "Part of ", a { href: "http://todomvc.com", "TodoMVC" }}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -136,13 +179,17 @@ pub fn TodoEntry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element {
|
|||||||
cx.props.todos.make_mut()[&cx.props.id].checked = evt.value.parse().unwrap();
|
cx.props.todos.make_mut()[&cx.props.id].checked = evt.value.parse().unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
r#for: "cbg-{todo.id}",
|
r#for: "cbg-{todo.id}",
|
||||||
onclick: move |_| is_editing.set(true),
|
ondblclick: move |_| is_editing.set(true),
|
||||||
prevent_default: "onclick",
|
prevent_default: "onclick",
|
||||||
"{todo.contents}"
|
"{todo.contents}"
|
||||||
}
|
}
|
||||||
|
button {
|
||||||
|
class: "destroy",
|
||||||
|
onclick: move |_| { cx.props.todos.make_mut().remove(&todo.id); },
|
||||||
|
prevent_default: "onclick",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
is_editing.then(|| rsx!{
|
is_editing.then(|| rsx!{
|
||||||
input {
|
input {
|
||||||
|
|||||||
@@ -2,7 +2,13 @@
|
|||||||
name = "dioxus-autofmt"
|
name = "dioxus-autofmt"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
authors = ["Jonathan Kelley"]
|
||||||
|
description = "Autofomatter for Dioxus RSX"
|
||||||
|
license = "MIT/Apache-2.0"
|
||||||
|
repository = "https://github.com/DioxusLabs/dioxus/"
|
||||||
|
homepage = "https://dioxuslabs.com"
|
||||||
|
documentation = "https://dioxuslabs.com"
|
||||||
|
keywords = ["dom", "ui", "gui", "react"]
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
@@ -11,7 +17,7 @@ proc-macro2 = { version = "1.0.6", features = ["span-locations"] }
|
|||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
syn = { version = "1.0.11", features = ["full", "extra-traits"] }
|
syn = { version = "1.0.11", features = ["full", "extra-traits"] }
|
||||||
serde = { version = "1.0.136", features = ["derive"] }
|
serde = { version = "1.0.136", features = ["derive"] }
|
||||||
prettyplease = { git = "https://github.com/DioxusLabs/prettyplease-macro-fmt.git", features = [
|
prettyplease = { package = "prettier-please", version = "0.1.16", features = [
|
||||||
"verbatim",
|
"verbatim",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ fn sort_bfs(paths: &[&'static [u8]]) -> Vec<(usize, &'static [u8])> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
fn sorting() {
|
fn sorting() {
|
||||||
let r: [(usize, &[u8]); 5] = [
|
let r: [(usize, &[u8]); 5] = [
|
||||||
(0, &[0, 1]),
|
(0, &[0, 1]),
|
||||||
|
|||||||
+25
-12
@@ -148,6 +148,13 @@ impl<'b> VirtualDom {
|
|||||||
|
|
||||||
// Make sure the roots get transferred over while we're here
|
// Make sure the roots get transferred over while we're here
|
||||||
right_template.root_ids.transfer(&left_template.root_ids);
|
right_template.root_ids.transfer(&left_template.root_ids);
|
||||||
|
|
||||||
|
// Update the node refs
|
||||||
|
for i in 0..right_template.root_ids.len() {
|
||||||
|
if let Some(root_id) = right_template.root_ids.get(i) {
|
||||||
|
self.update_template(root_id, right_template);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn diff_dynamic_node(
|
fn diff_dynamic_node(
|
||||||
@@ -628,10 +635,12 @@ impl<'b> VirtualDom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let id = self.find_last_element(&new[last]);
|
let id = self.find_last_element(&new[last]);
|
||||||
self.mutations.push(Mutation::InsertAfter {
|
if nodes_created > 0 {
|
||||||
id,
|
self.mutations.push(Mutation::InsertAfter {
|
||||||
m: nodes_created,
|
id,
|
||||||
});
|
m: nodes_created,
|
||||||
|
})
|
||||||
|
}
|
||||||
nodes_created = 0;
|
nodes_created = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -652,10 +661,12 @@ impl<'b> VirtualDom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let id = self.find_first_element(&new[last]);
|
let id = self.find_first_element(&new[last]);
|
||||||
self.mutations.push(Mutation::InsertBefore {
|
if nodes_created > 0 {
|
||||||
id,
|
self.mutations.push(Mutation::InsertBefore {
|
||||||
m: nodes_created,
|
id,
|
||||||
});
|
m: nodes_created,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
nodes_created = 0;
|
nodes_created = 0;
|
||||||
}
|
}
|
||||||
@@ -676,10 +687,12 @@ impl<'b> VirtualDom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let id = self.find_first_element(&new[first_lis]);
|
let id = self.find_first_element(&new[first_lis]);
|
||||||
self.mutations.push(Mutation::InsertBefore {
|
if nodes_created > 0 {
|
||||||
id,
|
self.mutations.push(Mutation::InsertBefore {
|
||||||
m: nodes_created,
|
id,
|
||||||
});
|
m: nodes_created,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ pub enum Mutation<'a> {
|
|||||||
/// The ID of the element being mounted to
|
/// The ID of the element being mounted to
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
|
|
||||||
/// The number of nodes on the stack
|
/// The number of nodes on the stack to append to the target element
|
||||||
m: usize,
|
m: usize,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ pub enum Mutation<'a> {
|
|||||||
/// The ID of the node we're going to replace with
|
/// The ID of the node we're going to replace with
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
|
|
||||||
/// The number of nodes on the stack to use to replace
|
/// The number of nodes on the stack to replace the target element with
|
||||||
m: usize,
|
m: usize,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@ pub enum Mutation<'a> {
|
|||||||
/// `[0,1,2]` represents 1st child's 2nd child's 3rd child.
|
/// `[0,1,2]` represents 1st child's 2nd child's 3rd child.
|
||||||
path: &'static [u8],
|
path: &'static [u8],
|
||||||
|
|
||||||
/// The number of nodes on the stack to use to replace
|
/// The number of nodes on the stack to replace the target element with
|
||||||
m: usize,
|
m: usize,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -176,7 +176,7 @@ pub enum Mutation<'a> {
|
|||||||
/// The ID of the node to insert after.
|
/// The ID of the node to insert after.
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
|
|
||||||
/// The ids of the nodes to insert after the target node.
|
/// The number of nodes on the stack to insert after the target node.
|
||||||
m: usize,
|
m: usize,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -185,7 +185,7 @@ pub enum Mutation<'a> {
|
|||||||
/// The ID of the node to insert before.
|
/// The ID of the node to insert before.
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
|
|
||||||
/// The ids of the nodes to insert before the target node.
|
/// The number of nodes on the stack to insert before the target node.
|
||||||
m: usize,
|
m: usize,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ pub struct Config {
|
|||||||
pub(crate) pre_rendered: Option<String>,
|
pub(crate) pre_rendered: Option<String>,
|
||||||
pub(crate) disable_context_menu: bool,
|
pub(crate) disable_context_menu: bool,
|
||||||
pub(crate) resource_dir: Option<PathBuf>,
|
pub(crate) resource_dir: Option<PathBuf>,
|
||||||
|
pub(crate) data_dir: Option<PathBuf>,
|
||||||
pub(crate) custom_head: Option<String>,
|
pub(crate) custom_head: Option<String>,
|
||||||
pub(crate) custom_index: Option<String>,
|
pub(crate) custom_index: Option<String>,
|
||||||
pub(crate) root_name: String,
|
pub(crate) root_name: String,
|
||||||
@@ -44,6 +45,7 @@ impl Config {
|
|||||||
pre_rendered: None,
|
pre_rendered: None,
|
||||||
disable_context_menu: !cfg!(debug_assertions),
|
disable_context_menu: !cfg!(debug_assertions),
|
||||||
resource_dir: None,
|
resource_dir: None,
|
||||||
|
data_dir: None,
|
||||||
custom_head: None,
|
custom_head: None,
|
||||||
custom_index: None,
|
custom_index: None,
|
||||||
root_name: "main".to_string(),
|
root_name: "main".to_string(),
|
||||||
@@ -56,6 +58,14 @@ impl Config {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// set the directory where data will be stored in release mode.
|
||||||
|
///
|
||||||
|
/// > Note: This **must** be set when bundling on Windows.
|
||||||
|
pub fn with_data_directory(mut self, path: impl Into<PathBuf>) -> Self {
|
||||||
|
self.data_dir = Some(path.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Set whether or not the right-click context menu should be disabled.
|
/// Set whether or not the right-click context menu should be disabled.
|
||||||
pub fn with_disable_context_menu(mut self, disable: bool) -> Self {
|
pub fn with_disable_context_menu(mut self, disable: bool) -> Self {
|
||||||
self.disable_context_menu = disable;
|
self.disable_context_menu = disable;
|
||||||
|
|||||||
@@ -54,6 +54,10 @@ pub fn build(
|
|||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// These are commented out because wry is currently broken in wry
|
||||||
|
// let mut web_context = WebContext::new(cfg.data_dir.clone());
|
||||||
|
// .with_web_context(&mut web_context);
|
||||||
|
|
||||||
for (name, handler) in cfg.protocols.drain(..) {
|
for (name, handler) in cfg.protocols.drain(..) {
|
||||||
webview = webview.with_custom_protocol(name, handler)
|
webview = webview.with_custom_protocol(name, handler)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,26 @@ fn NameCard(cx: Scope) -> Element {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If needed, we can update the atom's value, based on itself:
|
||||||
|
|
||||||
|
```rust, ignore
|
||||||
|
static COUNT: Atom<i32> = |_| 0;
|
||||||
|
|
||||||
|
fn Counter(cx: Scope) -> Element {
|
||||||
|
let mut count = use_atom_state(cx, COUNT);
|
||||||
|
|
||||||
|
cx.render(rsx!{
|
||||||
|
p {
|
||||||
|
"{count}"
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
onclick: move |_| count += 1,
|
||||||
|
"Increment counter"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
It's that simple!
|
It's that simple!
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|||||||
@@ -5,6 +5,16 @@ use std::convert::TryInto;
|
|||||||
use std::fmt::{Debug, Formatter};
|
use std::fmt::{Debug, Formatter};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[cfg(feature = "serialize")]
|
||||||
|
fn resilient_deserialize_code<'de, D>(deserializer: D) -> Result<Code, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
use serde::Deserialize;
|
||||||
|
// If we fail to deserialize the code for any reason, just return Unidentified instead of failing.
|
||||||
|
Ok(Code::deserialize(deserializer).unwrap_or(Code::Unidentified))
|
||||||
|
}
|
||||||
|
|
||||||
pub type KeyboardEvent = Event<KeyboardData>;
|
pub type KeyboardEvent = Event<KeyboardData>;
|
||||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[derive(Clone, PartialEq, Eq)]
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
@@ -27,6 +37,10 @@ pub struct KeyboardData {
|
|||||||
pub key_code: KeyCode,
|
pub key_code: KeyCode,
|
||||||
|
|
||||||
/// the physical key on the keyboard
|
/// the physical key on the keyboard
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serialize",
|
||||||
|
serde(deserialize_with = "resilient_deserialize_code")
|
||||||
|
)]
|
||||||
code: Code,
|
code: Code,
|
||||||
|
|
||||||
/// Indicate if the `alt` modifier key was pressed during this keyboard event
|
/// Indicate if the `alt` modifier key was pressed during this keyboard event
|
||||||
@@ -102,7 +116,7 @@ impl KeyboardData {
|
|||||||
/// The value of the key pressed by the user, taking into consideration the state of modifier keys such as Shift as well as the keyboard locale and layout.
|
/// The value of the key pressed by the user, taking into consideration the state of modifier keys such as Shift as well as the keyboard locale and layout.
|
||||||
pub fn key(&self) -> Key {
|
pub fn key(&self) -> Key {
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
FromStr::from_str(&self.key).expect("could not parse")
|
FromStr::from_str(&self.key).unwrap_or(Key::Unidentified)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A physical key on the keyboard (as opposed to the character generated by pressing the key). In other words, this property returns a value that isn't altered by keyboard layout or the state of the modifier keys.
|
/// A physical key on the keyboard (as opposed to the character generated by pressing the key). In other words, this property returns a value that isn't altered by keyboard layout or the state of the modifier keys.
|
||||||
@@ -158,10 +172,24 @@ impl Debug for KeyboardData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg(feature = "serialize")]
|
||||||
feature = "serialize",
|
impl<'de> serde::Deserialize<'de> for KeyCode {
|
||||||
derive(serde_repr::Serialize_repr, serde_repr::Deserialize_repr)
|
fn deserialize<D>(deserializer: D) -> Result<KeyCode, D::Error>
|
||||||
)]
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
// We could be deserializing a unicode character, so we need to use u64 even if the output only takes u8
|
||||||
|
let value = u64::deserialize(deserializer)?;
|
||||||
|
|
||||||
|
if let Ok(smaller_uint) = value.try_into() {
|
||||||
|
Ok(KeyCode::from_raw_code(smaller_uint))
|
||||||
|
} else {
|
||||||
|
Ok(KeyCode::Unknown)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "serialize", derive(serde_repr::Serialize_repr))]
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum KeyCode {
|
pub enum KeyCode {
|
||||||
@@ -525,7 +553,6 @@ pub enum KeyCode {
|
|||||||
// kanji, = 244
|
// kanji, = 244
|
||||||
// unlock trackpad (Chrome/Edge), = 251
|
// unlock trackpad (Chrome/Edge), = 251
|
||||||
// toggle touchpad, = 255
|
// toggle touchpad, = 255
|
||||||
#[cfg_attr(feature = "serialize", serde(other))]
|
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ pub struct DioxusState {
|
|||||||
|
|
||||||
impl DioxusState {
|
impl DioxusState {
|
||||||
/// Initialize the DioxusState in the RealDom
|
/// Initialize the DioxusState in the RealDom
|
||||||
pub fn create(rdom: &mut RealDom) -> Self {
|
pub fn create<V: FromAnyValue + Send + Sync>(rdom: &mut RealDom<V>) -> Self {
|
||||||
let root_id = rdom.root_id();
|
let root_id = rdom.root_id();
|
||||||
let mut root = rdom.get_mut(root_id).unwrap();
|
let mut root = rdom.get_mut(root_id).unwrap();
|
||||||
root.insert(ElementIdComponent(ElementId(0)));
|
root.insert(ElementIdComponent(ElementId(0)));
|
||||||
@@ -48,7 +48,11 @@ impl DioxusState {
|
|||||||
self.node_id_mapping.get(element_id.0).copied().flatten()
|
self.node_id_mapping.get(element_id.0).copied().flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_element_id(&mut self, mut node: NodeMut, element_id: ElementId) {
|
fn set_element_id<V: FromAnyValue + Send + Sync>(
|
||||||
|
&mut self,
|
||||||
|
mut node: NodeMut<V>,
|
||||||
|
element_id: ElementId,
|
||||||
|
) {
|
||||||
let node_id = node.id();
|
let node_id = node.id();
|
||||||
node.insert(ElementIdComponent(element_id));
|
node.insert(ElementIdComponent(element_id));
|
||||||
if self.node_id_mapping.len() <= element_id.0 {
|
if self.node_id_mapping.len() <= element_id.0 {
|
||||||
@@ -57,7 +61,7 @@ impl DioxusState {
|
|||||||
self.node_id_mapping[element_id.0] = Some(node_id);
|
self.node_id_mapping[element_id.0] = Some(node_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_child(&self, rdom: &RealDom, path: &[u8]) -> NodeId {
|
fn load_child<V: FromAnyValue + Send + Sync>(&self, rdom: &RealDom<V>, path: &[u8]) -> NodeId {
|
||||||
let mut current = rdom.get(*self.stack.last().unwrap()).unwrap();
|
let mut current = rdom.get(*self.stack.last().unwrap()).unwrap();
|
||||||
for i in path {
|
for i in path {
|
||||||
let new_id = current.child_ids()[*i as usize];
|
let new_id = current.child_ids()[*i as usize];
|
||||||
@@ -67,7 +71,11 @@ impl DioxusState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the dom with some mutations and return a set of nodes that were updated. Pass the dirty nodes to update_state.
|
/// Updates the dom with some mutations and return a set of nodes that were updated. Pass the dirty nodes to update_state.
|
||||||
pub fn apply_mutations(&mut self, rdom: &mut RealDom, mutations: Mutations) {
|
pub fn apply_mutations<V: FromAnyValue + Send + Sync>(
|
||||||
|
&mut self,
|
||||||
|
rdom: &mut RealDom<V>,
|
||||||
|
mutations: Mutations,
|
||||||
|
) {
|
||||||
for template in mutations.templates {
|
for template in mutations.templates {
|
||||||
let mut template_root_ids = Vec::new();
|
let mut template_root_ids = Vec::new();
|
||||||
for root in template.roots {
|
for root in template.roots {
|
||||||
@@ -222,7 +230,10 @@ impl DioxusState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_template_node(rdom: &mut RealDom, node: &TemplateNode) -> NodeId {
|
fn create_template_node<V: FromAnyValue + Send + Sync>(
|
||||||
|
rdom: &mut RealDom<V>,
|
||||||
|
node: &TemplateNode,
|
||||||
|
) -> NodeId {
|
||||||
match node {
|
match node {
|
||||||
TemplateNode::Element {
|
TemplateNode::Element {
|
||||||
tag,
|
tag,
|
||||||
|
|||||||
@@ -16,37 +16,27 @@ use crate::{NodeId, NodeMask};
|
|||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct DirtyNodes {
|
struct DirtyNodes {
|
||||||
passes_dirty: Vec<u64>,
|
nodes_dirty: FxHashSet<NodeId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DirtyNodes {
|
impl DirtyNodes {
|
||||||
pub fn add_node(&mut self, node_id: NodeId) {
|
pub fn add_node(&mut self, node_id: NodeId) {
|
||||||
let node_id = node_id.uindex();
|
self.nodes_dirty.insert(node_id);
|
||||||
let index = node_id / 64;
|
|
||||||
let bit = node_id % 64;
|
|
||||||
let encoded = 1 << bit;
|
|
||||||
if let Some(passes) = self.passes_dirty.get_mut(index) {
|
|
||||||
*passes |= encoded;
|
|
||||||
} else {
|
|
||||||
self.passes_dirty.resize(index + 1, 0);
|
|
||||||
self.passes_dirty[index] |= encoded;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.passes_dirty.iter().all(|dirty| *dirty == 0)
|
self.nodes_dirty.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pop(&mut self) -> Option<usize> {
|
pub fn pop(&mut self) -> Option<NodeId> {
|
||||||
let index = self.passes_dirty.iter().position(|dirty| *dirty != 0)?;
|
self.nodes_dirty.iter().next().copied().map(|id| {
|
||||||
let passes = self.passes_dirty[index];
|
self.nodes_dirty.remove(&id);
|
||||||
let node_id = passes.trailing_zeros();
|
id
|
||||||
let encoded = 1 << node_id;
|
})
|
||||||
self.passes_dirty[index] &= !encoded;
|
|
||||||
Some((index * 64) + node_id as usize)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tracks the dirty nodes sorted by height for each pass. We resolve passes based on the height of the node in order to avoid resolving any node twice in a pass.
|
||||||
#[derive(Clone, Unique)]
|
#[derive(Clone, Unique)]
|
||||||
pub struct DirtyNodeStates {
|
pub struct DirtyNodeStates {
|
||||||
dirty: Arc<FxHashMap<TypeId, RwLock<BTreeMap<u16, DirtyNodes>>>>,
|
dirty: Arc<FxHashMap<TypeId, RwLock<BTreeMap<u16, DirtyNodes>>>>,
|
||||||
@@ -76,7 +66,7 @@ impl DirtyNodeStates {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pop_front(&self, pass_id: TypeId) -> Option<(u16, usize)> {
|
fn pop_front(&self, pass_id: TypeId) -> Option<(u16, NodeId)> {
|
||||||
let mut values = self.dirty.get(&pass_id)?.write();
|
let mut values = self.dirty.get(&pass_id)?.write();
|
||||||
let mut value = values.first_entry()?;
|
let mut value = values.first_entry()?;
|
||||||
let height = *value.key();
|
let height = *value.key();
|
||||||
@@ -89,7 +79,7 @@ impl DirtyNodeStates {
|
|||||||
Some((height, id))
|
Some((height, id))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pop_back(&self, pass_id: TypeId) -> Option<(u16, usize)> {
|
fn pop_back(&self, pass_id: TypeId) -> Option<(u16, NodeId)> {
|
||||||
let mut values = self.dirty.get(&pass_id)?.write();
|
let mut values = self.dirty.get(&pass_id)?.write();
|
||||||
let mut value = values.last_entry()?;
|
let mut value = values.last_entry()?;
|
||||||
let height = *value.key();
|
let height = *value.key();
|
||||||
@@ -214,7 +204,6 @@ pub fn run_pass<V: FromAnyValue + Send + Sync>(
|
|||||||
match pass_direction {
|
match pass_direction {
|
||||||
PassDirection::ParentToChild => {
|
PassDirection::ParentToChild => {
|
||||||
while let Some((height, id)) = dirty.pop_front(type_id) {
|
while let Some((height, id)) = dirty.pop_front(type_id) {
|
||||||
let id = tree.id_at(id).unwrap();
|
|
||||||
if (update_node)(id, ctx) {
|
if (update_node)(id, ctx) {
|
||||||
nodes_updated.insert(id);
|
nodes_updated.insert(id);
|
||||||
for id in tree.children_ids(id) {
|
for id in tree.children_ids(id) {
|
||||||
@@ -227,7 +216,6 @@ pub fn run_pass<V: FromAnyValue + Send + Sync>(
|
|||||||
}
|
}
|
||||||
PassDirection::ChildToParent => {
|
PassDirection::ChildToParent => {
|
||||||
while let Some((height, id)) = dirty.pop_back(type_id) {
|
while let Some((height, id)) = dirty.pop_back(type_id) {
|
||||||
let id = tree.id_at(id).unwrap();
|
|
||||||
if (update_node)(id, ctx) {
|
if (update_node)(id, ctx) {
|
||||||
nodes_updated.insert(id);
|
nodes_updated.insert(id);
|
||||||
if let Some(id) = tree.parent_id(id) {
|
if let Some(id) = tree.parent_id(id) {
|
||||||
@@ -240,7 +228,6 @@ pub fn run_pass<V: FromAnyValue + Send + Sync>(
|
|||||||
}
|
}
|
||||||
PassDirection::AnyOrder => {
|
PassDirection::AnyOrder => {
|
||||||
while let Some((height, id)) = dirty.pop_back(type_id) {
|
while let Some((height, id)) = dirty.pop_back(type_id) {
|
||||||
let id = tree.id_at(id).unwrap();
|
|
||||||
if (update_node)(id, ctx) {
|
if (update_node)(id, ctx) {
|
||||||
nodes_updated.insert(id);
|
nodes_updated.insert(id);
|
||||||
for dependant in &dependants {
|
for dependant in &dependants {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ pub trait TreeMut: TreeRef {
|
|||||||
|
|
||||||
impl<'a> TreeRef for TreeRefView<'a> {
|
impl<'a> TreeRef for TreeRefView<'a> {
|
||||||
fn parent_id(&self, id: NodeId) -> Option<NodeId> {
|
fn parent_id(&self, id: NodeId) -> Option<NodeId> {
|
||||||
self.get(id).unwrap().parent
|
self.get(id).ok()?.parent
|
||||||
}
|
}
|
||||||
|
|
||||||
fn children_ids(&self, id: NodeId) -> Vec<NodeId> {
|
fn children_ids(&self, id: NodeId) -> Vec<NodeId> {
|
||||||
@@ -59,7 +59,7 @@ impl<'a> TreeRef for TreeRefView<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn height(&self, id: NodeId) -> Option<u16> {
|
fn height(&self, id: NodeId) -> Option<u16> {
|
||||||
Some(self.get(id).unwrap().height)
|
Some(self.get(id).ok()?.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn contains(&self, id: NodeId) -> bool {
|
fn contains(&self, id: NodeId) -> bool {
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ thiserror = "1.0.30"
|
|||||||
futures-util = "0.3.21"
|
futures-util = "0.3.21"
|
||||||
serde = { version = "1", optional = true }
|
serde = { version = "1", optional = true }
|
||||||
serde_urlencoded = { version = "0.7.1", optional = true }
|
serde_urlencoded = { version = "0.7.1", optional = true }
|
||||||
simple_logger = "4.0.0"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["query"]
|
default = ["query"]
|
||||||
@@ -46,7 +45,6 @@ wasm_test = []
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
console_error_panic_hook = "0.1.7"
|
console_error_panic_hook = "0.1.7"
|
||||||
log = "0.4.14"
|
|
||||||
wasm-logger = "0.2.0"
|
wasm-logger = "0.2.0"
|
||||||
wasm-bindgen-test = "0.3"
|
wasm-bindgen-test = "0.3"
|
||||||
gloo-utils = "0.1.2"
|
gloo-utils = "0.1.2"
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rsx-rosetta"
|
name = "rsx-rosetta"
|
||||||
version = "0.0.0"
|
version = "0.3.0"
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
|
authors = ["Jonathan Kelley"]
|
||||||
|
description = "Autofomatter for Dioxus RSX"
|
||||||
|
license = "MIT/Apache-2.0"
|
||||||
|
repository = "https://github.com/DioxusLabs/dioxus/"
|
||||||
|
homepage = "https://dioxuslabs.com"
|
||||||
|
documentation = "https://dioxuslabs.com"
|
||||||
|
keywords = ["dom", "ui", "gui", "react"]
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dioxus-autofmt = { path = "../autofmt" }
|
dioxus-autofmt = { path = "../autofmt", version = "0.3.0" }
|
||||||
dioxus-rsx = { path = "../rsx" , version = "^0.0.3" }
|
dioxus-rsx = { path = "../rsx" , version = "^0.0.3" }
|
||||||
html_parser = "0.6.3"
|
html_parser = "0.6.3"
|
||||||
proc-macro2 = "1.0.49"
|
proc-macro2 = "1.0.49"
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ pub struct FileMapBuildResult<Ctx: HotReloadingContext> {
|
|||||||
|
|
||||||
pub struct FileMap<Ctx: HotReloadingContext> {
|
pub struct FileMap<Ctx: HotReloadingContext> {
|
||||||
pub map: HashMap<PathBuf, (String, Option<Template<'static>>)>,
|
pub map: HashMap<PathBuf, (String, Option<Template<'static>>)>,
|
||||||
in_workspace: HashMap<PathBuf, bool>,
|
in_workspace: HashMap<PathBuf, Option<PathBuf>>,
|
||||||
phantom: std::marker::PhantomData<Ctx>,
|
phantom: std::marker::PhantomData<Ctx>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,11 +112,8 @@ impl<Ctx: HotReloadingContext> FileMap<Ctx> {
|
|||||||
) {
|
) {
|
||||||
// if the file!() macro is invoked in a workspace, the path is relative to the workspace root, otherwise it's relative to the crate root
|
// if the file!() macro is invoked in a workspace, the path is relative to the workspace root, otherwise it's relative to the crate root
|
||||||
// we need to check if the file is in a workspace or not and strip the prefix accordingly
|
// we need to check if the file is in a workspace or not and strip the prefix accordingly
|
||||||
let prefix = if in_workspace {
|
let prefix = if let Some(workspace) = &in_workspace {
|
||||||
crate_dir.parent().ok_or(io::Error::new(
|
workspace
|
||||||
io::ErrorKind::Other,
|
|
||||||
"Could not load workspace",
|
|
||||||
))?
|
|
||||||
} else {
|
} else {
|
||||||
crate_dir
|
crate_dir
|
||||||
};
|
};
|
||||||
@@ -173,9 +170,9 @@ impl<Ctx: HotReloadingContext> FileMap<Ctx> {
|
|||||||
Ok(UpdateResult::NeedsRebuild)
|
Ok(UpdateResult::NeedsRebuild)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn child_in_workspace(&mut self, crate_dir: &Path) -> io::Result<bool> {
|
fn child_in_workspace(&mut self, crate_dir: &Path) -> io::Result<Option<PathBuf>> {
|
||||||
if let Some(in_workspace) = self.in_workspace.get(crate_dir) {
|
if let Some(in_workspace) = self.in_workspace.get(crate_dir) {
|
||||||
Ok(*in_workspace)
|
Ok(in_workspace.clone())
|
||||||
} else {
|
} else {
|
||||||
let mut cmd = Cmd::new();
|
let mut cmd = Cmd::new();
|
||||||
let manafest_path = crate_dir.join("Cargo.toml");
|
let manafest_path = crate_dir.join("Cargo.toml");
|
||||||
@@ -186,9 +183,10 @@ impl<Ctx: HotReloadingContext> FileMap<Ctx> {
|
|||||||
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
|
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
|
||||||
|
|
||||||
let in_workspace = metadata.workspace_root != crate_dir;
|
let in_workspace = metadata.workspace_root != crate_dir;
|
||||||
|
let workspace_path = in_workspace.then(|| metadata.workspace_root.into());
|
||||||
self.in_workspace
|
self.in_workspace
|
||||||
.insert(crate_dir.to_path_buf(), in_workspace);
|
.insert(crate_dir.to_path_buf(), workspace_path.clone());
|
||||||
Ok(in_workspace)
|
Ok(workspace_path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,6 +59,11 @@ impl FromStr for IfmtInput {
|
|||||||
let mut current_captured = String::new();
|
let mut current_captured = String::new();
|
||||||
while let Some(c) = chars.next() {
|
while let Some(c) = chars.next() {
|
||||||
if c == ':' {
|
if c == ':' {
|
||||||
|
// two :s in a row is a path, not a format arg
|
||||||
|
if chars.next_if(|c| *c == ':').is_some() {
|
||||||
|
current_captured.push_str("::");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let mut current_format_args = String::new();
|
let mut current_format_args = String::new();
|
||||||
for c in chars.by_ref() {
|
for c in chars.by_ref() {
|
||||||
if c == '}' {
|
if c == '}' {
|
||||||
|
|||||||
@@ -17,6 +17,12 @@ pub enum Segment {
|
|||||||
Attr(usize),
|
Attr(usize),
|
||||||
Node(usize),
|
Node(usize),
|
||||||
PreRendered(String),
|
PreRendered(String),
|
||||||
|
/// A marker for where to insert a dynamic styles
|
||||||
|
StyleMarker {
|
||||||
|
// If the marker is inside a style tag or not
|
||||||
|
// This will be true if there are static styles
|
||||||
|
inside_style_tag: bool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Write for StringChain {
|
impl std::fmt::Write for StringChain {
|
||||||
@@ -61,16 +67,45 @@ impl StringCache {
|
|||||||
} => {
|
} => {
|
||||||
cur_path.push(root_idx);
|
cur_path.push(root_idx);
|
||||||
write!(chain, "<{tag}")?;
|
write!(chain, "<{tag}")?;
|
||||||
|
// we need to collect the styles and write them at the end
|
||||||
|
let mut styles = Vec::new();
|
||||||
|
let mut has_dynamic_attrs = false;
|
||||||
for attr in *attrs {
|
for attr in *attrs {
|
||||||
match attr {
|
match attr {
|
||||||
TemplateAttribute::Static { name, value, .. } => {
|
TemplateAttribute::Static {
|
||||||
write!(chain, " {name}=\"{value}\"")?;
|
name,
|
||||||
|
value,
|
||||||
|
namespace,
|
||||||
|
} => {
|
||||||
|
if let Some("style") = namespace {
|
||||||
|
styles.push((name, value));
|
||||||
|
} else {
|
||||||
|
write!(chain, " {name}=\"{value}\"")?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
TemplateAttribute::Dynamic { id: index } => {
|
TemplateAttribute::Dynamic { id: index } => {
|
||||||
chain.segments.push(Segment::Attr(*index))
|
chain.segments.push(Segment::Attr(*index));
|
||||||
|
has_dynamic_attrs = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// write the styles
|
||||||
|
if !styles.is_empty() {
|
||||||
|
write!(chain, " style=\"")?;
|
||||||
|
for (name, value) in styles {
|
||||||
|
write!(chain, "{name}:{value};")?;
|
||||||
|
}
|
||||||
|
chain.segments.push(Segment::StyleMarker {
|
||||||
|
inside_style_tag: true,
|
||||||
|
});
|
||||||
|
write!(chain, "\"")?;
|
||||||
|
} else if has_dynamic_attrs {
|
||||||
|
chain.segments.push(Segment::StyleMarker {
|
||||||
|
inside_style_tag: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if children.is_empty() && tag_is_self_closing(tag) {
|
if children.is_empty() && tag_is_self_closing(tag) {
|
||||||
write!(chain, "/>")?;
|
write!(chain, "/>")?;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -70,15 +70,24 @@ impl Renderer {
|
|||||||
.or_insert_with(|| Rc::new(StringCache::from_template(template).unwrap()))
|
.or_insert_with(|| Rc::new(StringCache::from_template(template).unwrap()))
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
|
// We need to keep track of the dynamic styles so we can insert them into the right place
|
||||||
|
let mut accumulated_dynamic_styles = Vec::new();
|
||||||
|
|
||||||
for segment in entry.segments.iter() {
|
for segment in entry.segments.iter() {
|
||||||
match segment {
|
match segment {
|
||||||
Segment::Attr(idx) => {
|
Segment::Attr(idx) => {
|
||||||
let attr = &template.dynamic_attrs[*idx];
|
let attr = &template.dynamic_attrs[*idx];
|
||||||
match attr.value {
|
if attr.namespace == Some("style") {
|
||||||
AttributeValue::Text(value) => write!(buf, " {}=\"{}\"", attr.name, value)?,
|
accumulated_dynamic_styles.push(attr);
|
||||||
AttributeValue::Bool(value) => write!(buf, " {}={}", attr.name, value)?,
|
} else {
|
||||||
_ => {}
|
match attr.value {
|
||||||
};
|
AttributeValue::Text(value) => {
|
||||||
|
write!(buf, " {}=\"{}\"", attr.name, value)?
|
||||||
|
}
|
||||||
|
AttributeValue::Bool(value) => write!(buf, " {}={}", attr.name, value)?,
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Segment::Node(idx) => match &template.dynamic_nodes[*idx] {
|
Segment::Node(idx) => match &template.dynamic_nodes[*idx] {
|
||||||
DynamicNode::Component(node) => {
|
DynamicNode::Component(node) => {
|
||||||
@@ -128,6 +137,34 @@ impl Renderer {
|
|||||||
},
|
},
|
||||||
|
|
||||||
Segment::PreRendered(contents) => write!(buf, "{contents}")?,
|
Segment::PreRendered(contents) => write!(buf, "{contents}")?,
|
||||||
|
|
||||||
|
Segment::StyleMarker { inside_style_tag } => {
|
||||||
|
if !accumulated_dynamic_styles.is_empty() {
|
||||||
|
// if we are inside a style tag, we don't need to write the style attribute
|
||||||
|
if !*inside_style_tag {
|
||||||
|
write!(buf, " style=\"")?;
|
||||||
|
}
|
||||||
|
for attr in &accumulated_dynamic_styles {
|
||||||
|
match attr.value {
|
||||||
|
AttributeValue::Text(value) => {
|
||||||
|
write!(buf, "{}:{};", attr.name, value)?
|
||||||
|
}
|
||||||
|
AttributeValue::Bool(value) => {
|
||||||
|
write!(buf, "{}:{};", attr.name, value)?
|
||||||
|
}
|
||||||
|
AttributeValue::Float(f) => write!(buf, "{}:{};", attr.name, f)?,
|
||||||
|
AttributeValue::Int(i) => write!(buf, "{}:{};", attr.name, i)?,
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if !*inside_style_tag {
|
||||||
|
write!(buf, "\"")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear the accumulated styles
|
||||||
|
accumulated_dynamic_styles.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,6 +205,9 @@ fn to_string_works() {
|
|||||||
vec![
|
vec![
|
||||||
PreRendered("<div class=\"asdasdasd\" class=\"asdasdasd\"".into(),),
|
PreRendered("<div class=\"asdasdasd\" class=\"asdasdasd\"".into(),),
|
||||||
Attr(0,),
|
Attr(0,),
|
||||||
|
StyleMarker {
|
||||||
|
inside_style_tag: false,
|
||||||
|
},
|
||||||
PreRendered(">Hello world 1 -->".into(),),
|
PreRendered(">Hello world 1 -->".into(),),
|
||||||
Node(0,),
|
Node(0,),
|
||||||
PreRendered(
|
PreRendered(
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn static_styles() {
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
render! { div { width: "100px" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut dom = VirtualDom::new(app);
|
||||||
|
_ = dom.rebuild();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
dioxus_ssr::render(&dom),
|
||||||
|
r#"<div style="width:100px;"></div>"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn partially_dynamic_styles() {
|
||||||
|
let dynamic = 123;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
dioxus_ssr::render_lazy(rsx! {
|
||||||
|
div { width: "100px", height: "{dynamic}px" }
|
||||||
|
}),
|
||||||
|
r#"<div style="width:100px;height:123px;"></div>"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dynamic_styles() {
|
||||||
|
let dynamic = 123;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
dioxus_ssr::render_lazy(rsx! {
|
||||||
|
div { width: "{dynamic}px" }
|
||||||
|
}),
|
||||||
|
r#"<div style="width:123px;"></div>"#
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -128,10 +128,11 @@ impl WebsysDom {
|
|||||||
mounted_id = Some(id);
|
mounted_id = Some(id);
|
||||||
let name = attribute.name;
|
let name = attribute.name;
|
||||||
if let AttributeValue::Listener(_) = value {
|
if let AttributeValue::Listener(_) = value {
|
||||||
|
let event_name = &name[2..];
|
||||||
self.interpreter.new_event_listener(
|
self.interpreter.new_event_listener(
|
||||||
&name[2..],
|
event_name,
|
||||||
id.0 as u32,
|
id.0 as u32,
|
||||||
event_bubbles(name) as u8,
|
event_bubbles(event_name) as u8,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user