Merge branch 'upstream' into query-system
This commit is contained in:
commit
223c7efce2
27 files changed
+480
-87
No files matched your search
@@ -35,6 +35,7 @@ fn sort_bfs(paths: &[&'static [u8]]) -> Vec<(usize, &'static [u8])> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(debug_assertions)]
|
||||
fn sorting() {
|
||||
let r: [(usize, &[u8]); 5] = [
|
||||
(0, &[0, 1]),
|
||||
|
||||
@@ -148,6 +148,13 @@ impl<'b> VirtualDom {
|
||||
|
||||
// Make sure the roots get transferred over while we're here
|
||||
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(
|
||||
|
||||
@@ -21,7 +21,7 @@ serde = "1.0.136"
|
||||
serde_json = "1.0.79"
|
||||
thiserror = "1.0.30"
|
||||
log = "0.4.14"
|
||||
wry = { version = "0.23.4" }
|
||||
wry = { version = "0.27.2" }
|
||||
futures-channel = "0.3.21"
|
||||
tokio = { version = "1.16.1", features = [
|
||||
"sync",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 15 KiB |
@@ -1,3 +1,4 @@
|
||||
use std::borrow::Cow;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use wry::application::window::Icon;
|
||||
@@ -28,7 +29,7 @@ type DropHandler = Box<dyn Fn(&Window, FileDropEvent) -> bool>;
|
||||
|
||||
pub(crate) type WryProtocol = (
|
||||
String,
|
||||
Box<dyn Fn(&HttpRequest<Vec<u8>>) -> WryResult<HttpResponse<Vec<u8>>> + 'static>,
|
||||
Box<dyn Fn(&HttpRequest<Vec<u8>>) -> WryResult<HttpResponse<Cow<'static, [u8]>>> + 'static>,
|
||||
);
|
||||
|
||||
impl Config {
|
||||
@@ -98,7 +99,7 @@ impl Config {
|
||||
/// Set a custom protocol
|
||||
pub fn with_custom_protocol<F>(mut self, name: String, handler: F) -> Self
|
||||
where
|
||||
F: Fn(&HttpRequest<Vec<u8>>) -> WryResult<HttpResponse<Vec<u8>>> + 'static,
|
||||
F: Fn(&HttpRequest<Vec<u8>>) -> WryResult<HttpResponse<Cow<'static, [u8]>>> + 'static,
|
||||
{
|
||||
self.protocols.push((name, Box::new(handler)));
|
||||
self
|
||||
|
||||
@@ -40,8 +40,8 @@ use tao::{
|
||||
};
|
||||
pub use wry;
|
||||
pub use wry::application as tao;
|
||||
use wry::application::window::WindowId;
|
||||
use wry::webview::WebView;
|
||||
use wry::{application::window::WindowId, webview::WebContext};
|
||||
|
||||
/// Launch the WebView and run the event loop.
|
||||
///
|
||||
@@ -311,7 +311,7 @@ fn create_new_window(
|
||||
event_handlers: &WindowEventHandlers,
|
||||
shortcut_manager: ShortcutRegistry,
|
||||
) -> WebviewHandler {
|
||||
let webview = webview::build(&mut cfg, event_loop, proxy.clone());
|
||||
let (webview, web_context) = webview::build(&mut cfg, event_loop, proxy.clone());
|
||||
|
||||
dom.base_scope().provide_context(DesktopContext::new(
|
||||
webview.clone(),
|
||||
@@ -329,6 +329,7 @@ fn create_new_window(
|
||||
webview,
|
||||
dom,
|
||||
waker: waker::tao_waker(proxy, id),
|
||||
web_context,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,6 +337,9 @@ struct WebviewHandler {
|
||||
dom: VirtualDom,
|
||||
webview: Rc<wry::webview::WebView>,
|
||||
waker: Waker,
|
||||
// This is nessisary because of a bug in wry. Wry assumes the webcontext is alive for the lifetime of the webview. We need to keep the webcontext alive, otherwise the webview will crash
|
||||
#[allow(dead_code)]
|
||||
web_context: WebContext,
|
||||
}
|
||||
|
||||
/// Poll the virtualdom until it's pending
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use dioxus_interpreter_js::INTERPRETER_JS;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use wry::{
|
||||
http::{status::StatusCode, Request, Response},
|
||||
Result,
|
||||
@@ -27,7 +30,7 @@ pub(super) fn desktop_handler(
|
||||
custom_head: Option<String>,
|
||||
custom_index: Option<String>,
|
||||
root_name: &str,
|
||||
) -> Result<Response<Vec<u8>>> {
|
||||
) -> Result<Response<Cow<'static, [u8]>>> {
|
||||
// If the request is for the root, we'll serve the index.html file.
|
||||
if request.uri().path() == "/" {
|
||||
// If a custom index is provided, just defer to that, expecting the user to know what they're doing.
|
||||
@@ -53,7 +56,7 @@ pub(super) fn desktop_handler(
|
||||
|
||||
return Response::builder()
|
||||
.header("Content-Type", "text/html")
|
||||
.body(body)
|
||||
.body(Cow::from(body))
|
||||
.map_err(From::from);
|
||||
}
|
||||
|
||||
@@ -72,13 +75,13 @@ pub(super) fn desktop_handler(
|
||||
if asset.exists() {
|
||||
return Response::builder()
|
||||
.header("Content-Type", get_mime_from_path(&asset)?)
|
||||
.body(std::fs::read(asset)?)
|
||||
.body(Cow::from(std::fs::read(asset)?))
|
||||
.map_err(From::from);
|
||||
}
|
||||
|
||||
Response::builder()
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.body(String::from("Not Found").into_bytes())
|
||||
.body(Cow::from(String::from("Not Found").into_bytes()))
|
||||
.map_err(From::from)
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ pub fn build(
|
||||
cfg: &mut Config,
|
||||
event_loop: &EventLoopWindowTarget<UserWindowEvent>,
|
||||
proxy: EventLoopProxy<UserWindowEvent>,
|
||||
) -> Rc<WebView> {
|
||||
) -> (Rc<WebView>, WebContext) {
|
||||
let builder = cfg.window.clone();
|
||||
let window = builder.build(event_loop).unwrap();
|
||||
let file_handler = cfg.file_drop_handler.take();
|
||||
@@ -57,6 +57,17 @@ pub fn build(
|
||||
})
|
||||
.with_web_context(&mut web_context);
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
// Windows has a platform specific settings to disable the browser shortcut keys
|
||||
use wry::webview::WebViewBuilderExtWindows;
|
||||
webview = webview.with_browser_accelerator_keys(false);
|
||||
}
|
||||
|
||||
// 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(..) {
|
||||
webview = webview.with_custom_protocol(name, handler)
|
||||
}
|
||||
@@ -81,5 +92,5 @@ pub fn build(
|
||||
webview = webview.with_devtools(true);
|
||||
}
|
||||
|
||||
Rc::new(webview.build().unwrap())
|
||||
(Rc::new(webview.build().unwrap()), web_context)
|
||||
}
|
||||
@@ -5,6 +5,16 @@ use std::convert::TryInto;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
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>;
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
@@ -27,6 +37,10 @@ pub struct KeyboardData {
|
||||
pub key_code: KeyCode,
|
||||
|
||||
/// the physical key on the keyboard
|
||||
#[cfg_attr(
|
||||
feature = "serialize",
|
||||
serde(deserialize_with = "resilient_deserialize_code")
|
||||
)]
|
||||
code: Code,
|
||||
|
||||
/// 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.
|
||||
pub fn key(&self) -> Key {
|
||||
#[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.
|
||||
@@ -158,10 +172,24 @@ impl Debug for KeyboardData {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "serialize",
|
||||
derive(serde_repr::Serialize_repr, serde_repr::Deserialize_repr)
|
||||
)]
|
||||
#[cfg(feature = "serialize")]
|
||||
impl<'de> serde::Deserialize<'de> for KeyCode {
|
||||
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)]
|
||||
#[repr(u8)]
|
||||
pub enum KeyCode {
|
||||
@@ -525,7 +553,6 @@ pub enum KeyCode {
|
||||
// kanji, = 244
|
||||
// unlock trackpad (Chrome/Edge), = 251
|
||||
// toggle touchpad, = 255
|
||||
#[cfg_attr(feature = "serialize", serde(other))]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
|
||||
@@ -10,15 +10,14 @@ homepage = "https://dioxuslabs.com"
|
||||
documentation = "https://docs.rs/dioxus"
|
||||
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
wasm-bindgen = { version = "0.2.79", optional = true }
|
||||
js-sys = { version = "0.3.56", optional = true }
|
||||
web-sys = { version = "0.3.56", optional = true, features = ["Element", "Node"] }
|
||||
sledgehammer_bindgen = { version = "0.1.3", optional = true }
|
||||
sledgehammer_utils = { version = "0.1.0", optional = true }
|
||||
sledgehammer_bindgen = { version = "0.2.1", optional = true }
|
||||
sledgehammer_utils = { version = "0.1.1", optional = true }
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
|
||||
[features]
|
||||
|
||||
@@ -108,6 +108,7 @@ mod js {
|
||||
const listeners = new ListenerMap();
|
||||
let nodes = [];
|
||||
let stack = [];
|
||||
let root;
|
||||
const templates = {};
|
||||
let node, els, end, ptr_end, k;
|
||||
export function save_template(nodes, tmpl_id) {
|
||||
|
||||
@@ -325,7 +325,7 @@ pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream {
|
||||
|
||||
#(#items)*
|
||||
|
||||
fn workload_system(type_id: std::any::TypeId, dependants: dioxus_native_core::exports::FxHashSet<std::any::TypeId>, pass_direction: dioxus_native_core::prelude::PassDirection) -> dioxus_native_core::exports::shipyard::WorkloadSystem {
|
||||
fn workload_system(type_id: std::any::TypeId, dependants: std::sync::Arc<dioxus_native_core::prelude::Dependants>, pass_direction: dioxus_native_core::prelude::PassDirection) -> dioxus_native_core::exports::shipyard::WorkloadSystem {
|
||||
use dioxus_native_core::exports::shipyard::{IntoWorkloadSystem, Get, AddComponent};
|
||||
use dioxus_native_core::tree::TreeRef;
|
||||
use dioxus_native_core::prelude::{NodeType, NodeView};
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
use dioxus_native_core::exports::shipyard::Component;
|
||||
use dioxus_native_core::node_ref::*;
|
||||
use dioxus_native_core::prelude::*;
|
||||
use dioxus_native_core::real_dom::NodeTypeMut;
|
||||
use dioxus_native_core_macro::partial_derive_state;
|
||||
|
||||
// All states need to derive Component
|
||||
#[derive(Default, Debug, Copy, Clone, Component)]
|
||||
struct Size(f64, f64);
|
||||
|
||||
/// Derive some of the boilerplate for the State implementation
|
||||
#[partial_derive_state]
|
||||
impl State for Size {
|
||||
type ParentDependencies = ();
|
||||
|
||||
// The size of the current node depends on the size of its children
|
||||
type ChildDependencies = (Self,);
|
||||
|
||||
type NodeDependencies = (FontSize,);
|
||||
|
||||
// Size only cares about the width, height, and text parts of the current node
|
||||
const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new()
|
||||
// Get access to the width and height attributes
|
||||
.with_attrs(AttributeMaskBuilder::Some(&["width", "height"]))
|
||||
// Get access to the text of the node
|
||||
.with_text();
|
||||
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
node_view: NodeView<()>,
|
||||
(font_size,): <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
|
||||
_parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_: &SendAnyMap,
|
||||
) -> bool {
|
||||
let font_size = font_size.size;
|
||||
let mut width;
|
||||
let mut height;
|
||||
if let Some(text) = node_view.text() {
|
||||
// if the node has text, use the text to size our object
|
||||
width = text.len() as f64 * font_size;
|
||||
height = font_size;
|
||||
} else {
|
||||
// otherwise, the size is the maximum size of the children
|
||||
width = children
|
||||
.iter()
|
||||
.map(|(item,)| item.0)
|
||||
.reduce(|accum, item| if accum >= item { accum } else { item })
|
||||
.unwrap_or(0.0);
|
||||
|
||||
height = children
|
||||
.iter()
|
||||
.map(|(item,)| item.1)
|
||||
.reduce(|accum, item| if accum >= item { accum } else { item })
|
||||
.unwrap_or(0.0);
|
||||
}
|
||||
// if the node contains a width or height attribute it overrides the other size
|
||||
for a in node_view.attributes().into_iter().flatten() {
|
||||
match &*a.attribute.name {
|
||||
"width" => width = a.value.as_float().unwrap(),
|
||||
"height" => height = a.value.as_float().unwrap(),
|
||||
// because Size only depends on the width and height, no other attributes will be passed to the member
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
// to determine what other parts of the dom need to be updated we return a boolean that marks if this member changed
|
||||
let changed = (width != self.0) || (height != self.1);
|
||||
*self = Self(width, height);
|
||||
changed
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Component)]
|
||||
struct FontSize {
|
||||
size: f64,
|
||||
}
|
||||
|
||||
impl Default for FontSize {
|
||||
fn default() -> Self {
|
||||
Self { size: 16.0 }
|
||||
}
|
||||
}
|
||||
|
||||
#[partial_derive_state]
|
||||
impl State for FontSize {
|
||||
// TextColor depends on the TextColor part of the parent
|
||||
type ParentDependencies = (Self,);
|
||||
|
||||
type ChildDependencies = ();
|
||||
|
||||
type NodeDependencies = ();
|
||||
|
||||
// TextColor only cares about the color attribute of the current node
|
||||
const NODE_MASK: NodeMaskBuilder<'static> =
|
||||
// Get access to the color attribute
|
||||
NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["font-size"]));
|
||||
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
node_view: NodeView<()>,
|
||||
_node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
|
||||
parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_context: &SendAnyMap,
|
||||
) -> bool {
|
||||
let mut new = None;
|
||||
for attr in node_view.attributes().into_iter().flatten() {
|
||||
if attr.attribute.name == "font-size" {
|
||||
new = Some(FontSize {
|
||||
size: attr.value.as_float().unwrap(),
|
||||
});
|
||||
}
|
||||
}
|
||||
let new = new.unwrap_or(parent.map(|(p,)| *p).unwrap_or_default());
|
||||
// check if the member has changed
|
||||
let changed = new != *self;
|
||||
*self = new;
|
||||
changed
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut rdom: RealDom = RealDom::new([FontSize::to_type_erased(), Size::to_type_erased()]);
|
||||
|
||||
let mut count = 0;
|
||||
|
||||
// intial render
|
||||
let text_id = rdom.create_node(format!("Count: {count}")).id();
|
||||
let mut root = rdom.get_mut(rdom.root_id()).unwrap();
|
||||
// set the color to red
|
||||
if let NodeTypeMut::Element(mut element) = root.node_type_mut() {
|
||||
element.set_attribute(("color", "style"), "red".to_string());
|
||||
element.set_attribute(("font-size", "style"), 1.);
|
||||
}
|
||||
root.add_child(text_id);
|
||||
|
||||
let ctx = SendAnyMap::new();
|
||||
// update the State for nodes in the real_dom tree
|
||||
let _to_rerender = rdom.update_state(ctx);
|
||||
|
||||
// we need to run the vdom in a async runtime
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()?
|
||||
.block_on(async {
|
||||
loop {
|
||||
// update the count and font size
|
||||
count += 1;
|
||||
let mut text = rdom.get_mut(text_id).unwrap();
|
||||
if let NodeTypeMut::Text(mut text) = text.node_type_mut() {
|
||||
*text = format!("Count: {count}");
|
||||
}
|
||||
if let NodeTypeMut::Element(mut element) =
|
||||
rdom.get_mut(rdom.root_id()).unwrap().node_type_mut()
|
||||
{
|
||||
element.set_attribute(("font-size", "style"), count as f64);
|
||||
}
|
||||
|
||||
let ctx = SendAnyMap::new();
|
||||
let _to_rerender = rdom.update_state(ctx);
|
||||
|
||||
// render...
|
||||
rdom.traverse_depth_first(|node| {
|
||||
let indent = " ".repeat(node.height() as usize);
|
||||
let font_size = *node.get::<FontSize>().unwrap();
|
||||
let size = *node.get::<Size>().unwrap();
|
||||
let id = node.id();
|
||||
println!("{indent}{id:?} {font_size:?} {size:?}");
|
||||
});
|
||||
|
||||
// wait 1 second
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -35,7 +35,7 @@ pub mod prelude {
|
||||
pub use crate::node::{ElementNode, FromAnyValue, NodeType, OwnedAttributeView, TextNode};
|
||||
pub use crate::node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView};
|
||||
pub use crate::passes::{run_pass, PassDirection, RunPassView, TypeErasedState};
|
||||
pub use crate::passes::{Dependancy, DependancyView, State};
|
||||
pub use crate::passes::{Dependancy, DependancyView, Dependants, State};
|
||||
pub use crate::real_dom::{NodeImmutable, NodeMut, NodeRef, RealDom};
|
||||
pub use crate::NodeId;
|
||||
pub use crate::SendAnyMap;
|
||||
|
||||
@@ -126,7 +126,7 @@ pub trait State<V: FromAnyValue + Send + Sync = ()>: Any + Send + Sync {
|
||||
/// Create a workload system for this state
|
||||
fn workload_system(
|
||||
type_id: TypeId,
|
||||
dependants: FxHashSet<TypeId>,
|
||||
dependants: Arc<Dependants>,
|
||||
pass_direction: PassDirection,
|
||||
) -> WorkloadSystem;
|
||||
|
||||
@@ -138,10 +138,16 @@ pub trait State<V: FromAnyValue + Send + Sync = ()>: Any + Send + Sync {
|
||||
let node_mask = Self::NODE_MASK.build();
|
||||
TypeErasedState {
|
||||
this_type_id: TypeId::of::<Self>(),
|
||||
combined_dependancy_type_ids: all_dependanices::<V, Self>().iter().copied().collect(),
|
||||
parent_dependant: !Self::ParentDependencies::type_ids().is_empty(),
|
||||
child_dependant: !Self::ChildDependencies::type_ids().is_empty(),
|
||||
dependants: FxHashSet::default(),
|
||||
parent_dependancies_ids: Self::ParentDependencies::type_ids()
|
||||
.iter()
|
||||
.copied()
|
||||
.collect(),
|
||||
child_dependancies_ids: Self::ChildDependencies::type_ids()
|
||||
.iter()
|
||||
.copied()
|
||||
.collect(),
|
||||
node_dependancies_ids: Self::NodeDependencies::type_ids().iter().copied().collect(),
|
||||
dependants: Default::default(),
|
||||
mask: node_mask,
|
||||
pass_direction: pass_direction::<V, Self>(),
|
||||
workload: Self::workload_system,
|
||||
@@ -166,13 +172,6 @@ fn pass_direction<V: FromAnyValue + Send + Sync, S: State<V>>() -> PassDirection
|
||||
}
|
||||
}
|
||||
|
||||
fn all_dependanices<V: FromAnyValue + Send + Sync, S: State<V>>() -> Box<[TypeId]> {
|
||||
let mut dependencies = S::ParentDependencies::type_ids().to_vec();
|
||||
dependencies.extend(S::ChildDependencies::type_ids().iter());
|
||||
dependencies.extend(S::NodeDependencies::type_ids().iter());
|
||||
dependencies.into_boxed_slice()
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Borrow, BorrowInfo)]
|
||||
pub struct RunPassView<'a, V: FromAnyValue + Send + Sync = ()> {
|
||||
@@ -188,7 +187,7 @@ pub struct RunPassView<'a, V: FromAnyValue + Send + Sync = ()> {
|
||||
#[doc(hidden)]
|
||||
pub fn run_pass<V: FromAnyValue + Send + Sync>(
|
||||
type_id: TypeId,
|
||||
dependants: FxHashSet<TypeId>,
|
||||
dependants: Arc<Dependants>,
|
||||
pass_direction: PassDirection,
|
||||
view: RunPassView<V>,
|
||||
mut update_node: impl FnMut(NodeId, &SendAnyMap) -> bool,
|
||||
@@ -206,11 +205,7 @@ pub fn run_pass<V: FromAnyValue + Send + Sync>(
|
||||
while let Some((height, id)) = dirty.pop_front(type_id) {
|
||||
if (update_node)(id, ctx) {
|
||||
nodes_updated.insert(id);
|
||||
for id in tree.children_ids(id) {
|
||||
for dependant in &dependants {
|
||||
dirty.insert(*dependant, id, height + 1);
|
||||
}
|
||||
}
|
||||
dependants.mark_dirty(&dirty, id, &tree, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -218,11 +213,7 @@ pub fn run_pass<V: FromAnyValue + Send + Sync>(
|
||||
while let Some((height, id)) = dirty.pop_back(type_id) {
|
||||
if (update_node)(id, ctx) {
|
||||
nodes_updated.insert(id);
|
||||
if let Some(id) = tree.parent_id(id) {
|
||||
for dependant in &dependants {
|
||||
dirty.insert(*dependant, id, height - 1);
|
||||
}
|
||||
}
|
||||
dependants.mark_dirty(&dirty, id, &tree, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -230,24 +221,53 @@ pub fn run_pass<V: FromAnyValue + Send + Sync>(
|
||||
while let Some((height, id)) = dirty.pop_back(type_id) {
|
||||
if (update_node)(id, ctx) {
|
||||
nodes_updated.insert(id);
|
||||
for dependant in &dependants {
|
||||
dirty.insert(*dependant, id, height);
|
||||
}
|
||||
dependants.mark_dirty(&dirty, id, &tree, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The states that depend on this state
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Dependants {
|
||||
/// The states in the parent direction that should be invalidated when this state is invalidated
|
||||
pub parent: Vec<TypeId>,
|
||||
/// The states in the child direction that should be invalidated when this state is invalidated
|
||||
pub child: Vec<TypeId>,
|
||||
/// The states in the node direction that should be invalidated when this state is invalidated
|
||||
pub node: Vec<TypeId>,
|
||||
}
|
||||
|
||||
impl Dependants {
|
||||
fn mark_dirty(&self, dirty: &DirtyNodeStates, id: NodeId, tree: &impl TreeRef, height: u16) {
|
||||
for dependant in &self.child {
|
||||
for id in tree.children_ids(id) {
|
||||
dirty.insert(*dependant, id, height + 1);
|
||||
}
|
||||
}
|
||||
|
||||
for dependant in &self.parent {
|
||||
if let Some(id) = tree.parent_id(id) {
|
||||
dirty.insert(*dependant, id, height - 1);
|
||||
}
|
||||
}
|
||||
|
||||
for dependant in &self.node {
|
||||
dirty.insert(*dependant, id, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A type erased version of [`State`] that can be added to the [`crate::prelude::RealDom`] with [`crate::prelude::RealDom::new`]
|
||||
pub struct TypeErasedState<V: FromAnyValue + Send = ()> {
|
||||
pub(crate) this_type_id: TypeId,
|
||||
pub(crate) parent_dependant: bool,
|
||||
pub(crate) child_dependant: bool,
|
||||
pub(crate) combined_dependancy_type_ids: FxHashSet<TypeId>,
|
||||
pub(crate) dependants: FxHashSet<TypeId>,
|
||||
pub(crate) parent_dependancies_ids: FxHashSet<TypeId>,
|
||||
pub(crate) child_dependancies_ids: FxHashSet<TypeId>,
|
||||
pub(crate) node_dependancies_ids: FxHashSet<TypeId>,
|
||||
pub(crate) dependants: Arc<Dependants>,
|
||||
pub(crate) mask: NodeMask,
|
||||
pub(crate) workload: fn(TypeId, FxHashSet<TypeId>, PassDirection) -> WorkloadSystem,
|
||||
pub(crate) workload: fn(TypeId, Arc<Dependants>, PassDirection) -> WorkloadSystem,
|
||||
pub(crate) pass_direction: PassDirection,
|
||||
phantom: PhantomData<V>,
|
||||
}
|
||||
@@ -260,10 +280,18 @@ impl<V: FromAnyValue + Send> TypeErasedState<V> {
|
||||
self.pass_direction,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn combined_dependancy_type_ids(&self) -> impl Iterator<Item = TypeId> + '_ {
|
||||
self.parent_dependancies_ids
|
||||
.iter()
|
||||
.chain(self.child_dependancies_ids.iter())
|
||||
.chain(self.node_dependancies_ids.iter())
|
||||
.copied()
|
||||
}
|
||||
}
|
||||
|
||||
/// The direction that a pass should be run in
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub enum PassDirection {
|
||||
/// The pass should be run from the root to the leaves
|
||||
ParentToChild,
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::node::{
|
||||
};
|
||||
use crate::node_ref::{NodeMask, NodeMaskBuilder};
|
||||
use crate::node_watcher::NodeWatcher;
|
||||
use crate::passes::{DirtyNodeStates, TypeErasedState};
|
||||
use crate::passes::{DirtyNodeStates, PassDirection, TypeErasedState};
|
||||
use crate::prelude::AttributeMaskBuilder;
|
||||
use crate::tree::{TreeMut, TreeMutView, TreeRef, TreeRefView};
|
||||
use crate::NodeId;
|
||||
@@ -68,12 +68,13 @@ impl<V: FromAnyValue + Send + Sync> NodesDirty<V> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark a node as added or removed from the tree
|
||||
/// Mark a node that has had a parent changed
|
||||
fn mark_parent_added_or_removed(&mut self, node_id: NodeId) {
|
||||
let hm = self.passes_updated.entry(node_id).or_default();
|
||||
for pass in &*self.passes {
|
||||
if pass.parent_dependant {
|
||||
hm.insert(pass.this_type_id);
|
||||
// If any of the states in this node depend on the parent then mark them as dirty
|
||||
for &pass in &pass.parent_dependancies_ids {
|
||||
hm.insert(pass);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,8 +83,9 @@ impl<V: FromAnyValue + Send + Sync> NodesDirty<V> {
|
||||
fn mark_child_changed(&mut self, node_id: NodeId) {
|
||||
let hm = self.passes_updated.entry(node_id).or_default();
|
||||
for pass in &*self.passes {
|
||||
if pass.child_dependant {
|
||||
hm.insert(pass.this_type_id);
|
||||
// If any of the states in this node depend on the children then mark them as dirty
|
||||
for &pass in &pass.child_dependancies_ids {
|
||||
hm.insert(pass);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -116,16 +118,46 @@ impl<V: FromAnyValue + Send + Sync> RealDom<V> {
|
||||
pub fn new(tracked_states: impl Into<Box<[TypeErasedState<V>]>>) -> RealDom<V> {
|
||||
let mut tracked_states = tracked_states.into();
|
||||
// resolve dependants for each pass
|
||||
for i in 1..tracked_states.len() {
|
||||
for i in 1..=tracked_states.len() {
|
||||
let (before, after) = tracked_states.split_at_mut(i);
|
||||
let (current, before) = before.split_last_mut().unwrap();
|
||||
for pass in before.iter_mut().chain(after.iter_mut()) {
|
||||
for state in before.iter_mut().chain(after.iter_mut()) {
|
||||
let dependants = Arc::get_mut(&mut state.dependants).unwrap();
|
||||
// If this node depends on the other state as a parent, then the other state should update its children of the current type when it is invalidated
|
||||
if current
|
||||
.combined_dependancy_type_ids
|
||||
.contains(&pass.this_type_id)
|
||||
.parent_dependancies_ids
|
||||
.contains(&state.this_type_id)
|
||||
&& !dependants.child.contains(¤t.this_type_id)
|
||||
{
|
||||
pass.dependants.insert(current.this_type_id);
|
||||
dependants.child.push(current.this_type_id);
|
||||
}
|
||||
// If this node depends on the other state as a child, then the other state should update its parent of the current type when it is invalidated
|
||||
if current.child_dependancies_ids.contains(&state.this_type_id)
|
||||
&& !dependants.parent.contains(¤t.this_type_id)
|
||||
{
|
||||
dependants.parent.push(current.this_type_id);
|
||||
}
|
||||
// If this node depends on the other state as a sibling, then the other state should update its siblings of the current type when it is invalidated
|
||||
if current.node_dependancies_ids.contains(&state.this_type_id)
|
||||
&& !dependants.node.contains(¤t.this_type_id)
|
||||
{
|
||||
dependants.node.push(current.this_type_id);
|
||||
}
|
||||
}
|
||||
// If the current state depends on itself, then it should update itself when it is invalidated
|
||||
let dependants = Arc::get_mut(&mut current.dependants).unwrap();
|
||||
match current.pass_direction {
|
||||
PassDirection::ChildToParent => {
|
||||
if !dependants.parent.contains(¤t.this_type_id) {
|
||||
dependants.parent.push(current.this_type_id);
|
||||
}
|
||||
}
|
||||
PassDirection::ParentToChild => {
|
||||
if !dependants.child.contains(¤t.this_type_id) {
|
||||
dependants.child.push(current.this_type_id);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let workload = construct_workload(&mut tracked_states);
|
||||
@@ -1011,7 +1043,8 @@ fn construct_workload<V: FromAnyValue + Send + Sync>(
|
||||
// mark any dependancies
|
||||
for i in 0..unresloved_workloads.len() {
|
||||
let (_, pass, _) = &unresloved_workloads[i];
|
||||
for ty_id in pass.combined_dependancy_type_ids.clone() {
|
||||
let all_dependancies: Vec<_> = pass.combined_dependancy_type_ids().collect();
|
||||
for ty_id in all_dependancies {
|
||||
let &(dependancy_id, _, _) = unresloved_workloads
|
||||
.iter()
|
||||
.find(|(_, pass, _)| pass.this_type_id == ty_id)
|
||||
|
||||
@@ -23,6 +23,8 @@ pub enum Segment {
|
||||
// This will be true if there are static styles
|
||||
inside_style_tag: bool,
|
||||
},
|
||||
/// A marker for where to insert a dynamic inner html
|
||||
InnerHtmlMarker,
|
||||
}
|
||||
|
||||
impl std::fmt::Write for StringChain {
|
||||
@@ -69,6 +71,9 @@ impl StringCache {
|
||||
write!(chain, "<{tag}")?;
|
||||
// we need to collect the styles and write them at the end
|
||||
let mut styles = Vec::new();
|
||||
// we need to collect the inner html and write it at the end
|
||||
let mut inner_html = None;
|
||||
// we need to keep track of if we have dynamic attrs to know if we need to insert a style and inner_html marker
|
||||
let mut has_dynamic_attrs = false;
|
||||
for attr in *attrs {
|
||||
match attr {
|
||||
@@ -77,7 +82,9 @@ impl StringCache {
|
||||
value,
|
||||
namespace,
|
||||
} => {
|
||||
if let Some("style") = namespace {
|
||||
if *name == "dangerous_inner_html" {
|
||||
inner_html = Some(value);
|
||||
} else if let Some("style") = namespace {
|
||||
styles.push((name, value));
|
||||
} else {
|
||||
write!(chain, " {name}=\"{value}\"")?;
|
||||
@@ -110,6 +117,13 @@ impl StringCache {
|
||||
write!(chain, "/>")?;
|
||||
} else {
|
||||
write!(chain, ">")?;
|
||||
// Write the static inner html, or insert a marker if dynamic inner html is possible
|
||||
if let Some(inner_html) = inner_html {
|
||||
chain.write_str(inner_html)?;
|
||||
} else if has_dynamic_attrs {
|
||||
chain.segments.push(Segment::InnerHtmlMarker);
|
||||
}
|
||||
|
||||
for child in *children {
|
||||
Self::recurse(child, cur_path, root_idx, chain)?;
|
||||
}
|
||||
|
||||
@@ -70,6 +70,8 @@ impl Renderer {
|
||||
.or_insert_with(|| Rc::new(StringCache::from_template(template).unwrap()))
|
||||
.clone();
|
||||
|
||||
let mut inner_html = None;
|
||||
|
||||
// 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();
|
||||
|
||||
@@ -77,7 +79,9 @@ impl Renderer {
|
||||
match segment {
|
||||
Segment::Attr(idx) => {
|
||||
let attr = &template.dynamic_attrs[*idx];
|
||||
if attr.namespace == Some("style") {
|
||||
if attr.name == "dangerous_inner_html" {
|
||||
inner_html = Some(attr);
|
||||
} else if attr.namespace == Some("style") {
|
||||
accumulated_dynamic_styles.push(attr);
|
||||
} else {
|
||||
match attr.value {
|
||||
@@ -85,6 +89,10 @@ impl Renderer {
|
||||
write!(buf, " {}=\"{}\"", attr.name, value)?
|
||||
}
|
||||
AttributeValue::Bool(value) => write!(buf, " {}={}", attr.name, value)?,
|
||||
AttributeValue::Int(value) => write!(buf, " {}={}", attr.name, value)?,
|
||||
AttributeValue::Float(value) => {
|
||||
write!(buf, " {}={}", attr.name, value)?
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
@@ -165,6 +173,19 @@ impl Renderer {
|
||||
accumulated_dynamic_styles.clear();
|
||||
}
|
||||
}
|
||||
|
||||
Segment::InnerHtmlMarker => {
|
||||
if let Some(inner_html) = inner_html.take() {
|
||||
let inner_html = &inner_html.value;
|
||||
match inner_html {
|
||||
AttributeValue::Text(value) => write!(buf, "{}", value)?,
|
||||
AttributeValue::Bool(value) => write!(buf, "{}", value)?,
|
||||
AttributeValue::Float(f) => write!(buf, "{}", f)?,
|
||||
AttributeValue::Int(i) => write!(buf, "{}", i)?,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,7 +229,9 @@ fn to_string_works() {
|
||||
StyleMarker {
|
||||
inside_style_tag: false,
|
||||
},
|
||||
PreRendered(">Hello world 1 -->".into(),),
|
||||
PreRendered(">".into()),
|
||||
InnerHtmlMarker,
|
||||
PreRendered("Hello world 1 -->".into(),),
|
||||
Node(0,),
|
||||
PreRendered(
|
||||
"<-- Hello world 2<div>nest 1</div><div></div><div>nest 2</div>".into(),
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn static_inner_html() {
|
||||
fn app(cx: Scope) -> Element {
|
||||
render! { div { dangerous_inner_html: "<div>1234</div>" } }
|
||||
}
|
||||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
_ = dom.rebuild();
|
||||
|
||||
assert_eq!(dioxus_ssr::render(&dom), r#"<div><div>1234</div></div>"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dynamic_inner_html() {
|
||||
fn app(cx: Scope) -> Element {
|
||||
let inner_html = "<div>1234</div>";
|
||||
render! { div { dangerous_inner_html: "{inner_html}" } }
|
||||
}
|
||||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
_ = dom.rebuild();
|
||||
|
||||
assert_eq!(dioxus_ssr::render(&dom), r#"<div><div>1234</div></div>"#);
|
||||
}
|
||||
@@ -49,15 +49,16 @@ impl Config {
|
||||
|
||||
/// Set the name of the element that Dioxus will use as the root.
|
||||
///
|
||||
/// This is akint to calling React.render() on the element with the specified name.
|
||||
/// This is akin to calling React.render() on the element with the specified name.
|
||||
pub fn rootname(mut self, name: impl Into<String>) -> Self {
|
||||
self.rootname = name.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the name of the element that Dioxus will use as the root.
|
||||
/// Sets a string cache for wasm bindgen to [intern](https://docs.rs/wasm-bindgen/0.2.84/wasm_bindgen/fn.intern.html). This can help reduce the time it takes for wasm bindgen to pass
|
||||
/// strings from rust to javascript. This can significantly improve pefromance when passing strings to javascript, but can have a negative impact on startup time.
|
||||
///
|
||||
/// This is akint to calling React.render() on the element with the specified name.
|
||||
/// > Currently this cache is only used when creating static elements and attributes.
|
||||
pub fn with_string_cache(mut self, cache: Vec<String>) -> Self {
|
||||
self.cached_strings = cache;
|
||||
self
|
||||
|
||||
@@ -128,10 +128,11 @@ impl WebsysDom {
|
||||
mounted_id = Some(id);
|
||||
let name = attribute.name;
|
||||
if let AttributeValue::Listener(_) = value {
|
||||
let event_name = &name[2..];
|
||||
self.interpreter.new_event_listener(
|
||||
&name[2..],
|
||||
event_name,
|
||||
id.0 as u32,
|
||||
event_bubbles(name) as u8,
|
||||
event_bubbles(event_name) as u8,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user