mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 14:44:12 +00:00
Merge branch 'upstream' into fix-ssr-raw-attributes
This commit is contained in:
commit
6afd764aa4
109 changed files with 6361 additions and 4894 deletions
|
@ -15,7 +15,8 @@ members = [
|
|||
"packages/liveview",
|
||||
"packages/autofmt",
|
||||
"packages/rsx",
|
||||
"packages/tui",
|
||||
"packages/dioxus-tui",
|
||||
"packages/rink",
|
||||
"packages/native-core",
|
||||
"packages/native-core-macro",
|
||||
"packages/rsx-rosetta",
|
||||
|
|
|
@ -11,10 +11,13 @@ dioxus = { path = "../../packages/dioxus" }
|
|||
dioxus-desktop = { path = "../../packages/desktop" }
|
||||
dioxus-web = { path = "../../packages/web" }
|
||||
dioxus-ssr = { path = "../../packages/ssr" }
|
||||
dioxus-native-core = { path = "../../packages/native-core" }
|
||||
dioxus-native-core-macro = { path = "../../packages/native-core-macro" }
|
||||
dioxus-router = { path = "../../packages/router" }
|
||||
dioxus-liveview = { path = "../../packages/liveview", features = ["axum"] }
|
||||
dioxus-tui = { path = "../../packages/tui" }
|
||||
dioxus-tui = { path = "../../packages/dioxus-tui" }
|
||||
fermi = { path = "../../packages/fermi" }
|
||||
shipyard = "0.6.2"
|
||||
|
||||
|
||||
# dioxus = { path = "../../packages/dioxus", features = ["desktop", "web", "ssr", "router", "fermi", "tui"] }
|
||||
|
|
311
docs/guide/examples/custom_renderer.rs
Normal file
311
docs/guide/examples/custom_renderer.rs
Normal file
|
@ -0,0 +1,311 @@
|
|||
use dioxus::html::input_data::keyboard_types::{Code, Key, Modifiers};
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_native_core::exports::shipyard::Component;
|
||||
use dioxus_native_core::node_ref::*;
|
||||
use dioxus_native_core::prelude::*;
|
||||
use dioxus_native_core::utils::cursor::{Cursor, Pos};
|
||||
use dioxus_native_core_macro::partial_derive_state;
|
||||
|
||||
// ANCHOR: state_impl
|
||||
struct FontSize(f64);
|
||||
|
||||
// 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 = ();
|
||||
|
||||
// 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<()>,
|
||||
_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 font_size = context.get::<FontSize>().unwrap().0;
|
||||
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, Default, Component)]
|
||||
struct TextColor {
|
||||
r: u8,
|
||||
g: u8,
|
||||
b: u8,
|
||||
}
|
||||
|
||||
#[partial_derive_state]
|
||||
impl State for TextColor {
|
||||
// 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(&["color"]));
|
||||
|
||||
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 {
|
||||
// TextColor only depends on the color tag, so getting the first tag is equivilent to looking through all tags
|
||||
let new = match node_view
|
||||
.attributes()
|
||||
.and_then(|mut attrs| attrs.next())
|
||||
.and_then(|attr| attr.value.as_text())
|
||||
{
|
||||
// if there is a color tag, translate it
|
||||
Some("red") => TextColor { r: 255, g: 0, b: 0 },
|
||||
Some("green") => TextColor { r: 0, g: 255, b: 0 },
|
||||
Some("blue") => TextColor { r: 0, g: 0, b: 255 },
|
||||
Some(color) => panic!("unknown color {color}"),
|
||||
// otherwise check if the node has a parent and inherit that color
|
||||
None => match parent {
|
||||
Some((parent,)) => *parent,
|
||||
None => Self::default(),
|
||||
},
|
||||
};
|
||||
// check if the member has changed
|
||||
let changed = new != *self;
|
||||
*self = new;
|
||||
changed
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default, Component)]
|
||||
struct Border(bool);
|
||||
|
||||
#[partial_derive_state]
|
||||
impl State for Border {
|
||||
// TextColor depends on the TextColor part of the parent
|
||||
type ParentDependencies = (Self,);
|
||||
|
||||
type ChildDependencies = ();
|
||||
|
||||
type NodeDependencies = ();
|
||||
|
||||
// Border does not depended on any other member in the current node
|
||||
const NODE_MASK: NodeMaskBuilder<'static> =
|
||||
// Get access to the border attribute
|
||||
NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["border"]));
|
||||
|
||||
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 {
|
||||
// check if the node contians a border attribute
|
||||
let new = Self(
|
||||
node_view
|
||||
.attributes()
|
||||
.and_then(|mut attrs| attrs.next().map(|a| a.attribute.name == "border"))
|
||||
.is_some(),
|
||||
);
|
||||
// check if the member has changed
|
||||
let changed = new != *self;
|
||||
*self = new;
|
||||
changed
|
||||
}
|
||||
}
|
||||
// ANCHOR_END: state_impl
|
||||
|
||||
// ANCHOR: rendering
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
fn app(cx: Scope) -> Element {
|
||||
let count = use_state(cx, || 0);
|
||||
|
||||
use_future(cx, (count,), |(count,)| async move {
|
||||
loop {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
count.set(*count + 1);
|
||||
}
|
||||
});
|
||||
|
||||
cx.render(rsx! {
|
||||
div{
|
||||
color: "red",
|
||||
"{count}"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// create the vdom, the real_dom, and the binding layer between them
|
||||
let mut vdom = VirtualDom::new(app);
|
||||
let mut rdom: RealDom = RealDom::new([
|
||||
Border::to_type_erased(),
|
||||
TextColor::to_type_erased(),
|
||||
Size::to_type_erased(),
|
||||
]);
|
||||
let mut dioxus_intigration_state = DioxusState::create(&mut rdom);
|
||||
|
||||
let mutations = vdom.rebuild();
|
||||
// update the structure of the real_dom tree
|
||||
dioxus_intigration_state.apply_mutations(&mut rdom, mutations);
|
||||
let mut ctx = SendAnyMap::new();
|
||||
// set the font size to 3.3
|
||||
ctx.insert(FontSize(3.3));
|
||||
// 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 {
|
||||
// wait for the vdom to update
|
||||
vdom.wait_for_work().await;
|
||||
|
||||
// get the mutations from the vdom
|
||||
let mutations = vdom.render_immediate();
|
||||
|
||||
// update the structure of the real_dom tree
|
||||
dioxus_intigration_state.apply_mutations(&mut rdom, mutations);
|
||||
|
||||
// update the state of the real_dom tree
|
||||
let mut ctx = SendAnyMap::new();
|
||||
// set the font size to 3.3
|
||||
ctx.insert(FontSize(3.3));
|
||||
let _to_rerender = rdom.update_state(ctx);
|
||||
|
||||
// render...
|
||||
rdom.traverse_depth_first(|node| {
|
||||
let indent = " ".repeat(node.height() as usize);
|
||||
let color = *node.get::<TextColor>().unwrap();
|
||||
let size = *node.get::<Size>().unwrap();
|
||||
let border = *node.get::<Border>().unwrap();
|
||||
let id = node.id();
|
||||
let node = node.node_type();
|
||||
let node_type = &*node;
|
||||
println!("{indent}{id:?} {color:?} {size:?} {border:?} {node_type:?}");
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
// ANCHOR_END: rendering
|
||||
|
||||
// ANCHOR: derive_state
|
||||
// All states must derive Component (https://docs.rs/shipyard/latest/shipyard/derive.Component.html)
|
||||
// They also must implement Default or provide a custom implementation of create in the State trait
|
||||
#[derive(Default, Component)]
|
||||
struct MyState;
|
||||
|
||||
/// Derive some of the boilerplate for the State implementation
|
||||
#[partial_derive_state]
|
||||
impl State for MyState {
|
||||
// The states of the parent nodes this state depends on
|
||||
type ParentDependencies = ();
|
||||
|
||||
// The states of the child nodes this state depends on
|
||||
type ChildDependencies = (Self,);
|
||||
|
||||
// The states of the current node this state depends on
|
||||
type NodeDependencies = ();
|
||||
|
||||
// The parts of the current text, element, or placeholder node in the tree that this state depends on
|
||||
const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new();
|
||||
|
||||
// How to update the state of the current node based on the state of the parent nodes, child nodes, and the current node
|
||||
// Returns true if the node was updated and false if the node was not updated
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
// The view of the current node limited to the parts this state depends on
|
||||
_node_view: NodeView<()>,
|
||||
// The state of the current node that this state depends on
|
||||
_node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
|
||||
// The state of the parent nodes that this state depends on
|
||||
_parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
// The state of the child nodes that this state depends on
|
||||
_children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
// The context of the current node used to pass global state into the tree
|
||||
_context: &SendAnyMap,
|
||||
) -> bool {
|
||||
todo!()
|
||||
}
|
||||
|
||||
// partial_derive_state will generate a default implementation of all the other methods
|
||||
}
|
||||
// ANCHOR_END: derive_state
|
||||
|
||||
#[allow(unused)]
|
||||
// ANCHOR: cursor
|
||||
fn text_editing() {
|
||||
let mut cursor = Cursor::default();
|
||||
let mut text = String::new();
|
||||
|
||||
// handle keyboard input with a max text length of 10
|
||||
cursor.handle_input(
|
||||
&Code::ArrowRight,
|
||||
&Key::ArrowRight,
|
||||
&Modifiers::empty(),
|
||||
&mut text,
|
||||
10,
|
||||
);
|
||||
|
||||
// mannually select text between characters 0-5 on the first line (this could be from dragging with a mouse)
|
||||
cursor.start = Pos::new(0, 0);
|
||||
cursor.end = Some(Pos::new(5, 0));
|
||||
|
||||
// delete the selected text and move the cursor to the start of the selection
|
||||
cursor.delete_selection(&mut text);
|
||||
}
|
||||
// ANCHOR_END: cursor
|
|
@ -99,13 +99,14 @@ Template {
|
|||
attr_paths: &'a [&'a [u8]],
|
||||
}
|
||||
```
|
||||
|
||||
> For more detailed docs about the struture of templates see the [Template api docs](https://docs.rs/dioxus-core/latest/dioxus_core/prelude/struct.Template.html)
|
||||
|
||||
This template will be sent to the renderer in the [list of templates](https://docs.rs/dioxus-core/latest/dioxus_core/struct.Mutations.html#structfield.templates) supplied with the mutations the first time it is used. Any time the renderer encounters a [LoadTemplate](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.LoadTemplate) mutation after this, it should clone the template and store it in the given id.
|
||||
|
||||
For dynamic nodes and dynamic text nodes, a placeholder node should be created and inserted into the UI so that the node can be navigated to later.
|
||||
For dynamic nodes and dynamic text nodes, a placeholder node should be created and inserted into the UI so that the node can be modified later.
|
||||
|
||||
In HTML renderers, this template could look like:
|
||||
In HTML renderers, this template could look like this:
|
||||
|
||||
```html
|
||||
<h1>""</h1>
|
||||
|
@ -206,7 +207,9 @@ nodes: [
|
|||
"count: 0",
|
||||
]
|
||||
```
|
||||
|
||||
Over time, our stack looked like this:
|
||||
|
||||
```rust
|
||||
[Root]
|
||||
[Root, <h1>""</h1>]
|
||||
|
@ -218,15 +221,15 @@ Conveniently, this approach completely separates the Virtual DOM and the Real DO
|
|||
|
||||
Dioxus is also really fast. Because Dioxus splits the diff and patch phase, it's able to make all the edits to the RealDOM in a very short amount of time (less than a single frame) making rendering very snappy. It also allows Dioxus to cancel large diffing operations if higher priority work comes in while it's diffing.
|
||||
|
||||
This little demo serves to show exactly how a Renderer would need to process an edit stream to build UIs.
|
||||
This little demo serves to show exactly how a Renderer would need to process a mutation stream to build UIs.
|
||||
|
||||
## Event loop
|
||||
|
||||
Like most GUIs, Dioxus relies on an event loop to progress the VirtualDOM. The VirtualDOM itself can produce events as well, so it's important that your custom renderer can handle those too.
|
||||
Like most GUIs, Dioxus relies on an event loop to progress the VirtualDOM. The VirtualDOM itself can produce events as well, so it's important for your custom renderer can handle those too.
|
||||
|
||||
The code for the WebSys implementation is straightforward, so we'll add it here to demonstrate how simple an event loop is:
|
||||
|
||||
```rust
|
||||
```rust, ignore
|
||||
pub async fn run(&mut self) -> dioxus_core::error::Result<()> {
|
||||
// Push the body element onto the WebsysDom's stack machine
|
||||
let mut websys_dom = crate::new::WebsysDom::new(prepare_websys_dom());
|
||||
|
@ -254,9 +257,9 @@ pub async fn run(&mut self) -> dioxus_core::error::Result<()> {
|
|||
}
|
||||
```
|
||||
|
||||
It's important that you decode the real events from your event system into Dioxus' synthetic event system (synthetic meaning abstracted). This simply means matching your event type and creating a Dioxus `UserEvent` type. Right now, the VirtualEvent system is modeled almost entirely around the HTML spec, but we are interested in slimming it down.
|
||||
It's important to decode what the real events are for your event system into Dioxus' synthetic event system (synthetic meaning abstracted). This simply means matching your event type and creating a Dioxus `UserEvent` type. Right now, the virtual event system is modeled almost entirely around the HTML spec, but we are interested in slimming it down.
|
||||
|
||||
```rust
|
||||
```rust, ignore
|
||||
fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
|
||||
match event.type_().as_str() {
|
||||
"keydown" => {
|
||||
|
@ -294,16 +297,17 @@ For more examples and information on how to create custom namespaces, see the [`
|
|||
|
||||
# Native Core
|
||||
|
||||
If you are creating a renderer in rust, the [native-core](https://github.com/DioxusLabs/dioxus/tree/master/packages/native-core) crate provides some utilities to implement a renderer. It provides an abstraction over Mutations and Templates and contains helpers that can handle the layout, and text editing for you.
|
||||
If you are creating a renderer in rust, the [native-core](https://github.com/DioxusLabs/dioxus/tree/master/packages/native-core) crate provides some utilities to implement a renderer. It provides an abstraction over Mutations and Templates and contains helpers that can handle the layout and text editing for you.
|
||||
|
||||
## The RealDom
|
||||
|
||||
The `RealDom` is a higher-level abstraction over updating the Dom. It updates with `Mutations` and provides a way to incrementally update the state of nodes based on attributes or other states that change.
|
||||
The `RealDom` is a higher-level abstraction over updating the Dom. It uses an entity component system to manage the state of nodes. This system allows you to modify insert and modify arbitrary components on nodes. On top of this, the RealDom provides a way to manage a tree of nodes, and the State trait provides a way to automatically add and update these components when the tree is modified. It also provides a way to apply `Mutations` to the RealDom.
|
||||
|
||||
### Example
|
||||
|
||||
Let's build a toy renderer with borders, size, and text color.
|
||||
Before we start let's take a look at an example element we can render:
|
||||
|
||||
```rust
|
||||
cx.render(rsx!{
|
||||
div{
|
||||
|
@ -372,188 +376,24 @@ In the following diagram arrows represent dataflow:
|
|||
[//]: # " end"
|
||||
[//]: # " end"
|
||||
|
||||
To help in building a Dom, native-core provides four traits: State, ChildDepState, ParentDepState, NodeDepState, and a RealDom struct. The ChildDepState, ParentDepState, and NodeDepState provide a way to describe how some information in a node relates to that of its relatives. By providing how to build a single node from its relations, native-core will derive a way to update the state of all nodes for you with ```#[derive(State)]```. Once you have a state you can provide it as a generic to RealDom. RealDom provides all of the methods to interact and update your new dom.
|
||||
To help in building a Dom, native-core provides the State trait and a RealDom struct. The State trait provides a way to describe how states in a node depend on other states in its relatives. By describing how to update a single node from its relations, native-core will derive a way to update the states of all nodes for you. Once you have a state you can provide it as a generic to RealDom. RealDom provides all of the methods to interact and update your new dom.
|
||||
|
||||
```rust
|
||||
Native Core cannot create all of the required methods for the State trait, but it can derive some of them. To implement the State trait, you must implement the following methods and let the `#[partial_derive_state]` macro handle the rest:
|
||||
|
||||
use dioxus_native_core::node_ref::*;
|
||||
use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State};
|
||||
use dioxus_native_core_macro::{sorted_str_slice, State};
|
||||
|
||||
#[derive(Default, Copy, Clone)]
|
||||
struct Size(f64, f64);
|
||||
// Size only depends on the current node and its children, so it implements ChildDepState
|
||||
impl ChildDepState for Size {
|
||||
// Size accepts a font size context
|
||||
type Ctx = f64;
|
||||
// Size depends on the Size part of each child
|
||||
type DepState = (Self,);
|
||||
// Size only cares about the width, height, and text parts of the current node
|
||||
const NODE_MASK: NodeMask =
|
||||
NodeMask::new_with_attrs(AttributeMask::Static(&sorted_str_slice!([
|
||||
"width", "height"
|
||||
])))
|
||||
.with_text();
|
||||
fn reduce<'a>(
|
||||
&mut self,
|
||||
node: NodeView,
|
||||
children: impl Iterator<Item = (&'a Self,)>,
|
||||
ctx: &Self::Ctx,
|
||||
) -> bool
|
||||
where
|
||||
Self::DepState: 'a,
|
||||
{
|
||||
let mut width;
|
||||
let mut height;
|
||||
if let Some(text) = node.text() {
|
||||
// if the node has text, use the text to size our object
|
||||
width = text.len() as f64 * ctx;
|
||||
height = *ctx;
|
||||
} else {
|
||||
// otherwise, the size is the maximum size of the children
|
||||
width = children
|
||||
.by_ref()
|
||||
.map(|(item,)| item.0)
|
||||
.reduce(|accum, item| if accum >= item { accum } else { item })
|
||||
.unwrap_or(0.0);
|
||||
|
||||
height = children
|
||||
.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.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, Default)]
|
||||
struct TextColor {
|
||||
r: u8,
|
||||
g: u8,
|
||||
b: u8,
|
||||
}
|
||||
// TextColor only depends on the current node and its parent, so it implements ParentDepState
|
||||
impl ParentDepState for TextColor {
|
||||
type Ctx = ();
|
||||
// TextColor depends on the TextColor part of the parent
|
||||
type DepState = (Self,);
|
||||
// TextColor only cares about the color attribute of the current node
|
||||
const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::Static(&["color"]));
|
||||
fn reduce(&mut self, node: NodeView, parent: Option<(&Self,)>, _ctx: &Self::Ctx) -> bool {
|
||||
// TextColor only depends on the color tag, so getting the first tag is equivilent to looking through all tags
|
||||
let new = match node
|
||||
.attributes()
|
||||
.and_then(|attrs| attrs.next())
|
||||
.map(|attr| attr.attribute.name.as_str())
|
||||
{
|
||||
// if there is a color tag, translate it
|
||||
Some("red") => TextColor { r: 255, g: 0, b: 0 },
|
||||
Some("green") => TextColor { r: 0, g: 255, b: 0 },
|
||||
Some("blue") => TextColor { r: 0, g: 0, b: 255 },
|
||||
Some(_) => panic!("unknown color"),
|
||||
// otherwise check if the node has a parent and inherit that color
|
||||
None => match parent {
|
||||
Some((parent,)) => *parent,
|
||||
None => Self::default(),
|
||||
},
|
||||
};
|
||||
// check if the member has changed
|
||||
let changed = new != *self;
|
||||
*self = new;
|
||||
changed
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
struct Border(bool);
|
||||
// TextColor only depends on the current node, so it implements NodeDepState
|
||||
impl NodeDepState for Border {
|
||||
type Ctx = ();
|
||||
type DepState = ();
|
||||
|
||||
// Border does not depended on any other member in the current node
|
||||
const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::Static(&["border"]));
|
||||
fn reduce(&mut self, node: NodeView, _sibling: (), _ctx: &Self::Ctx) -> bool {
|
||||
// check if the node contians a border attribute
|
||||
let new = Self(
|
||||
node.attributes()
|
||||
.and_then(|attrs| attrs.next().map(|a| a.attribute.name == "border"))
|
||||
.is_some(),
|
||||
);
|
||||
// check if the member has changed
|
||||
let changed = new != *self;
|
||||
*self = new;
|
||||
changed
|
||||
}
|
||||
}
|
||||
|
||||
// State provides a derive macro, but anotations on the members are needed in the form #[dep_type(dep_member, CtxType)]
|
||||
#[derive(State, Default, Clone)]
|
||||
struct ToyState {
|
||||
// the color member of it's parent and no context
|
||||
#[parent_dep_state(color)]
|
||||
color: TextColor,
|
||||
// depends on the node, and no context
|
||||
#[node_dep_state()]
|
||||
border: Border,
|
||||
// depends on the layout_width member of children and f32 context (for text size)
|
||||
#[child_dep_state(size, f32)]
|
||||
size: Size,
|
||||
}
|
||||
```rust, ignore
|
||||
{{#include ../../../examples/custom_renderer.rs:derive_state}}
|
||||
```
|
||||
|
||||
Now that we have our state, we can put it to use in our dom. We can update the dom with update_state to update the structure of the dom (adding, removing, and changing properties of nodes) and then apply_mutations to update the ToyState for each of the nodes that changed.
|
||||
Lets take a look at how to implement the State trait for a simple renderer.
|
||||
|
||||
```rust
|
||||
fn main(){
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx!{
|
||||
div{
|
||||
color: "red",
|
||||
"hello world"
|
||||
}
|
||||
})
|
||||
}
|
||||
let vdom = VirtualDom::new(app);
|
||||
let rdom: RealDom<ToyState> = RealDom::new();
|
||||
{{#include ../../../examples/custom_renderer.rs:state_impl}}
|
||||
```
|
||||
|
||||
let mutations = dom.rebuild();
|
||||
// update the structure of the real_dom tree
|
||||
let to_update = rdom.apply_mutations(vec![mutations]);
|
||||
let mut ctx = AnyMap::new();
|
||||
// set the font size to 3.3
|
||||
ctx.insert(3.3f64);
|
||||
// update the ToyState for nodes in the real_dom tree
|
||||
let _to_rerender = rdom.update_state(&dom, to_update, ctx).unwrap();
|
||||
Now that we have our state, we can put it to use in our RealDom. We can update the RealDom with apply_mutations to update the structure of the dom (adding, removing, and changing properties of nodes) and then update_state to update the States for each of the nodes that changed.
|
||||
|
||||
// we need to run the vdom in a async runtime
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()?
|
||||
.block_on(async {
|
||||
loop{
|
||||
let wait = vdom.wait_for_work();
|
||||
let mutations = vdom.work_with_deadline(|| false);
|
||||
let to_update = rdom.apply_mutations(mutations);
|
||||
let mut ctx = AnyMap::new();
|
||||
ctx.insert(3.3f64);
|
||||
let _to_rerender = rdom.update_state(vdom, to_update, ctx).unwrap();
|
||||
|
||||
// render...
|
||||
}
|
||||
})
|
||||
}
|
||||
```rust
|
||||
{{#include ../../../examples/custom_renderer.rs:rendering}}
|
||||
```
|
||||
|
||||
## Layout
|
||||
|
@ -565,26 +405,9 @@ For most platforms, the layout of the Elements will stay the same. The [layout_a
|
|||
To make it easier to implement text editing in rust renderers, `native-core` also contains a renderer-agnostic cursor system. The cursor can handle text editing, selection, and movement with common keyboard shortcuts integrated.
|
||||
|
||||
```rust
|
||||
let mut cursor = Cursor::default();
|
||||
let mut text = String::new();
|
||||
|
||||
let keyboard_data = dioxus_html::KeyboardData::new(
|
||||
dioxus_html::input_data::keyboard_types::Key::ArrowRight,
|
||||
dioxus_html::input_data::keyboard_types::Code::ArrowRight,
|
||||
dioxus_html::input_data::keyboard_types::Location::Standard,
|
||||
false,
|
||||
Modifiers::empty(),
|
||||
);
|
||||
// handle keyboard input with a max text length of 10
|
||||
cursor.handle_input(&keyboard_data, &mut text, 10);
|
||||
|
||||
// mannually select text between characters 0-5 on the first line (this could be from dragging with a mouse)
|
||||
cursor.start = Pos::new(0, 0);
|
||||
cursor.end = Some(Pos::new(5, 0));
|
||||
|
||||
// delete the selected text and move the cursor to the start of the selection
|
||||
cursor.delete_selection(&mut text);
|
||||
{{#include ../../../examples/custom_renderer.rs:cursor}}
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
That should be it! You should have nearly all the knowledge required on how to implement your renderer. We're super interested in seeing Dioxus apps brought to custom desktop renderers, mobile renderers, video game UI, and even augmented reality! If you're interested in contributing to any of these projects, don't be afraid to reach out or join the [community](https://discord.gg/XgGxMSkvUM).
|
||||
|
|
36
examples/counter.rs
Normal file
36
examples/counter.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
//! Comparison example with leptos' counter example
|
||||
//! https://github.com/leptos-rs/leptos/blob/main/examples/counters/src/lib.rs
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let counters = use_state(cx, || vec![0, 0, 0]);
|
||||
let sum: usize = counters.iter().copied().sum();
|
||||
|
||||
render! {
|
||||
div {
|
||||
button { onclick: move |_| counters.make_mut().push(0), "Add counter" }
|
||||
button { onclick: move |_| { counters.make_mut().pop(); }, "Remove counter" }
|
||||
p { "Total: {sum}" }
|
||||
for (i, counter) in counters.iter().enumerate() {
|
||||
li {
|
||||
button { onclick: move |_| counters.make_mut()[i] -= 1, "-1" }
|
||||
input {
|
||||
value: "{counter}",
|
||||
oninput: move |e| {
|
||||
if let Ok(value) = e.value.parse::<usize>() {
|
||||
counters.make_mut()[i] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
button { onclick: move |_| counters.make_mut()[i] += 1, "+1" }
|
||||
button { onclick: move |_| { counters.make_mut().remove(i); }, "x" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ fn main() {
|
|||
dioxus_desktop::launch(app);
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
pub enum FilterState {
|
||||
All,
|
||||
Active,
|
||||
|
@ -39,56 +39,99 @@ pub fn app(cx: Scope<()>) -> Element {
|
|||
.collect::<Vec<_>>();
|
||||
filtered_todos.sort_unstable();
|
||||
|
||||
let show_clear_completed = todos.values().any(|todo| todo.checked);
|
||||
let items_left = filtered_todos.len();
|
||||
let item_text = match items_left {
|
||||
let active_todo_count = todos.values().filter(|item| !item.checked).count();
|
||||
let active_todo_text = match active_todo_count {
|
||||
1 => "item",
|
||||
_ => "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",
|
||||
style { include_str!("./assets/todomvc.css") }
|
||||
div {
|
||||
header { class: "header",
|
||||
h1 {"todos"}
|
||||
input {
|
||||
class: "new-todo",
|
||||
placeholder: "What needs to be done?",
|
||||
value: "{draft}",
|
||||
autofocus: "true",
|
||||
oninput: move |evt| {
|
||||
draft.set(evt.value.clone());
|
||||
},
|
||||
onkeydown: move |evt| {
|
||||
if evt.key() == Key::Enter && !draft.is_empty() {
|
||||
todos.make_mut().insert(
|
||||
**todo_id,
|
||||
TodoItem {
|
||||
id: **todo_id,
|
||||
checked: false,
|
||||
contents: draft.to_string(),
|
||||
},
|
||||
);
|
||||
*todo_id.make_mut() += 1;
|
||||
draft.set("".to_string());
|
||||
}
|
||||
header { class: "header",
|
||||
h1 {"todos"}
|
||||
input {
|
||||
class: "new-todo",
|
||||
placeholder: "What needs to be done?",
|
||||
value: "{draft}",
|
||||
autofocus: "true",
|
||||
oninput: move |evt| {
|
||||
draft.set(evt.value.clone());
|
||||
},
|
||||
onkeydown: move |evt| {
|
||||
if evt.key() == Key::Enter && !draft.is_empty() {
|
||||
todos.make_mut().insert(
|
||||
**todo_id,
|
||||
TodoItem {
|
||||
id: **todo_id,
|
||||
checked: false,
|
||||
contents: draft.to_string(),
|
||||
},
|
||||
);
|
||||
*todo_id.make_mut() += 1;
|
||||
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",
|
||||
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!(
|
||||
footer { class: "footer",
|
||||
span { class: "todo-count",
|
||||
strong {"{items_left} "}
|
||||
span {"{item_text} left"}
|
||||
strong {"{active_todo_count} "}
|
||||
span {"{active_todo_text} left"}
|
||||
}
|
||||
ul { class: "filters",
|
||||
li { class: "All", a { onclick: move |_| filter.set(FilterState::All), "All" }}
|
||||
li { class: "Active", a { onclick: move |_| filter.set(FilterState::Active), "Active" }}
|
||||
li { class: "Completed", a { onclick: move |_| filter.set(FilterState::Completed), "Completed" }}
|
||||
for (state, state_text, url) in [
|
||||
(FilterState::All, "All", "#/"),
|
||||
(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!(
|
||||
button {
|
||||
|
@ -102,8 +145,8 @@ pub fn app(cx: Scope<()>) -> Element {
|
|||
}
|
||||
}
|
||||
footer { class: "info",
|
||||
p {"Double-click to edit a todo"}
|
||||
p { "Created by ", a { href: "http://github.com/jkelleyrtp/", "jkelleyrtp" }}
|
||||
p { "Double-click to edit a todo" }
|
||||
p { "Created by ", a { href: "http://github.com/jkelleyrtp/", "jkelleyrtp" }}
|
||||
p { "Part of ", a { href: "http://todomvc.com", "TodoMVC" }}
|
||||
}
|
||||
})
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
r#for: "cbg-{todo.id}",
|
||||
onclick: move |_| is_editing.set(true),
|
||||
ondblclick: move |_| is_editing.set(true),
|
||||
prevent_default: "onclick",
|
||||
"{todo.contents}"
|
||||
}
|
||||
button {
|
||||
class: "destroy",
|
||||
onclick: move |_| { cx.props.todos.make_mut().remove(&todo.id); },
|
||||
prevent_default: "onclick",
|
||||
}
|
||||
}
|
||||
is_editing.then(|| rsx!{
|
||||
input {
|
||||
|
|
|
@ -2,7 +2,13 @@
|
|||
name = "dioxus-autofmt"
|
||||
version = "0.3.0"
|
||||
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
|
||||
|
||||
[dependencies]
|
||||
|
@ -11,7 +17,7 @@ proc-macro2 = { version = "1.0.6", features = ["span-locations"] }
|
|||
quote = "1.0"
|
||||
syn = { version = "1.0.11", features = ["full", "extra-traits"] }
|
||||
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",
|
||||
] }
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
@ -628,10 +635,12 @@ impl<'b> VirtualDom {
|
|||
}
|
||||
|
||||
let id = self.find_last_element(&new[last]);
|
||||
self.mutations.push(Mutation::InsertAfter {
|
||||
id,
|
||||
m: nodes_created,
|
||||
});
|
||||
if nodes_created > 0 {
|
||||
self.mutations.push(Mutation::InsertAfter {
|
||||
id,
|
||||
m: nodes_created,
|
||||
})
|
||||
}
|
||||
nodes_created = 0;
|
||||
}
|
||||
|
||||
|
@ -652,10 +661,12 @@ impl<'b> VirtualDom {
|
|||
}
|
||||
|
||||
let id = self.find_first_element(&new[last]);
|
||||
self.mutations.push(Mutation::InsertBefore {
|
||||
id,
|
||||
m: nodes_created,
|
||||
});
|
||||
if nodes_created > 0 {
|
||||
self.mutations.push(Mutation::InsertBefore {
|
||||
id,
|
||||
m: nodes_created,
|
||||
});
|
||||
}
|
||||
|
||||
nodes_created = 0;
|
||||
}
|
||||
|
@ -676,10 +687,12 @@ impl<'b> VirtualDom {
|
|||
}
|
||||
|
||||
let id = self.find_first_element(&new[first_lis]);
|
||||
self.mutations.push(Mutation::InsertBefore {
|
||||
id,
|
||||
m: nodes_created,
|
||||
});
|
||||
if nodes_created > 0 {
|
||||
self.mutations.push(Mutation::InsertBefore {
|
||||
id,
|
||||
m: nodes_created,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ pub enum Mutation<'a> {
|
|||
/// The ID of the element being mounted to
|
||||
id: ElementId,
|
||||
|
||||
/// The number of nodes on the stack
|
||||
/// The number of nodes on the stack to append to the target element
|
||||
m: usize,
|
||||
},
|
||||
|
||||
|
@ -155,7 +155,7 @@ pub enum Mutation<'a> {
|
|||
/// The ID of the node we're going to replace with
|
||||
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,
|
||||
},
|
||||
|
||||
|
@ -167,7 +167,7 @@ pub enum Mutation<'a> {
|
|||
/// `[0,1,2]` represents 1st child's 2nd child's 3rd child.
|
||||
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,
|
||||
},
|
||||
|
||||
|
@ -176,7 +176,7 @@ pub enum Mutation<'a> {
|
|||
/// The ID of the node to insert after.
|
||||
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,
|
||||
},
|
||||
|
||||
|
@ -185,7 +185,7 @@ pub enum Mutation<'a> {
|
|||
/// The ID of the node to insert before.
|
||||
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,
|
||||
},
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use std::borrow::Cow;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use wry::application::window::Icon;
|
||||
|
@ -18,6 +19,7 @@ pub struct Config {
|
|||
pub(crate) pre_rendered: Option<String>,
|
||||
pub(crate) disable_context_menu: bool,
|
||||
pub(crate) resource_dir: Option<PathBuf>,
|
||||
pub(crate) data_dir: Option<PathBuf>,
|
||||
pub(crate) custom_head: Option<String>,
|
||||
pub(crate) custom_index: Option<String>,
|
||||
pub(crate) root_name: String,
|
||||
|
@ -27,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 {
|
||||
|
@ -44,6 +46,7 @@ impl Config {
|
|||
pre_rendered: None,
|
||||
disable_context_menu: !cfg!(debug_assertions),
|
||||
resource_dir: None,
|
||||
data_dir: None,
|
||||
custom_head: None,
|
||||
custom_index: None,
|
||||
root_name: "main".to_string(),
|
||||
|
@ -56,6 +59,14 @@ impl Config {
|
|||
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.
|
||||
pub fn with_disable_context_menu(mut self, disable: bool) -> Self {
|
||||
self.disable_context_menu = disable;
|
||||
|
@ -88,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
|
||||
|
|
|
@ -36,8 +36,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.
|
||||
///
|
||||
|
@ -281,7 +281,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(),
|
||||
|
@ -299,6 +299,7 @@ fn create_new_window(
|
|||
webview,
|
||||
dom,
|
||||
waker: waker::tao_waker(proxy, id),
|
||||
web_context,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -306,6 +307,7 @@ struct WebviewHandler {
|
|||
dom: VirtualDom,
|
||||
webview: Rc<wry::webview::WebView>,
|
||||
waker: Waker,
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,13 +7,13 @@ use tao::event_loop::{EventLoopProxy, EventLoopWindowTarget};
|
|||
pub use wry;
|
||||
pub use wry::application as tao;
|
||||
use wry::application::window::Window;
|
||||
use wry::webview::{WebView, WebViewBuilder};
|
||||
use wry::webview::{WebContext, WebView, WebViewBuilder};
|
||||
|
||||
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();
|
||||
|
@ -33,6 +33,8 @@ pub fn build(
|
|||
));
|
||||
}
|
||||
|
||||
let mut web_context = WebContext::new(cfg.data_dir.clone());
|
||||
|
||||
let mut webview = WebViewBuilder::new(window)
|
||||
.unwrap()
|
||||
.with_transparent(cfg.window.window.transparent)
|
||||
|
@ -52,7 +54,8 @@ pub fn build(
|
|||
.as_ref()
|
||||
.map(|handler| handler(window, evet))
|
||||
.unwrap_or_default()
|
||||
});
|
||||
})
|
||||
.with_web_context(&mut web_context);
|
||||
|
||||
for (name, handler) in cfg.protocols.drain(..) {
|
||||
webview = webview.with_custom_protocol(name, handler)
|
||||
|
@ -78,5 +81,5 @@ pub fn build(
|
|||
webview = webview.with_devtools(true);
|
||||
}
|
||||
|
||||
Rc::new(webview.build().unwrap())
|
||||
(Rc::new(webview.build().unwrap()), web_context)
|
||||
}
|
||||
|
|
|
@ -16,20 +16,15 @@ license = "MIT/Apache-2.0"
|
|||
dioxus = { path = "../dioxus", version = "^0.3.0" }
|
||||
dioxus-core = { path = "../core", version = "^0.3.0", features = ["serialize"] }
|
||||
dioxus-html = { path = "../html", version = "^0.3.0" }
|
||||
dioxus-native-core = { path = "../native-core", version = "^0.3.0" }
|
||||
dioxus-native-core = { path = "../native-core", version = "^0.2.0", features = ["dioxus"] }
|
||||
dioxus-native-core-macro = { path = "../native-core-macro", version = "^0.3.0" }
|
||||
dioxus-hot-reload = { path = "../hot-reload", optional = true }
|
||||
rink = { path = "../rink" }
|
||||
|
||||
tui = "0.17.0"
|
||||
crossterm = "0.23.0"
|
||||
anyhow = "1.0.42"
|
||||
tokio = { version = "1.15.0", features = ["full"] }
|
||||
futures = "0.3.19"
|
||||
taffy = "0.2.1"
|
||||
smallvec = "1.6"
|
||||
rustc-hash = "1.1.0"
|
||||
anymap = "1.0.0-beta.2"
|
||||
futures-channel = "0.3.25"
|
||||
|
||||
[dev-dependencies]
|
||||
dioxus = { path = "../dioxus" }
|
|
@ -1,5 +1,5 @@
|
|||
<div align="center">
|
||||
<h1>Rink</h1>
|
||||
<h1>Dioxus TUI</h1>
|
||||
<p>
|
||||
<strong>Beautiful terminal user interfaces in Rust with <a href="https://dioxuslabs.com/">Dioxus </a>.</strong>
|
||||
</p>
|
||||
|
@ -37,7 +37,6 @@
|
|||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
<br/>
|
||||
|
||||
Leverage React-like patterns, CSS, HTML, and Rust to build beautiful, portable, terminal user interfaces with Dioxus.
|
||||
|
@ -63,33 +62,34 @@ fn app(cx: Scope) -> Element {
|
|||
|
||||
You can use Html-like semantics with inline styles, tree hierarchy, components, and more in your [`text-based user interface (TUI)`](https://en.wikipedia.org/wiki/Text-based_user_interface) application.
|
||||
|
||||
Rink is essentially a port of [Ink](https://github.com/vadimdemedes/ink) but for [`Rust`](https://www.rust-lang.org/) and [`Dioxus`](https://dioxuslabs.com/). Rink doesn't depend on Node.js or any other JavaScript runtime, so your binaries are portable and beautiful.
|
||||
Dioxus TUI is essentially a port of [Ink](https://github.com/vadimdemedes/ink) but for [`Rust`](https://www.rust-lang.org/) and [`Dioxus`](https://dioxuslabs.com/). Dioxus TUI doesn't depend on Node.js or any other JavaScript runtime, so your binaries are portable and beautiful.
|
||||
|
||||
## Limitations
|
||||
|
||||
- **Subset of Html**
|
||||
Terminals can only render a subset of HTML. We support as much as we can.
|
||||
Terminals can only render a subset of HTML. We support as much as we can.
|
||||
- **Particular frontend design**
|
||||
Terminals and browsers are and look different. Therefore, the same design might not be the best to cover both renderers.
|
||||
|
||||
Terminals and browsers are and look different. Therefore, the same design might not be the best to cover both renderers.
|
||||
|
||||
## Status
|
||||
|
||||
**WARNING: Rink is currently under construction!**
|
||||
**WARNING: Dioxus TUI is currently under construction!**
|
||||
|
||||
Rendering a VirtualDom works fine, but the ecosystem of hooks is not yet ready. Additionally, some bugs in the flexbox implementation might be quirky at times.
|
||||
|
||||
## Features
|
||||
|
||||
Rink features:
|
||||
Dioxus TUI features:
|
||||
|
||||
- [x] Flexbox-based layout system
|
||||
- [ ] CSS selectors
|
||||
- [x] inline CSS support
|
||||
- [x] Built-in focusing system
|
||||
|
||||
* [x] Widgets<sup>1</sup>
|
||||
* [ ] Support for events, hooks, and callbacks<sup>2</sup>
|
||||
* [ ] Html tags<sup>3</sup>
|
||||
|
||||
<sup>1</sup> Currently only a subset of the input element is implemented as a component (not an element). The `Input` component supports sliders, text, numbers, passwords, buttons, and checkboxes.
|
||||
<sup>2</sup> Basic keyboard, mouse, and focus events are implemented.
|
||||
<sup>3</sup> Currently, most HTML tags don't translate into any meaning inside of Dioxus TUI. So an `input` *element* won't mean anything nor does it have any additional functionality.
|
||||
<sup>3</sup> Currently, most HTML tags don't translate into any meaning inside of Dioxus TUI. So an `input` _element_ won't mean anything nor does it have any additional functionality.
|
165
packages/dioxus-tui/benches/update.rs
Normal file
165
packages/dioxus-tui/benches/update.rs
Normal file
|
@ -0,0 +1,165 @@
|
|||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_tui::{Config, TuiContext};
|
||||
|
||||
criterion_group!(mbenches, tui_update);
|
||||
criterion_main!(mbenches);
|
||||
|
||||
/// This benchmarks the cache performance of the TUI for small edits by changing one box at a time.
|
||||
fn tui_update(c: &mut Criterion) {
|
||||
{
|
||||
let mut group = c.benchmark_group("Update boxes");
|
||||
|
||||
for size in 1..=20usize {
|
||||
let parameter_string = format!("{}", (size).pow(2));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("size", parameter_string),
|
||||
&size,
|
||||
|b, size| {
|
||||
b.iter(|| {
|
||||
dioxus_tui::launch_cfg_with_props(
|
||||
app,
|
||||
GridProps {
|
||||
size: *size,
|
||||
update_count: 1,
|
||||
},
|
||||
Config::default().with_headless(),
|
||||
)
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mut group = c.benchmark_group("Update many boxes");
|
||||
|
||||
for update_count in 1..=20usize {
|
||||
let update_count = update_count * 20;
|
||||
let parameter_string = update_count.to_string();
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("update count", parameter_string),
|
||||
&update_count,
|
||||
|b, update_count| {
|
||||
b.iter(|| {
|
||||
dioxus_tui::launch_cfg_with_props(
|
||||
app,
|
||||
GridProps {
|
||||
size: 20,
|
||||
update_count: *update_count,
|
||||
},
|
||||
Config::default().with_headless(),
|
||||
)
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Props, PartialEq)]
|
||||
struct BoxProps {
|
||||
x: usize,
|
||||
y: usize,
|
||||
hue: f32,
|
||||
alpha: f32,
|
||||
}
|
||||
#[allow(non_snake_case)]
|
||||
fn Box(cx: Scope<BoxProps>) -> Element {
|
||||
let count = use_state(cx, || 0);
|
||||
|
||||
let x = cx.props.x * 2;
|
||||
let y = cx.props.y * 2;
|
||||
let hue = cx.props.hue;
|
||||
let display_hue = cx.props.hue as u32 / 10;
|
||||
let count = count.get();
|
||||
let alpha = cx.props.alpha + (count % 100) as f32;
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
left: "{x}%",
|
||||
top: "{y}%",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
background_color: "hsl({hue}, 100%, 50%, {alpha}%)",
|
||||
align_items: "center",
|
||||
p{"{display_hue:03}"}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Props, PartialEq)]
|
||||
struct GridProps {
|
||||
size: usize,
|
||||
update_count: usize,
|
||||
}
|
||||
#[allow(non_snake_case)]
|
||||
fn Grid(cx: Scope<GridProps>) -> Element {
|
||||
let size = cx.props.size;
|
||||
let count = use_state(cx, || 0);
|
||||
let counts = use_ref(cx, || vec![0; size * size]);
|
||||
|
||||
let ctx: TuiContext = cx.consume_context().unwrap();
|
||||
if *count.get() + cx.props.update_count >= (size * size) {
|
||||
ctx.quit();
|
||||
} else {
|
||||
for _ in 0..cx.props.update_count {
|
||||
counts.with_mut(|c| {
|
||||
let i = *count.current();
|
||||
c[i] += 1;
|
||||
c[i] %= 360;
|
||||
});
|
||||
count.with_mut(|i| {
|
||||
*i += 1;
|
||||
*i %= size * size;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render! {
|
||||
div{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
flex_direction: "column",
|
||||
(0..size).map(|x|
|
||||
{
|
||||
rsx! {
|
||||
div{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
flex_direction: "row",
|
||||
(0..size).map(|y|
|
||||
{
|
||||
let alpha = y as f32*100.0/size as f32 + counts.read()[x*size + y] as f32;
|
||||
let key = format!("{}-{}", x, y);
|
||||
rsx! {
|
||||
Box{
|
||||
x: x,
|
||||
y: y,
|
||||
alpha: 100.0,
|
||||
hue: alpha,
|
||||
key: "{key}",
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn app(cx: Scope<GridProps>) -> Element {
|
||||
cx.render(rsx! {
|
||||
div{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
Grid{
|
||||
size: cx.props.size,
|
||||
update_count: cx.props.update_count,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
use dioxus::core::RenderReturn;
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_tui::query::Query;
|
||||
use dioxus_tui::DioxusElementToNodeId;
|
||||
use dioxus_tui::Query;
|
||||
use dioxus_tui::Size;
|
||||
|
||||
fn main() {
|
||||
|
@ -11,6 +12,7 @@ fn app(cx: Scope) -> Element {
|
|||
let hue = use_state(cx, || 0.0);
|
||||
let brightness = use_state(cx, || 0.0);
|
||||
let tui_query: Query = cx.consume_context().unwrap();
|
||||
let mapping: DioxusElementToNodeId = cx.consume_context().unwrap();
|
||||
// disable templates so that every node has an id and can be queried
|
||||
cx.render(rsx! {
|
||||
div{
|
||||
|
@ -19,7 +21,7 @@ fn app(cx: Scope) -> Element {
|
|||
onmousemove: move |evt| {
|
||||
if let RenderReturn::Ready(node) = cx.root_node() {
|
||||
if let Some(id) = node.root_ids.get(0){
|
||||
let node = tui_query.get(id);
|
||||
let node = tui_query.get(mapping.get_node_id(id).unwrap());
|
||||
let Size{width, height} = node.size().unwrap();
|
||||
let pos = evt.inner().element_coordinates();
|
||||
hue.set((pos.x as f32/width as f32)*255.0);
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
|
@ -1,26 +1,12 @@
|
|||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_tui::{Config, TuiContext};
|
||||
|
||||
criterion_group!(mbenches, tui_update);
|
||||
criterion_main!(mbenches);
|
||||
|
||||
/// This benchmarks the cache performance of the TUI for small edits by changing one box at a time.
|
||||
fn tui_update(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("Update boxes");
|
||||
|
||||
// We can also use loops to define multiple benchmarks, even over multiple dimensions.
|
||||
for size in 1..=8usize {
|
||||
let parameter_string = format!("{}", (3 * size).pow(2));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("size", parameter_string),
|
||||
&size,
|
||||
|b, size| {
|
||||
b.iter(|| {
|
||||
dioxus_tui::launch_cfg_with_props(app, *size, Config::default().with_headless())
|
||||
})
|
||||
},
|
||||
);
|
||||
fn main() {
|
||||
for size in 1..=20usize {
|
||||
for _ in 0..10 {
|
||||
dioxus_tui::launch_cfg_with_props(app, size, Config::default().with_headless())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
use dioxus::prelude::*;
|
||||
use dioxus_html::FormData;
|
||||
use dioxus_tui::prelude::*;
|
||||
use dioxus_tui::Config;
|
||||
|
||||
fn main() {
|
||||
dioxus_tui::launch_cfg(app, Config::new());
|
143
packages/dioxus-tui/src/lib.rs
Normal file
143
packages/dioxus-tui/src/lib.rs
Normal file
|
@ -0,0 +1,143 @@
|
|||
pub mod prelude;
|
||||
pub mod widgets;
|
||||
|
||||
use std::{
|
||||
ops::Deref,
|
||||
rc::Rc,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
use dioxus_core::{Component, ElementId, VirtualDom};
|
||||
use dioxus_html::EventData;
|
||||
use dioxus_native_core::dioxus::{DioxusState, NodeImmutableDioxusExt};
|
||||
use dioxus_native_core::prelude::*;
|
||||
|
||||
pub use rink::{query::Query, Config, RenderingMode, Size, TuiContext};
|
||||
use rink::{render, Driver};
|
||||
|
||||
pub fn launch(app: Component<()>) {
|
||||
launch_cfg(app, Config::default())
|
||||
}
|
||||
|
||||
pub fn launch_cfg(app: Component<()>, cfg: Config) {
|
||||
launch_cfg_with_props(app, (), cfg);
|
||||
}
|
||||
|
||||
pub fn launch_cfg_with_props<Props: 'static>(app: Component<Props>, props: Props, cfg: Config) {
|
||||
render(cfg, |rdom, taffy, event_tx| {
|
||||
let dioxus_state = {
|
||||
let mut rdom = rdom.write().unwrap();
|
||||
DioxusState::create(&mut rdom)
|
||||
};
|
||||
let dioxus_state = Rc::new(RwLock::new(dioxus_state));
|
||||
let mut vdom = VirtualDom::new_with_props(app, props)
|
||||
.with_root_context(TuiContext::new(event_tx))
|
||||
.with_root_context(Query::new(rdom.clone(), taffy.clone()))
|
||||
.with_root_context(DioxusElementToNodeId {
|
||||
mapping: dioxus_state.clone(),
|
||||
});
|
||||
let muts = vdom.rebuild();
|
||||
let mut rdom = rdom.write().unwrap();
|
||||
dioxus_state
|
||||
.write()
|
||||
.unwrap()
|
||||
.apply_mutations(&mut rdom, muts);
|
||||
DioxusRenderer {
|
||||
vdom,
|
||||
dioxus_state,
|
||||
#[cfg(all(feature = "hot-reload", debug_assertions))]
|
||||
hot_reload_rx: {
|
||||
let (hot_reload_tx, hot_reload_rx) =
|
||||
tokio::sync::mpsc::unbounded_channel::<dioxus_hot_reload::HotReloadMsg>();
|
||||
dioxus_hot_reload::connect(move |msg| {
|
||||
let _ = hot_reload_tx.send(msg);
|
||||
});
|
||||
hot_reload_rx
|
||||
},
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
struct DioxusRenderer {
|
||||
vdom: VirtualDom,
|
||||
dioxus_state: Rc<RwLock<DioxusState>>,
|
||||
#[cfg(all(feature = "hot-reload", debug_assertions))]
|
||||
hot_reload_rx: tokio::sync::mpsc::UnboundedReceiver<dioxus_hot_reload::HotReloadMsg>,
|
||||
}
|
||||
|
||||
impl Driver for DioxusRenderer {
|
||||
fn update(&mut self, rdom: &Arc<RwLock<RealDom>>) {
|
||||
let muts = self.vdom.render_immediate();
|
||||
{
|
||||
let mut rdom = rdom.write().unwrap();
|
||||
self.dioxus_state
|
||||
.write()
|
||||
.unwrap()
|
||||
.apply_mutations(&mut rdom, muts);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_event(
|
||||
&mut self,
|
||||
rdom: &Arc<RwLock<RealDom>>,
|
||||
id: NodeId,
|
||||
event: &str,
|
||||
value: Rc<EventData>,
|
||||
bubbles: bool,
|
||||
) {
|
||||
let id = { rdom.read().unwrap().get(id).unwrap().mounted_id() };
|
||||
if let Some(id) = id {
|
||||
self.vdom
|
||||
.handle_event(event, value.deref().clone().into_any(), id, bubbles);
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_async(&mut self) -> std::pin::Pin<Box<dyn futures::Future<Output = ()> + '_>> {
|
||||
#[cfg(all(feature = "hot-reload", debug_assertions))]
|
||||
return Box::pin(async {
|
||||
let hot_reload_wait = self.hot_reload_rx.recv();
|
||||
let mut hot_reload_msg = None;
|
||||
let wait_for_work = self.vdom.wait_for_work();
|
||||
tokio::select! {
|
||||
Some(msg) = hot_reload_wait => {
|
||||
#[cfg(all(feature = "hot-reload", debug_assertions))]
|
||||
{
|
||||
hot_reload_msg = Some(msg);
|
||||
}
|
||||
#[cfg(not(all(feature = "hot-reload", debug_assertions)))]
|
||||
let () = msg;
|
||||
}
|
||||
_ = wait_for_work => {}
|
||||
}
|
||||
// if we have a new template, replace the old one
|
||||
if let Some(msg) = hot_reload_msg {
|
||||
match msg {
|
||||
dioxus_hot_reload::HotReloadMsg::UpdateTemplate(template) => {
|
||||
self.vdom.replace_template(template);
|
||||
}
|
||||
dioxus_hot_reload::HotReloadMsg::Shutdown => {
|
||||
std::process::exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
#[cfg(not(all(feature = "hot-reload", debug_assertions)))]
|
||||
Box::pin(self.vdom.wait_for_work())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DioxusElementToNodeId {
|
||||
mapping: Rc<RwLock<DioxusState>>,
|
||||
}
|
||||
|
||||
impl DioxusElementToNodeId {
|
||||
pub fn get_node_id(&self, element_id: ElementId) -> Option<NodeId> {
|
||||
self.mapping
|
||||
.read()
|
||||
.unwrap()
|
||||
.try_element_to_node_id(element_id)
|
||||
}
|
||||
}
|
|
@ -1 +1,2 @@
|
|||
pub use crate::widgets::*;
|
||||
pub use rink::Config;
|
|
@ -37,7 +37,7 @@ pub(crate) fn Button<'a>(cx: Scope<'a, ButtonProps>) -> Element<'a> {
|
|||
}
|
||||
state.set(new_state);
|
||||
};
|
||||
cx.render(rsx! {
|
||||
render! {
|
||||
div{
|
||||
width: "{width}",
|
||||
height: "{height}",
|
||||
|
@ -55,5 +55,5 @@ pub(crate) fn Button<'a>(cx: Scope<'a, ButtonProps>) -> Element<'a> {
|
|||
},
|
||||
"{text}"
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -61,7 +61,7 @@ pub(crate) fn CheckBox<'a>(cx: Scope<'a, CheckBoxProps>) -> Element<'a> {
|
|||
}
|
||||
state.set(new_state);
|
||||
};
|
||||
cx.render(rsx! {
|
||||
render! {
|
||||
div {
|
||||
width: "{width}",
|
||||
height: "{height}",
|
||||
|
@ -78,5 +78,5 @@ pub(crate) fn CheckBox<'a>(cx: Scope<'a, CheckBoxProps>) -> Element<'a> {
|
|||
},
|
||||
"{text}"
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
22
packages/dioxus-tui/src/widgets/mod.rs
Normal file
22
packages/dioxus-tui/src/widgets/mod.rs
Normal file
|
@ -0,0 +1,22 @@
|
|||
mod button;
|
||||
mod checkbox;
|
||||
mod input;
|
||||
mod number;
|
||||
mod password;
|
||||
mod slider;
|
||||
mod textbox;
|
||||
|
||||
use dioxus_core::{RenderReturn, Scope};
|
||||
use dioxus_native_core::NodeId;
|
||||
pub use input::*;
|
||||
|
||||
use crate::DioxusElementToNodeId;
|
||||
|
||||
pub(crate) fn get_root_id<T>(cx: Scope<T>) -> Option<NodeId> {
|
||||
if let RenderReturn::Ready(sync) = cx.root_node() {
|
||||
let mapping: DioxusElementToNodeId = cx.consume_context()?;
|
||||
mapping.get_node_id(sync.root_ids.get(0)?)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
use crate::widgets::get_root_id;
|
||||
use crate::Query;
|
||||
use crossterm::{cursor::MoveTo, execute};
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_elements::input_data::keyboard_types::Key;
|
||||
use dioxus_html as dioxus_elements;
|
||||
use dioxus_html::FormData;
|
||||
use dioxus_native_core::utils::cursor::{Cursor, Pos};
|
||||
use rink::Query;
|
||||
use std::{collections::HashMap, io::stdout};
|
||||
use taffy::geometry::Point;
|
||||
|
||||
|
@ -99,7 +99,7 @@ pub(crate) fn NumbericInput<'a>(cx: Scope<'a, NumbericInputProps>) -> Element<'a
|
|||
update(text.clone());
|
||||
};
|
||||
|
||||
cx.render(rsx! {
|
||||
render! {
|
||||
div{
|
||||
width: "{width}",
|
||||
height: "{height}",
|
||||
|
@ -113,7 +113,7 @@ pub(crate) fn NumbericInput<'a>(cx: Scope<'a, NumbericInputProps>) -> Element<'a
|
|||
};
|
||||
if is_text{
|
||||
let mut text = text_ref.write();
|
||||
cursor.write().handle_input(&k, &mut *text, max_len);
|
||||
cursor.write().handle_input(&k.code(), &k.key(), &k.modifiers(), &mut *text, max_len);
|
||||
update(text.clone());
|
||||
|
||||
let node = tui_query.get(get_root_id(cx).unwrap());
|
||||
|
@ -205,5 +205,5 @@ pub(crate) fn NumbericInput<'a>(cx: Scope<'a, NumbericInputProps>) -> Element<'a
|
|||
|
||||
"{text_after_second_cursor}"
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
use crate::widgets::get_root_id;
|
||||
use crate::Query;
|
||||
use crossterm::{cursor::*, execute};
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_elements::input_data::keyboard_types::Key;
|
||||
use dioxus_html as dioxus_elements;
|
||||
use dioxus_html::FormData;
|
||||
use dioxus_native_core::utils::cursor::{Cursor, Pos};
|
||||
use rink::Query;
|
||||
use std::{collections::HashMap, io::stdout};
|
||||
use taffy::geometry::Point;
|
||||
|
||||
|
@ -88,7 +88,9 @@ pub(crate) fn Password<'a>(cx: Scope<'a, PasswordProps>) -> Element<'a> {
|
|||
return;
|
||||
}
|
||||
let mut text = text_ref.write();
|
||||
cursor.write().handle_input(&k, &mut *text, max_len);
|
||||
cursor
|
||||
.write()
|
||||
.handle_input(&k.code(), &k.key(), &k.modifiers(), &mut *text, max_len);
|
||||
if let Some(input_handler) = &cx.props.raw_oninput {
|
||||
input_handler.call(FormData {
|
||||
value: text.clone(),
|
||||
|
@ -114,7 +116,7 @@ pub(crate) fn Password<'a>(cx: Scope<'a, PasswordProps>) -> Element<'a> {
|
|||
}
|
||||
};
|
||||
|
||||
cx.render(rsx! {
|
||||
render! {
|
||||
div {
|
||||
width: "{width}",
|
||||
height: "{height}",
|
||||
|
@ -187,5 +189,5 @@ pub(crate) fn Password<'a>(cx: Scope<'a, PasswordProps>) -> Element<'a> {
|
|||
|
||||
"{text_after_second_cursor}"
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::widgets::get_root_id;
|
||||
use crate::Query;
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_elements::input_data::keyboard_types::Key;
|
||||
use dioxus_html as dioxus_elements;
|
||||
use dioxus_html::FormData;
|
||||
use rink::Query;
|
||||
|
||||
#[derive(Props)]
|
||||
pub(crate) struct SliderProps<'a> {
|
||||
|
@ -62,7 +62,7 @@ pub(crate) fn Slider<'a>(cx: Scope<'a, SliderProps>) -> Element<'a> {
|
|||
}
|
||||
};
|
||||
|
||||
cx.render(rsx! {
|
||||
render! {
|
||||
div{
|
||||
width: "{width}",
|
||||
height: "{height}",
|
||||
|
@ -103,5 +103,5 @@ pub(crate) fn Slider<'a>(cx: Scope<'a, SliderProps>) -> Element<'a> {
|
|||
background_color: "rgba(10,10,10,0.5)",
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
use crate::widgets::get_root_id;
|
||||
use crate::Query;
|
||||
use crossterm::{cursor::*, execute};
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_elements::input_data::keyboard_types::Key;
|
||||
use dioxus_html as dioxus_elements;
|
||||
use dioxus_html::FormData;
|
||||
use dioxus_native_core::utils::cursor::{Cursor, Pos};
|
||||
use rink::Query;
|
||||
use std::{collections::HashMap, io::stdout};
|
||||
use taffy::geometry::Point;
|
||||
|
||||
|
@ -79,7 +79,7 @@ pub(crate) fn TextBox<'a>(cx: Scope<'a, TextBoxProps>) -> Element<'a> {
|
|||
"solid"
|
||||
};
|
||||
|
||||
cx.render(rsx! {
|
||||
render! {
|
||||
div{
|
||||
width: "{width}",
|
||||
height: "{height}",
|
||||
|
@ -90,7 +90,7 @@ pub(crate) fn TextBox<'a>(cx: Scope<'a, TextBoxProps>) -> Element<'a> {
|
|||
return;
|
||||
}
|
||||
let mut text = text_ref.write();
|
||||
cursor.write().handle_input(&k, &mut *text, max_len);
|
||||
cursor.write().handle_input(&k.code(), &k.key(), &k.modifiers(), &mut *text, max_len);
|
||||
if let Some(input_handler) = &cx.props.raw_oninput{
|
||||
input_handler.call(FormData{
|
||||
value: text.clone(),
|
||||
|
@ -178,5 +178,5 @@ pub(crate) fn TextBox<'a>(cx: Scope<'a, TextBoxProps>) -> Element<'a> {
|
|||
|
||||
"{text_after_second_cursor}"
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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!
|
||||
|
||||
## Installation
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -1,563 +1,386 @@
|
|||
extern crate proc_macro;
|
||||
|
||||
mod sorted_slice;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::{quote, ToTokens, __private::Span};
|
||||
use sorted_slice::StrSlice;
|
||||
use syn::parenthesized;
|
||||
use syn::parse::ParseBuffer;
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::{
|
||||
self,
|
||||
parse::{Parse, ParseStream, Result},
|
||||
parse_macro_input, parse_quote, Error, Field, Ident, Token, Type,
|
||||
};
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{parse_macro_input, ItemImpl, Type, TypePath, TypeTuple};
|
||||
|
||||
/// Sorts a slice of string literals at compile time.
|
||||
#[proc_macro]
|
||||
pub fn sorted_str_slice(input: TokenStream) -> TokenStream {
|
||||
let slice: StrSlice = parse_macro_input!(input as StrSlice);
|
||||
let strings = slice.map.values();
|
||||
quote!([#(#strings, )*]).into()
|
||||
}
|
||||
/// A helper attribute for deriving `State` for a struct.
|
||||
#[proc_macro_attribute]
|
||||
pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let impl_block: syn::ItemImpl = parse_macro_input!(input as syn::ItemImpl);
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
enum DependencyKind {
|
||||
Node,
|
||||
Child,
|
||||
Parent,
|
||||
}
|
||||
|
||||
/// Derive's the state from any elements that have a node_dep_state, child_dep_state, parent_dep_state, or state attribute.
|
||||
///
|
||||
/// # Declaring elements
|
||||
/// Each of the attributes require specifying the members of the struct it depends on to allow the macro to find the optimal resultion order.
|
||||
/// These dependencies should match the types declared in the trait the member implements.
|
||||
///
|
||||
/// # The node_dep_state attribute
|
||||
/// The node_dep_state attribute declares a member that implements the NodeDepState trait.
|
||||
/// ```rust, ignore
|
||||
/// #[derive(State)]
|
||||
/// struct MyStruct {
|
||||
/// // MyDependency implements ChildDepState<()>
|
||||
/// #[node_dep_state()]
|
||||
/// my_dependency_1: MyDependency,
|
||||
/// // MyDependency2 implements ChildDepState<(MyDependency,)>
|
||||
/// #[node_dep_state(my_dependency_1)]
|
||||
/// my_dependency_2: MyDependency2,
|
||||
/// }
|
||||
/// // or
|
||||
/// #[derive(State)]
|
||||
/// struct MyStruct {
|
||||
/// // MyDependnancy implements NodeDepState<()>
|
||||
/// #[node_dep_state()]
|
||||
/// my_dependency_1: MyDependency,
|
||||
/// // MyDependency2 implements NodeDepState<()>
|
||||
/// #[node_dep_state()]
|
||||
/// my_dependency_2: MyDependency2,
|
||||
/// // MyDependency3 implements NodeDepState<(MyDependency, MyDependency2)> with Ctx = f32
|
||||
/// #[node_dep_state((my_dependency_1, my_dependency_2), f32)]
|
||||
/// my_dependency_3: MyDependency2,
|
||||
/// }
|
||||
/// ```
|
||||
/// # The child_dep_state attribute
|
||||
/// The child_dep_state attribute declares a member that implements the ChildDepState trait.
|
||||
/// ```rust, ignore
|
||||
/// #[derive(State)]
|
||||
/// struct MyStruct {
|
||||
/// // MyDependnacy implements ChildDepState with DepState = Self
|
||||
/// #[child_dep_state(my_dependency_1)]
|
||||
/// my_dependency_1: MyDependency,
|
||||
/// }
|
||||
/// // or
|
||||
/// #[derive(State)]
|
||||
/// struct MyStruct {
|
||||
/// // MyDependnacy implements ChildDepState with DepState = Self
|
||||
/// #[child_dep_state(my_dependency_1)]
|
||||
/// my_dependency_1: MyDependency,
|
||||
/// // MyDependnacy2 implements ChildDepState with DepState = MyDependency and Ctx = f32
|
||||
/// #[child_dep_state(my_dependency_1, f32)]
|
||||
/// my_dependency_2: MyDependency2,
|
||||
/// }
|
||||
/// ```
|
||||
/// # The parent_dep_state attribute
|
||||
/// The parent_dep_state attribute declares a member that implements the ParentDepState trait.
|
||||
/// The parent_dep_state attribute can be called in the forms:
|
||||
/// ```rust, ignore
|
||||
/// #[derive(State)]
|
||||
/// struct MyStruct {
|
||||
/// // MyDependnacy implements ParentDepState with DepState = Self
|
||||
/// #[parent_dep_state(my_dependency_1)]
|
||||
/// my_dependency_1: MyDependency,
|
||||
/// }
|
||||
/// // or
|
||||
/// #[derive(State)]
|
||||
/// struct MyStruct {
|
||||
/// // MyDependnacy implements ParentDepState with DepState = Self
|
||||
/// #[parent_dep_state(my_dependency_1)]
|
||||
/// my_dependency_1: MyDependency,
|
||||
/// // MyDependnacy2 implements ParentDepState with DepState = MyDependency and Ctx = f32
|
||||
/// #[parent_dep_state(my_dependency_1, f32)]
|
||||
/// my_dependency_2: MyDependency2,
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Combining dependancies
|
||||
/// The node_dep_state, parent_dep_state, and child_dep_state attributes can be combined to allow for more complex dependancies.
|
||||
/// For example if we wanted to combine the font that is passed from the parent to the child and the layout of the size children to find the size of the current node we could do:
|
||||
/// ```rust, ignore
|
||||
/// #[derive(State)]
|
||||
/// struct MyStruct {
|
||||
/// // ChildrenSize implements ChildDepState with DepState = Size
|
||||
/// #[child_dep_state(size)]
|
||||
/// children_size: ChildrenSize,
|
||||
/// // FontSize implements ParentDepState with DepState = Self
|
||||
/// #[parent_dep_state(font_size)]
|
||||
/// font_size: FontSize,
|
||||
/// // Size implements NodeDepState<(ChildrenSize, FontSize)>
|
||||
/// #[parent_dep_state((children_size, font_size))]
|
||||
/// size: Size,
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # The state attribute
|
||||
/// The state macro declares a member that implements the State trait. This allows you to organize your state into multiple isolated components.
|
||||
/// Unlike the other attributes, the state attribute does not accept any arguments, because a nested state cannot depend on any other part of the state.
|
||||
///
|
||||
/// # Custom values
|
||||
///
|
||||
/// If your state has a custom value type you can specify it with the state attribute.
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// #[derive(State)]
|
||||
/// #[state(custom_value = MyCustomType)]
|
||||
/// struct MyStruct {
|
||||
/// // ...
|
||||
/// }
|
||||
#[proc_macro_derive(
|
||||
State,
|
||||
attributes(node_dep_state, child_dep_state, parent_dep_state, state)
|
||||
)]
|
||||
pub fn state_macro_derive(input: TokenStream) -> TokenStream {
|
||||
let ast = syn::parse(input).unwrap();
|
||||
impl_derive_macro(&ast)
|
||||
}
|
||||
|
||||
fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
|
||||
let custom_type = ast
|
||||
.attrs
|
||||
let has_create_fn = impl_block
|
||||
.items
|
||||
.iter()
|
||||
.find(|a| a.path.is_ident("state"))
|
||||
.and_then(|attr| {
|
||||
// parse custom_type = "MyType"
|
||||
let assignment = attr.parse_args::<syn::Expr>().unwrap();
|
||||
if let syn::Expr::Assign(assign) = assignment {
|
||||
let (left, right) = (&*assign.left, &*assign.right);
|
||||
if let syn::Expr::Path(e) = left {
|
||||
let path = &e.path;
|
||||
if let Some(ident) = path.get_ident() {
|
||||
if ident == "custom_value" {
|
||||
return match right {
|
||||
syn::Expr::Path(e) => {
|
||||
let path = &e.path;
|
||||
Some(quote! {#path})
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
.unwrap_or(quote! {()});
|
||||
let type_name = &ast.ident;
|
||||
let fields: Vec<_> = match &ast.data {
|
||||
syn::Data::Struct(data) => match &data.fields {
|
||||
syn::Fields::Named(e) => &e.named,
|
||||
syn::Fields::Unnamed(_) => todo!("unnamed fields"),
|
||||
syn::Fields::Unit => todo!("unit structs"),
|
||||
}
|
||||
.any(|item| matches!(item, syn::ImplItem::Method(method) if method.sig.ident == "create"));
|
||||
|
||||
let parent_dependencies = impl_block
|
||||
.items
|
||||
.iter()
|
||||
.collect(),
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
let strct = Struct::new(type_name.clone(), &fields);
|
||||
match StateStruct::parse(&fields, &strct) {
|
||||
Ok(state_strct) => {
|
||||
let passes = state_strct.state_members.iter().map(|m| {
|
||||
let unit = &m.mem.unit_type;
|
||||
match m.dep_kind {
|
||||
DependencyKind::Node => quote! {dioxus_native_core::AnyPass::Node(&#unit)},
|
||||
DependencyKind::Child => quote! {dioxus_native_core::AnyPass::Upward(&#unit)},
|
||||
DependencyKind::Parent => {
|
||||
quote! {dioxus_native_core::AnyPass::Downward(&#unit)}
|
||||
}
|
||||
}
|
||||
});
|
||||
let member_types = state_strct.state_members.iter().map(|m| &m.mem.ty);
|
||||
let impl_members = state_strct
|
||||
.state_members
|
||||
.iter()
|
||||
.map(|m| m.impl_pass(state_strct.ty, &custom_type));
|
||||
|
||||
let gen = quote! {
|
||||
#(#impl_members)*
|
||||
impl State<#custom_type> for #type_name {
|
||||
const PASSES: &'static [dioxus_native_core::AnyPass<dioxus_native_core::node::Node<Self, #custom_type>>] = &[
|
||||
#(#passes),*
|
||||
];
|
||||
const MASKS: &'static [dioxus_native_core::NodeMask] = &[#(#member_types::NODE_MASK),*];
|
||||
}
|
||||
};
|
||||
gen.into()
|
||||
}
|
||||
Err(e) => e.into_compile_error().into(),
|
||||
}
|
||||
}
|
||||
|
||||
struct Struct {
|
||||
name: Ident,
|
||||
members: Vec<Member>,
|
||||
}
|
||||
|
||||
impl Struct {
|
||||
fn new(name: Ident, fields: &[&Field]) -> Self {
|
||||
let members = fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, f)| Member::parse(&name, f, i as u64))
|
||||
.collect();
|
||||
Self { name, members }
|
||||
}
|
||||
}
|
||||
|
||||
struct StateStruct<'a> {
|
||||
state_members: Vec<StateMember<'a>>,
|
||||
#[allow(unused)]
|
||||
child_states: Vec<&'a Member>,
|
||||
ty: &'a Ident,
|
||||
}
|
||||
|
||||
impl<'a> StateStruct<'a> {
|
||||
/// Parse the state structure, and find a resolution order that will allow us to update the state for each node in after the state(s) it depends on have been resolved.
|
||||
fn parse(fields: &[&'a Field], strct: &'a Struct) -> Result<Self> {
|
||||
let mut parse_err = Ok(());
|
||||
let mut state_members: Vec<_> = strct
|
||||
.members
|
||||
.iter()
|
||||
.zip(fields.iter())
|
||||
.filter_map(|(m, f)| match StateMember::parse(f, m, strct) {
|
||||
Ok(m) => m,
|
||||
Err(err) => {
|
||||
parse_err = Err(err);
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
parse_err?;
|
||||
for i in 0..state_members.len() {
|
||||
let deps: Vec<_> = state_members[i].dep_mems.iter().map(|m| m.id).collect();
|
||||
for dep in deps {
|
||||
state_members[dep as usize].dependant_mems.push(i as u64);
|
||||
}
|
||||
}
|
||||
|
||||
let child_states = strct
|
||||
.members
|
||||
.iter()
|
||||
.zip(fields.iter())
|
||||
.filter(|(_, f)| {
|
||||
f.attrs.iter().any(|a| {
|
||||
a.path
|
||||
.get_ident()
|
||||
.filter(|i| i.to_string().as_str() == "state")
|
||||
.is_some()
|
||||
})
|
||||
})
|
||||
.map(|(m, _)| m);
|
||||
|
||||
// members need to be sorted so that members are updated after the members they depend on
|
||||
Ok(Self {
|
||||
ty: &strct.name,
|
||||
state_members,
|
||||
child_states: child_states.collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn try_parenthesized(input: ParseStream) -> Result<ParseBuffer> {
|
||||
let inside;
|
||||
parenthesized!(inside in input);
|
||||
Ok(inside)
|
||||
}
|
||||
|
||||
struct Dependency {
|
||||
ctx_ty: Option<Type>,
|
||||
deps: Vec<Ident>,
|
||||
}
|
||||
|
||||
impl Parse for Dependency {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let deps: Option<Punctuated<Ident, Token![,]>> = {
|
||||
try_parenthesized(input)
|
||||
.ok()
|
||||
.and_then(|inside| inside.parse_terminated(Ident::parse).ok())
|
||||
};
|
||||
let deps: Vec<_> = deps
|
||||
.map(|deps| deps.into_iter().collect())
|
||||
.or_else(|| {
|
||||
input
|
||||
.parse::<Ident>()
|
||||
.ok()
|
||||
.filter(|i: &Ident| i != "NONE")
|
||||
.map(|i| vec![i])
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let comma: Option<Token![,]> = input.parse().ok();
|
||||
let ctx_ty = input.parse().ok();
|
||||
Ok(Self {
|
||||
ctx_ty: comma.and(ctx_ty),
|
||||
deps,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of the member and the ident of the member
|
||||
#[derive(PartialEq, Debug)]
|
||||
struct Member {
|
||||
id: u64,
|
||||
ty: Type,
|
||||
unit_type: Ident,
|
||||
ident: Ident,
|
||||
}
|
||||
|
||||
impl Member {
|
||||
fn parse(parent: &Ident, field: &Field, id: u64) -> Option<Self> {
|
||||
Some(Self {
|
||||
id,
|
||||
ty: field.ty.clone(),
|
||||
unit_type: Ident::new(
|
||||
("_Unit".to_string()
|
||||
+ parent.to_token_stream().to_string().as_str()
|
||||
+ field.ty.to_token_stream().to_string().as_str())
|
||||
.as_str(),
|
||||
Span::call_site(),
|
||||
),
|
||||
ident: field.ident.as_ref()?.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct StateMember<'a> {
|
||||
mem: &'a Member,
|
||||
// the kind of dependncies this state has
|
||||
dep_kind: DependencyKind,
|
||||
// the depenancy and if it is satified
|
||||
dep_mems: Vec<&'a Member>,
|
||||
// any members that depend on this member
|
||||
dependant_mems: Vec<u64>,
|
||||
// the context this state requires
|
||||
ctx_ty: Option<Type>,
|
||||
}
|
||||
|
||||
impl<'a> StateMember<'a> {
|
||||
fn parse(
|
||||
field: &Field,
|
||||
mem: &'a Member,
|
||||
parent: &'a Struct,
|
||||
) -> Result<Option<StateMember<'a>>> {
|
||||
let mut err = Ok(());
|
||||
let member = field.attrs.iter().find_map(|a| {
|
||||
let dep_kind = a
|
||||
.path
|
||||
.get_ident()
|
||||
.and_then(|i| match i.to_string().as_str() {
|
||||
"node_dep_state" => Some(DependencyKind::Node),
|
||||
"child_dep_state" => Some(DependencyKind::Child),
|
||||
"parent_dep_state" => Some(DependencyKind::Parent),
|
||||
_ => None,
|
||||
})?;
|
||||
match a.parse_args::<Dependency>() {
|
||||
Ok(dependency) => {
|
||||
let dep_mems = dependency
|
||||
.deps
|
||||
.iter()
|
||||
.filter_map(|name| {
|
||||
if let Some(found) = parent.members.iter().find(|m| &m.ident == name) {
|
||||
Some(found)
|
||||
} else {
|
||||
err = Err(Error::new(
|
||||
name.span(),
|
||||
format!("{} not found in {}", name, parent.name),
|
||||
));
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
Some(Self {
|
||||
mem,
|
||||
dep_kind,
|
||||
dep_mems,
|
||||
dependant_mems: Vec::new(),
|
||||
ctx_ty: dependency.ctx_ty,
|
||||
})
|
||||
}
|
||||
Err(e) => {
|
||||
err = Err(e);
|
||||
None
|
||||
}
|
||||
}
|
||||
});
|
||||
err?;
|
||||
Ok(member)
|
||||
}
|
||||
|
||||
/// generate code to call the resolve function for the state. This does not handle checking if resolving the state is necessary, or marking the states that depend on this state as dirty.
|
||||
fn impl_pass(
|
||||
&self,
|
||||
parent_type: &Ident,
|
||||
custom_type: impl ToTokens,
|
||||
) -> quote::__private::TokenStream {
|
||||
let ident = &self.mem.ident;
|
||||
let get_ctx = if let Some(ctx_ty) = &self.ctx_ty {
|
||||
if ctx_ty == &parse_quote!(()) {
|
||||
quote! {&()}
|
||||
.find_map(|item| {
|
||||
if let syn::ImplItem::Type(syn::ImplItemType { ident, ty, .. }) = item {
|
||||
(ident == "ParentDependencies").then_some(ty)
|
||||
} else {
|
||||
let msg = ctx_ty.to_token_stream().to_string() + " not found in context";
|
||||
quote! {ctx.get().expect(#msg)}
|
||||
None
|
||||
}
|
||||
})
|
||||
.expect("ParentDependencies must be defined");
|
||||
let child_dependencies = impl_block
|
||||
.items
|
||||
.iter()
|
||||
.find_map(|item| {
|
||||
if let syn::ImplItem::Type(syn::ImplItemType { ident, ty, .. }) = item {
|
||||
(ident == "ChildDependencies").then_some(ty)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.expect("ChildDependencies must be defined");
|
||||
let node_dependencies = impl_block
|
||||
.items
|
||||
.iter()
|
||||
.find_map(|item| {
|
||||
if let syn::ImplItem::Type(syn::ImplItemType { ident, ty, .. }) = item {
|
||||
(ident == "NodeDependencies").then_some(ty)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.expect("NodeDependencies must be defined");
|
||||
|
||||
let this_type = &impl_block.self_ty;
|
||||
let this_type = extract_type_path(this_type)
|
||||
.unwrap_or_else(|| panic!("Self must be a type path, found {}", quote!(#this_type)));
|
||||
|
||||
let mut combined_dependencies = HashSet::new();
|
||||
|
||||
let self_path: TypePath = syn::parse_quote!(Self);
|
||||
|
||||
let parent_dependencies = match extract_tuple(parent_dependencies) {
|
||||
Some(tuple) => {
|
||||
let mut parent_dependencies = Vec::new();
|
||||
for type_ in &tuple.elems {
|
||||
let mut type_ = extract_type_path(type_).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"ParentDependencies must be a tuple of type paths, found {}",
|
||||
quote!(#type_)
|
||||
)
|
||||
});
|
||||
if type_ == self_path {
|
||||
type_ = this_type.clone();
|
||||
}
|
||||
combined_dependencies.insert(type_.clone());
|
||||
parent_dependencies.push(type_);
|
||||
}
|
||||
parent_dependencies
|
||||
}
|
||||
_ => panic!(
|
||||
"ParentDependencies must be a tuple, found {}",
|
||||
quote!(#parent_dependencies)
|
||||
),
|
||||
};
|
||||
let child_dependencies = match extract_tuple(child_dependencies) {
|
||||
Some(tuple) => {
|
||||
let mut child_dependencies = Vec::new();
|
||||
for type_ in &tuple.elems {
|
||||
let mut type_ = extract_type_path(type_).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"ChildDependencies must be a tuple of type paths, found {}",
|
||||
quote!(#type_)
|
||||
)
|
||||
});
|
||||
if type_ == self_path {
|
||||
type_ = this_type.clone();
|
||||
}
|
||||
combined_dependencies.insert(type_.clone());
|
||||
child_dependencies.push(type_);
|
||||
}
|
||||
child_dependencies
|
||||
}
|
||||
_ => panic!(
|
||||
"ChildDependencies must be a tuple, found {}",
|
||||
quote!(#child_dependencies)
|
||||
),
|
||||
};
|
||||
let node_dependencies = match extract_tuple(node_dependencies) {
|
||||
Some(tuple) => {
|
||||
let mut node_dependencies = Vec::new();
|
||||
for type_ in &tuple.elems {
|
||||
let mut type_ = extract_type_path(type_).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"NodeDependencies must be a tuple of type paths, found {}",
|
||||
quote!(#type_)
|
||||
)
|
||||
});
|
||||
if type_ == self_path {
|
||||
type_ = this_type.clone();
|
||||
}
|
||||
combined_dependencies.insert(type_.clone());
|
||||
node_dependencies.push(type_);
|
||||
}
|
||||
node_dependencies
|
||||
}
|
||||
_ => panic!(
|
||||
"NodeDependencies must be a tuple, found {}",
|
||||
quote!(#node_dependencies)
|
||||
),
|
||||
};
|
||||
combined_dependencies.insert(this_type.clone());
|
||||
|
||||
let combined_dependencies: Vec<_> = combined_dependencies.into_iter().collect();
|
||||
let parent_dependancies_idxes: Vec<_> = parent_dependencies
|
||||
.iter()
|
||||
.filter_map(|ident| combined_dependencies.iter().position(|i| i == ident))
|
||||
.collect();
|
||||
let child_dependencies_idxes: Vec<_> = child_dependencies
|
||||
.iter()
|
||||
.filter_map(|ident| combined_dependencies.iter().position(|i| i == ident))
|
||||
.collect();
|
||||
let node_dependencies_idxes: Vec<_> = node_dependencies
|
||||
.iter()
|
||||
.filter_map(|ident| combined_dependencies.iter().position(|i| i == ident))
|
||||
.collect();
|
||||
let this_type_idx = combined_dependencies
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(i, ident)| (this_type == *ident).then_some(i))
|
||||
.unwrap();
|
||||
let this_view = format_ident!("__data{}", this_type_idx);
|
||||
|
||||
let combined_dependencies_quote = combined_dependencies.iter().map(|ident| {
|
||||
if ident == &this_type {
|
||||
quote! {shipyard::ViewMut<#ident>}
|
||||
} else {
|
||||
quote! {shipyard::View<#ident>}
|
||||
}
|
||||
});
|
||||
let combined_dependencies_quote = quote!((#(#combined_dependencies_quote,)*));
|
||||
|
||||
let ItemImpl {
|
||||
attrs,
|
||||
defaultness,
|
||||
unsafety,
|
||||
impl_token,
|
||||
generics,
|
||||
trait_,
|
||||
self_ty,
|
||||
items,
|
||||
..
|
||||
} = impl_block;
|
||||
let for_ = trait_.as_ref().map(|t| t.2);
|
||||
let trait_ = trait_.map(|t| t.1);
|
||||
|
||||
let split_views: Vec<_> = (0..combined_dependencies.len())
|
||||
.map(|i| {
|
||||
let ident = format_ident!("__data{}", i);
|
||||
if i == this_type_idx {
|
||||
quote! {mut #ident}
|
||||
} else {
|
||||
quote! {#ident}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let node_view = node_dependencies_idxes
|
||||
.iter()
|
||||
.map(|i| format_ident!("__data{}", i))
|
||||
.collect::<Vec<_>>();
|
||||
let get_node_view = {
|
||||
if node_dependencies.is_empty() {
|
||||
quote! {
|
||||
let raw_node = ();
|
||||
}
|
||||
} else {
|
||||
quote! {&()}
|
||||
};
|
||||
|
||||
let ty = &self.mem.ty;
|
||||
let unit_type = &self.mem.unit_type;
|
||||
let node_view =
|
||||
quote!(dioxus_native_core::node_ref::NodeView::new(&node.node_data, #ty::NODE_MASK));
|
||||
let dep_idents = self.dep_mems.iter().map(|m| &m.ident);
|
||||
let impl_specific = match self.dep_kind {
|
||||
DependencyKind::Node => {
|
||||
quote! {
|
||||
impl dioxus_native_core::NodePass<dioxus_native_core::node::Node<#parent_type, #custom_type>> for #unit_type {
|
||||
fn pass(&self, node: &mut dioxus_native_core::node::Node<#parent_type, #custom_type>, ctx: &dioxus_native_core::SendAnyMap) -> bool {
|
||||
node.state.#ident.reduce(#node_view, (#(&node.state.#dep_idents,)*), #get_ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
DependencyKind::Child => {
|
||||
let update = if self.dep_mems.iter().any(|m| m.id == self.mem.id) {
|
||||
quote! {
|
||||
if update {
|
||||
dioxus_native_core::PassReturn{
|
||||
progress: true,
|
||||
mark_dirty: true,
|
||||
}
|
||||
} else {
|
||||
dioxus_native_core::PassReturn{
|
||||
progress: false,
|
||||
mark_dirty: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
if update {
|
||||
dioxus_native_core::PassReturn{
|
||||
progress: false,
|
||||
mark_dirty: true,
|
||||
}
|
||||
} else {
|
||||
dioxus_native_core::PassReturn{
|
||||
progress: false,
|
||||
mark_dirty: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
let temps = (0..node_dependencies.len())
|
||||
.map(|i| format_ident!("__temp{}", i))
|
||||
.collect::<Vec<_>>();
|
||||
quote! {
|
||||
let raw_node: (#(*const #node_dependencies,)*) = {
|
||||
let (#(#temps,)*) = (#(&#node_view,)*).get(id).unwrap_or_else(|err| panic!("Failed to get node view {:?}", err));
|
||||
(#(#temps as *const _,)*)
|
||||
};
|
||||
quote!(
|
||||
impl dioxus_native_core::UpwardPass<dioxus_native_core::node::Node<#parent_type, #custom_type>> for #unit_type{
|
||||
fn pass<'a>(
|
||||
&self,
|
||||
node: &mut dioxus_native_core::node::Node<#parent_type, #custom_type>,
|
||||
children: &mut dyn Iterator<Item = &'a mut dioxus_native_core::node::Node<#parent_type, #custom_type>>,
|
||||
ctx: &dioxus_native_core::SendAnyMap,
|
||||
) -> dioxus_native_core::PassReturn {
|
||||
let update = node.state.#ident.reduce(#node_view, children.map(|c| (#(&c.state.#dep_idents,)*)), #get_ctx);
|
||||
#update
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
DependencyKind::Parent => {
|
||||
let update = if self.dep_mems.iter().any(|m| m.id == self.mem.id) {
|
||||
quote! {
|
||||
if update {
|
||||
dioxus_native_core::PassReturn{
|
||||
progress: true,
|
||||
mark_dirty: true,
|
||||
}
|
||||
} else {
|
||||
dioxus_native_core::PassReturn{
|
||||
progress: false,
|
||||
mark_dirty: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
if update {
|
||||
dioxus_native_core::PassReturn{
|
||||
progress: false,
|
||||
mark_dirty: true,
|
||||
}
|
||||
} else {
|
||||
dioxus_native_core::PassReturn{
|
||||
progress: false,
|
||||
mark_dirty: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
quote!(
|
||||
impl dioxus_native_core::DownwardPass<dioxus_native_core::node::Node<#parent_type, #custom_type>> for #unit_type {
|
||||
fn pass(&self, node: &mut dioxus_native_core::node::Node<#parent_type, #custom_type>, parent: Option<&mut dioxus_native_core::node::Node<#parent_type, #custom_type>>, ctx: &dioxus_native_core::SendAnyMap) -> dioxus_native_core::PassReturn{
|
||||
let update = node.state.#ident.reduce(#node_view, parent.as_ref().map(|p| (#(&p.state.#dep_idents,)*)), #get_ctx);
|
||||
#update
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
};
|
||||
let pass_id = self.mem.id;
|
||||
let depenancies = self.dep_mems.iter().map(|m| m.id);
|
||||
let dependants = &self.dependant_mems;
|
||||
let mask = self
|
||||
.dep_mems
|
||||
.iter()
|
||||
.map(|m| 1u64 << m.id)
|
||||
.fold(1 << self.mem.id, |a, b| a | b);
|
||||
quote! {
|
||||
#[derive(Clone, Copy)]
|
||||
struct #unit_type;
|
||||
#impl_specific
|
||||
impl dioxus_native_core::Pass for #unit_type {
|
||||
fn pass_id(&self) -> dioxus_native_core::PassId {
|
||||
dioxus_native_core::PassId(#pass_id)
|
||||
}
|
||||
fn dependancies(&self) -> &'static [dioxus_native_core::PassId] {
|
||||
&[#(dioxus_native_core::PassId(#depenancies)),*]
|
||||
}
|
||||
fn dependants(&self) -> &'static [dioxus_native_core::PassId] {
|
||||
&[#(dioxus_native_core::PassId(#dependants)),*]
|
||||
}
|
||||
fn mask(&self) -> dioxus_native_core::MemberMask {
|
||||
dioxus_native_core::MemberMask(#mask)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
let deref_node_view = {
|
||||
if node_dependencies.is_empty() {
|
||||
quote! {
|
||||
let node = raw_node;
|
||||
}
|
||||
} else {
|
||||
let indexes = (0..node_dependencies.len()).map(syn::Index::from);
|
||||
quote! {
|
||||
let node = unsafe { (#(dioxus_native_core::prelude::DependancyView::new(&*raw_node.#indexes),)*) };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let parent_view = parent_dependancies_idxes
|
||||
.iter()
|
||||
.map(|i| format_ident!("__data{}", i))
|
||||
.collect::<Vec<_>>();
|
||||
let get_parent_view = {
|
||||
if parent_dependencies.is_empty() {
|
||||
quote! {
|
||||
let raw_parent = tree.parent_id(id).map(|_| ());
|
||||
}
|
||||
} else {
|
||||
let temps = (0..parent_dependencies.len())
|
||||
.map(|i| format_ident!("__temp{}", i))
|
||||
.collect::<Vec<_>>();
|
||||
quote! {
|
||||
let raw_parent = tree.parent_id(id).and_then(|parent_id| {
|
||||
let raw_parent: Option<(#(*const #parent_dependencies,)*)> = (#(&#parent_view,)*).get(parent_id).ok().map(|c| {
|
||||
let (#(#temps,)*) = c;
|
||||
(#(#temps as *const _,)*)
|
||||
});
|
||||
raw_parent
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
let deref_parent_view = {
|
||||
if parent_dependencies.is_empty() {
|
||||
quote! {
|
||||
let parent = raw_parent;
|
||||
}
|
||||
} else {
|
||||
let indexes = (0..parent_dependencies.len()).map(syn::Index::from);
|
||||
quote! {
|
||||
let parent = unsafe { raw_parent.map(|raw_parent| (#(dioxus_native_core::prelude::DependancyView::new(&*raw_parent.#indexes),)*)) };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let child_view = child_dependencies_idxes
|
||||
.iter()
|
||||
.map(|i| format_ident!("__data{}", i))
|
||||
.collect::<Vec<_>>();
|
||||
let get_child_view = {
|
||||
if child_dependencies.is_empty() {
|
||||
quote! {
|
||||
let raw_children: Vec<_> = tree.children_ids(id).into_iter().map(|_| ()).collect();
|
||||
}
|
||||
} else {
|
||||
let temps = (0..child_dependencies.len())
|
||||
.map(|i| format_ident!("__temp{}", i))
|
||||
.collect::<Vec<_>>();
|
||||
quote! {
|
||||
let raw_children: Vec<_> = tree.children_ids(id).into_iter().filter_map(|id| {
|
||||
let raw_children: Option<(#(*const #child_dependencies,)*)> = (#(&#child_view,)*).get(id).ok().map(|c| {
|
||||
let (#(#temps,)*) = c;
|
||||
(#(#temps as *const _,)*)
|
||||
});
|
||||
raw_children
|
||||
}).collect();
|
||||
}
|
||||
}
|
||||
};
|
||||
let deref_child_view = {
|
||||
if child_dependencies.is_empty() {
|
||||
quote! {
|
||||
let children = raw_children;
|
||||
}
|
||||
} else {
|
||||
let indexes = (0..child_dependencies.len()).map(syn::Index::from);
|
||||
quote! {
|
||||
let children = unsafe { raw_children.iter().map(|raw_children| (#(dioxus_native_core::prelude::DependancyView::new(&*raw_children.#indexes),)*)).collect::<Vec<_>>() };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let trait_generics = trait_
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.segments
|
||||
.last()
|
||||
.unwrap()
|
||||
.arguments
|
||||
.clone();
|
||||
|
||||
// if a create function is defined, we don't generate one
|
||||
// otherwise we generate a default one that uses the update function and the default constructor
|
||||
let create_fn = (!has_create_fn).then(|| {
|
||||
quote! {
|
||||
fn create<'a>(
|
||||
node_view: dioxus_native_core::prelude::NodeView # trait_generics,
|
||||
node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
|
||||
parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
context: &dioxus_native_core::prelude::SendAnyMap,
|
||||
) -> Self {
|
||||
let mut myself = Self::default();
|
||||
myself.update(node_view, node, parent, children, context);
|
||||
myself
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
quote!(
|
||||
#(#attrs)*
|
||||
#defaultness #unsafety #impl_token #generics #trait_ #for_ #self_ty {
|
||||
#create_fn
|
||||
|
||||
#(#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 {
|
||||
use dioxus_native_core::exports::shipyard::{IntoWorkloadSystem, Get, AddComponent};
|
||||
use dioxus_native_core::tree::TreeRef;
|
||||
use dioxus_native_core::prelude::{NodeType, NodeView};
|
||||
|
||||
let node_mask = Self::NODE_MASK.build();
|
||||
|
||||
(move |data: #combined_dependencies_quote, run_view: dioxus_native_core::prelude::RunPassView #trait_generics| {
|
||||
let (#(#split_views,)*) = data;
|
||||
let tree = run_view.tree.clone();
|
||||
let node_types = run_view.node_type.clone();
|
||||
dioxus_native_core::prelude::run_pass(type_id, dependants.clone(), pass_direction, run_view, |id, context| {
|
||||
let node_data: &NodeType<_> = node_types.get(id).unwrap_or_else(|err| panic!("Failed to get node type {:?}", err));
|
||||
// get all of the states from the tree view
|
||||
// Safety: No node has itself as a parent or child.
|
||||
let raw_myself: Option<*mut Self> = (&mut #this_view).get(id).ok().map(|c| c as *mut _);
|
||||
#get_node_view
|
||||
#get_parent_view
|
||||
#get_child_view
|
||||
|
||||
let myself: Option<&mut Self> = unsafe { raw_myself.map(|val| &mut *val) };
|
||||
#deref_node_view
|
||||
#deref_parent_view
|
||||
#deref_child_view
|
||||
|
||||
let view = NodeView::new(id, node_data, &node_mask);
|
||||
if let Some(myself) = myself {
|
||||
myself
|
||||
.update(view, node, parent, children, context)
|
||||
}
|
||||
else {
|
||||
(&mut #this_view).add_component_unchecked(
|
||||
id,
|
||||
Self::create(view, node, parent, children, context));
|
||||
true
|
||||
}
|
||||
})
|
||||
}).into_workload_system().unwrap()
|
||||
}
|
||||
}
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn extract_tuple(ty: &Type) -> Option<TypeTuple> {
|
||||
match ty {
|
||||
Type::Tuple(tuple) => Some(tuple.clone()),
|
||||
Type::Group(group) => extract_tuple(&group.elem),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_type_path(ty: &Type) -> Option<TypePath> {
|
||||
match ty {
|
||||
Type::Path(path) => Some(path.clone()),
|
||||
Type::Group(group) => extract_type_path(&group.elem),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
extern crate proc_macro;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use syn::{
|
||||
self, bracketed,
|
||||
parse::{Parse, ParseStream, Result},
|
||||
LitStr, Token,
|
||||
};
|
||||
|
||||
pub struct StrSlice {
|
||||
pub map: BTreeMap<String, LitStr>,
|
||||
}
|
||||
|
||||
impl Parse for StrSlice {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let content;
|
||||
bracketed!(content in input);
|
||||
let mut map = BTreeMap::new();
|
||||
while let Ok(s) = content.parse::<LitStr>() {
|
||||
map.insert(s.value(), s);
|
||||
#[allow(unused_must_use)]
|
||||
{
|
||||
content.parse::<Token![,]>();
|
||||
}
|
||||
}
|
||||
Ok(StrSlice { map })
|
||||
}
|
||||
}
|
|
@ -1,244 +0,0 @@
|
|||
use dioxus::prelude::*;
|
||||
use dioxus_native_core::node_ref::*;
|
||||
use dioxus_native_core::real_dom::*;
|
||||
use dioxus_native_core::state::{
|
||||
ChildDepState, ElementBorrowable, NodeDepState, ParentDepState, State,
|
||||
};
|
||||
use dioxus_native_core::tree::*;
|
||||
use dioxus_native_core::SendAnyMap;
|
||||
use dioxus_native_core_macro::State;
|
||||
|
||||
macro_rules! dep {
|
||||
( child( $name:ty, $dep:ty ) ) => {
|
||||
impl ChildDepState for $name {
|
||||
type Ctx = ();
|
||||
type DepState = $dep;
|
||||
const NODE_MASK: NodeMask = NodeMask::ALL;
|
||||
fn reduce<'a>(
|
||||
&mut self,
|
||||
_: NodeView,
|
||||
_: impl Iterator<Item = <Self::DepState as ElementBorrowable>::ElementBorrowed<'a>>,
|
||||
_: &Self::Ctx,
|
||||
) -> bool
|
||||
where
|
||||
Self::DepState: 'a,
|
||||
{
|
||||
self.0 += 1;
|
||||
true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
( parent( $name:ty, $dep:ty ) ) => {
|
||||
impl ParentDepState for $name {
|
||||
type Ctx = ();
|
||||
type DepState = $dep;
|
||||
const NODE_MASK: NodeMask = NodeMask::ALL;
|
||||
fn reduce(
|
||||
&mut self,
|
||||
_: NodeView,
|
||||
_: Option<<Self::DepState as ElementBorrowable>::ElementBorrowed<'_>>,
|
||||
_: &Self::Ctx,
|
||||
) -> bool {
|
||||
self.0 += 1;
|
||||
true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
( node( $name:ty, $dep:ty ) ) => {
|
||||
impl NodeDepState for $name {
|
||||
type Ctx = ();
|
||||
type DepState = $dep;
|
||||
const NODE_MASK: NodeMask = NodeMask::ALL;
|
||||
fn reduce(
|
||||
&mut self,
|
||||
_: NodeView,
|
||||
_: <Self::DepState as ElementBorrowable>::ElementBorrowed<'_>,
|
||||
_: &Self::Ctx,
|
||||
) -> bool {
|
||||
self.0 += 1;
|
||||
true
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! test_state{
|
||||
( $s:ty, child: ( $( $child:ident ),* ), node: ( $( $node:ident ),* ), parent: ( $( $parent:ident ),* ) ) => {
|
||||
#[test]
|
||||
fn state_reduce_initally_called_minimally() {
|
||||
#[allow(non_snake_case)]
|
||||
fn Base(cx: Scope) -> Element {
|
||||
render!{
|
||||
div {
|
||||
div{
|
||||
div{
|
||||
p{}
|
||||
}
|
||||
p{
|
||||
"hello"
|
||||
}
|
||||
div{
|
||||
h1{}
|
||||
}
|
||||
p{
|
||||
"world"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut vdom = VirtualDom::new(Base);
|
||||
|
||||
let mutations = vdom.rebuild();
|
||||
|
||||
let mut dom: RealDom<$s> = RealDom::new();
|
||||
|
||||
let (nodes_updated, _) = dom.apply_mutations(mutations);
|
||||
let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::new());
|
||||
|
||||
dom.traverse_depth_first(|n| {
|
||||
$(
|
||||
assert_eq!(n.state.$child.0, 1);
|
||||
)*
|
||||
$(
|
||||
assert_eq!(n.state.$node.0, 1);
|
||||
)*
|
||||
$(
|
||||
assert_eq!(n.state.$parent.0, 1);
|
||||
)*
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod node_depends_on_child_and_parent {
|
||||
use super::*;
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
struct Node(i32);
|
||||
dep!(node(Node, (Child, Parent)));
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
struct Child(i32);
|
||||
dep!(child(Child, (Child,)));
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
struct Parent(i32);
|
||||
dep!(parent(Parent, (Parent,)));
|
||||
|
||||
#[derive(Debug, Clone, Default, State)]
|
||||
struct StateTester {
|
||||
#[node_dep_state((child, parent))]
|
||||
node: Node,
|
||||
#[child_dep_state(child)]
|
||||
child: Child,
|
||||
#[parent_dep_state(parent)]
|
||||
parent: Parent,
|
||||
}
|
||||
|
||||
test_state!(StateTester, child: (child), node: (node), parent: (parent));
|
||||
}
|
||||
|
||||
mod child_depends_on_node_that_depends_on_parent {
|
||||
use super::*;
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
struct Node(i32);
|
||||
dep!(node(Node, (Parent,)));
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
struct Child(i32);
|
||||
dep!(child(Child, (Node,)));
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
struct Parent(i32);
|
||||
dep!(parent(Parent, (Parent,)));
|
||||
|
||||
#[derive(Debug, Clone, Default, State)]
|
||||
struct StateTester {
|
||||
#[node_dep_state(parent)]
|
||||
node: Node,
|
||||
#[child_dep_state(node)]
|
||||
child: Child,
|
||||
#[parent_dep_state(parent)]
|
||||
parent: Parent,
|
||||
}
|
||||
|
||||
test_state!(StateTester, child: (child), node: (node), parent: (parent));
|
||||
}
|
||||
|
||||
mod parent_depends_on_node_that_depends_on_child {
|
||||
use super::*;
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
struct Node(i32);
|
||||
dep!(node(Node, (Child,)));
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
struct Child(i32);
|
||||
dep!(child(Child, (Child,)));
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
struct Parent(i32);
|
||||
dep!(parent(Parent, (Node,)));
|
||||
|
||||
#[derive(Debug, Clone, Default, State)]
|
||||
struct StateTester {
|
||||
#[node_dep_state(child)]
|
||||
node: Node,
|
||||
#[child_dep_state(child)]
|
||||
child: Child,
|
||||
#[parent_dep_state(node)]
|
||||
parent: Parent,
|
||||
}
|
||||
|
||||
test_state!(StateTester, child: (child), node: (node), parent: (parent));
|
||||
}
|
||||
|
||||
mod node_depends_on_other_node_state {
|
||||
use super::*;
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
struct Node1(i32);
|
||||
dep!(node(Node1, (Node2,)));
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
struct Node2(i32);
|
||||
dep!(node(Node2, ()));
|
||||
|
||||
#[derive(Debug, Clone, Default, State)]
|
||||
struct StateTester {
|
||||
#[node_dep_state((node2))]
|
||||
node1: Node1,
|
||||
#[node_dep_state()]
|
||||
node2: Node2,
|
||||
}
|
||||
|
||||
test_state!(StateTester, child: (), node: (node1, node2), parent: ());
|
||||
}
|
||||
|
||||
mod node_child_and_parent_state_depends_on_self {
|
||||
use super::*;
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
struct Node(i32);
|
||||
dep!(node(Node, ()));
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
struct Child(i32);
|
||||
dep!(child(Child, (Child,)));
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
struct Parent(i32);
|
||||
dep!(parent(Parent, (Parent,)));
|
||||
|
||||
#[derive(Debug, Clone, Default, State)]
|
||||
struct StateTester {
|
||||
#[node_dep_state()]
|
||||
node: Node,
|
||||
#[child_dep_state(child)]
|
||||
child: Child,
|
||||
#[parent_dep_state(parent)]
|
||||
parent: Parent,
|
||||
}
|
||||
|
||||
test_state!(StateTester, child: (child), node: (node), parent: (parent));
|
||||
}
|
|
@ -1,421 +0,0 @@
|
|||
use dioxus::prelude::*;
|
||||
use dioxus_native_core::real_dom::*;
|
||||
use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State};
|
||||
use dioxus_native_core::tree::TreeView;
|
||||
use dioxus_native_core::{node_ref::*, NodeId, SendAnyMap};
|
||||
use dioxus_native_core_macro::State;
|
||||
|
||||
#[derive(Debug, Clone, Default, State)]
|
||||
struct CallCounterState {
|
||||
#[child_dep_state(child_counter)]
|
||||
child_counter: ChildDepCallCounter,
|
||||
#[parent_dep_state(parent_counter)]
|
||||
parent_counter: ParentDepCallCounter,
|
||||
#[node_dep_state()]
|
||||
node_counter: NodeDepCallCounter,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct ChildDepCallCounter(u32);
|
||||
impl ChildDepState for ChildDepCallCounter {
|
||||
type Ctx = ();
|
||||
type DepState = (Self,);
|
||||
const NODE_MASK: NodeMask = NodeMask::ALL;
|
||||
fn reduce<'a>(
|
||||
&mut self,
|
||||
_: NodeView,
|
||||
_: impl Iterator<Item = (&'a Self,)>,
|
||||
_: &Self::Ctx,
|
||||
) -> bool
|
||||
where
|
||||
Self::DepState: 'a,
|
||||
{
|
||||
self.0 += 1;
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct ParentDepCallCounter(u32);
|
||||
impl ParentDepState for ParentDepCallCounter {
|
||||
type Ctx = ();
|
||||
type DepState = (Self,);
|
||||
const NODE_MASK: NodeMask = NodeMask::ALL;
|
||||
fn reduce(&mut self, _node: NodeView, _parent: Option<(&Self,)>, _ctx: &Self::Ctx) -> bool {
|
||||
self.0 += 1;
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct NodeDepCallCounter(u32);
|
||||
impl NodeDepState for NodeDepCallCounter {
|
||||
type Ctx = ();
|
||||
type DepState = ();
|
||||
const NODE_MASK: NodeMask = NodeMask::ALL;
|
||||
fn reduce(&mut self, _node: NodeView, _sibling: (), _ctx: &Self::Ctx) -> bool {
|
||||
self.0 += 1;
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::vec_box)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
struct BubbledUpStateTester(Option<String>, Vec<Box<BubbledUpStateTester>>);
|
||||
impl ChildDepState for BubbledUpStateTester {
|
||||
type Ctx = u32;
|
||||
type DepState = (Self,);
|
||||
const NODE_MASK: NodeMask = NodeMask::new().with_tag();
|
||||
fn reduce<'a>(
|
||||
&mut self,
|
||||
node: NodeView,
|
||||
children: impl Iterator<Item = (&'a Self,)>,
|
||||
ctx: &Self::Ctx,
|
||||
) -> bool
|
||||
where
|
||||
Self::DepState: 'a,
|
||||
{
|
||||
assert_eq!(*ctx, 42);
|
||||
*self = BubbledUpStateTester(
|
||||
node.tag().map(|s| s.to_string()),
|
||||
children
|
||||
.into_iter()
|
||||
.map(|(c,)| Box::new(c.clone()))
|
||||
.collect(),
|
||||
);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
struct PushedDownStateTester(Option<String>, Option<Box<PushedDownStateTester>>);
|
||||
impl ParentDepState for PushedDownStateTester {
|
||||
type Ctx = u32;
|
||||
type DepState = (Self,);
|
||||
const NODE_MASK: NodeMask = NodeMask::new().with_tag();
|
||||
fn reduce(&mut self, node: NodeView, parent: Option<(&Self,)>, ctx: &Self::Ctx) -> bool {
|
||||
assert_eq!(*ctx, 42);
|
||||
*self = PushedDownStateTester(
|
||||
node.tag().map(|s| s.to_string()),
|
||||
parent.map(|(c,)| Box::new(c.clone())),
|
||||
);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
struct NodeStateTester(Option<String>, Vec<(String, String)>);
|
||||
impl NodeDepState for NodeStateTester {
|
||||
type Ctx = u32;
|
||||
type DepState = ();
|
||||
const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::All).with_tag();
|
||||
fn reduce(&mut self, node: NodeView, _sibling: (), ctx: &Self::Ctx) -> bool {
|
||||
assert_eq!(*ctx, 42);
|
||||
*self = NodeStateTester(
|
||||
node.tag().map(|s| s.to_string()),
|
||||
node.attributes()
|
||||
.map(|iter| {
|
||||
iter.map(|a| {
|
||||
(
|
||||
a.attribute.name.to_string(),
|
||||
a.value.as_text().unwrap().to_string(),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(State, Clone, Default, Debug)]
|
||||
struct StateTester {
|
||||
#[child_dep_state(bubbled, u32)]
|
||||
bubbled: BubbledUpStateTester,
|
||||
#[parent_dep_state(pushed, u32)]
|
||||
pushed: PushedDownStateTester,
|
||||
#[node_dep_state(NONE, u32)]
|
||||
node: NodeStateTester,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_initial() {
|
||||
#[allow(non_snake_case)]
|
||||
fn Base(cx: Scope) -> Element {
|
||||
render! {
|
||||
div {
|
||||
p{
|
||||
color: "red"
|
||||
}
|
||||
h1{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut vdom = VirtualDom::new(Base);
|
||||
|
||||
let mutations = vdom.rebuild();
|
||||
|
||||
let mut dom: RealDom<StateTester> = RealDom::new();
|
||||
|
||||
let (nodes_updated, _) = dom.apply_mutations(mutations);
|
||||
let mut ctx = SendAnyMap::new();
|
||||
ctx.insert(42u32);
|
||||
let _to_rerender = dom.update_state(nodes_updated, ctx);
|
||||
|
||||
let root_div_id = dom.children_ids(NodeId(0)).unwrap()[0];
|
||||
let root_div = &dom.get(root_div_id).unwrap();
|
||||
assert_eq!(root_div.state.bubbled.0, Some("div".to_string()));
|
||||
assert_eq!(
|
||||
root_div.state.bubbled.1,
|
||||
vec![
|
||||
Box::new(BubbledUpStateTester(Some("p".to_string()), Vec::new())),
|
||||
Box::new(BubbledUpStateTester(Some("h1".to_string()), Vec::new()))
|
||||
]
|
||||
);
|
||||
assert_eq!(root_div.state.pushed.0, Some("div".to_string()));
|
||||
assert_eq!(
|
||||
root_div.state.pushed.1,
|
||||
Some(Box::new(PushedDownStateTester(
|
||||
Some("Root".to_string()),
|
||||
None
|
||||
)))
|
||||
);
|
||||
assert_eq!(root_div.state.node.0, Some("div".to_string()));
|
||||
assert_eq!(root_div.state.node.1, vec![]);
|
||||
|
||||
let child_p_id = dom.children_ids(root_div_id).unwrap()[0];
|
||||
let child_p = &dom[child_p_id];
|
||||
assert_eq!(child_p.state.bubbled.0, Some("p".to_string()));
|
||||
assert_eq!(child_p.state.bubbled.1, Vec::new());
|
||||
assert_eq!(child_p.state.pushed.0, Some("p".to_string()));
|
||||
assert_eq!(
|
||||
child_p.state.pushed.1,
|
||||
Some(Box::new(PushedDownStateTester(
|
||||
Some("div".to_string()),
|
||||
Some(Box::new(PushedDownStateTester(
|
||||
Some("Root".to_string()),
|
||||
None
|
||||
)))
|
||||
)))
|
||||
);
|
||||
assert_eq!(child_p.state.node.0, Some("p".to_string()));
|
||||
assert_eq!(
|
||||
child_p.state.node.1,
|
||||
vec![("color".to_string(), "red".to_string())]
|
||||
);
|
||||
|
||||
let child_h1_id = dom.children_ids(root_div_id).unwrap()[1];
|
||||
let child_h1 = &dom[child_h1_id];
|
||||
assert_eq!(child_h1.state.bubbled.0, Some("h1".to_string()));
|
||||
assert_eq!(child_h1.state.bubbled.1, Vec::new());
|
||||
assert_eq!(child_h1.state.pushed.0, Some("h1".to_string()));
|
||||
assert_eq!(
|
||||
child_h1.state.pushed.1,
|
||||
Some(Box::new(PushedDownStateTester(
|
||||
Some("div".to_string()),
|
||||
Some(Box::new(PushedDownStateTester(
|
||||
Some("Root".to_string()),
|
||||
None
|
||||
)))
|
||||
)))
|
||||
);
|
||||
assert_eq!(child_h1.state.node.0, Some("h1".to_string()));
|
||||
assert_eq!(child_h1.state.node.1, vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_reduce_parent_called_minimally_on_update() {
|
||||
#[allow(non_snake_case)]
|
||||
fn Base(cx: Scope) -> Element {
|
||||
let width = if cx.generation() == 0 { "100%" } else { "99%" };
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
width: "{width}",
|
||||
div{
|
||||
div{
|
||||
p{}
|
||||
}
|
||||
p{
|
||||
"hello"
|
||||
}
|
||||
div{
|
||||
h1{}
|
||||
}
|
||||
p{
|
||||
"world"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let mut vdom = VirtualDom::new(Base);
|
||||
|
||||
let mut dom: RealDom<CallCounterState> = RealDom::new();
|
||||
|
||||
let (nodes_updated, _) = dom.apply_mutations(vdom.rebuild());
|
||||
let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::new());
|
||||
vdom.mark_dirty(ScopeId(0));
|
||||
let (nodes_updated, _) = dom.apply_mutations(vdom.render_immediate());
|
||||
let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::new());
|
||||
|
||||
let mut is_root = true;
|
||||
dom.traverse_depth_first(|n| {
|
||||
if is_root {
|
||||
is_root = false;
|
||||
assert_eq!(n.state.parent_counter.0, 1);
|
||||
} else {
|
||||
assert_eq!(n.state.parent_counter.0, 2);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_reduce_child_called_minimally_on_update() {
|
||||
#[allow(non_snake_case)]
|
||||
fn Base(cx: Scope) -> Element {
|
||||
let width = if cx.generation() == 0 { "100%" } else { "99%" };
|
||||
cx.render(rsx! {
|
||||
// updated: 2
|
||||
div {
|
||||
// updated: 2
|
||||
div{
|
||||
// updated: 2
|
||||
div{
|
||||
// updated: 2
|
||||
p{
|
||||
width: "{width}",
|
||||
}
|
||||
}
|
||||
// updated: 1
|
||||
p{
|
||||
// updated: 1
|
||||
"hello"
|
||||
}
|
||||
// updated: 1
|
||||
div{
|
||||
// updated: 1
|
||||
h1{}
|
||||
}
|
||||
// updated: 1
|
||||
p{
|
||||
// updated: 1
|
||||
"world"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let mut vdom = VirtualDom::new(Base);
|
||||
|
||||
let mut dom: RealDom<CallCounterState> = RealDom::new();
|
||||
|
||||
let (nodes_updated, _) = dom.apply_mutations(vdom.rebuild());
|
||||
let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::new());
|
||||
vdom.mark_dirty(ScopeId(0));
|
||||
let (nodes_updated, _) = dom.apply_mutations(vdom.render_immediate());
|
||||
let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::new());
|
||||
|
||||
let mut traverse_count = 0;
|
||||
dom.traverse_depth_first(|n| {
|
||||
assert_eq!(n.state.child_counter.0, {
|
||||
if traverse_count > 4 {
|
||||
1
|
||||
} else {
|
||||
2
|
||||
}
|
||||
});
|
||||
traverse_count += 1;
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, State)]
|
||||
struct UnorderedDependanciesState {
|
||||
#[node_dep_state(c)]
|
||||
b: BDepCallCounter,
|
||||
#[node_dep_state()]
|
||||
c: CDepCallCounter,
|
||||
#[node_dep_state(b)]
|
||||
a: ADepCallCounter,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
struct ADepCallCounter(usize, BDepCallCounter);
|
||||
impl NodeDepState for ADepCallCounter {
|
||||
type Ctx = ();
|
||||
type DepState = (BDepCallCounter,);
|
||||
const NODE_MASK: NodeMask = NodeMask::NONE;
|
||||
fn reduce(
|
||||
&mut self,
|
||||
_node: NodeView,
|
||||
(sibling,): (&BDepCallCounter,),
|
||||
_ctx: &Self::Ctx,
|
||||
) -> bool {
|
||||
self.0 += 1;
|
||||
self.1 = sibling.clone();
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
struct BDepCallCounter(usize, CDepCallCounter);
|
||||
impl NodeDepState for BDepCallCounter {
|
||||
type Ctx = ();
|
||||
type DepState = (CDepCallCounter,);
|
||||
const NODE_MASK: NodeMask = NodeMask::NONE;
|
||||
fn reduce(
|
||||
&mut self,
|
||||
_node: NodeView,
|
||||
(sibling,): (&CDepCallCounter,),
|
||||
_ctx: &Self::Ctx,
|
||||
) -> bool {
|
||||
self.0 += 1;
|
||||
self.1 = sibling.clone();
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
struct CDepCallCounter(usize);
|
||||
impl NodeDepState for CDepCallCounter {
|
||||
type Ctx = ();
|
||||
type DepState = ();
|
||||
const NODE_MASK: NodeMask = NodeMask::ALL;
|
||||
fn reduce(&mut self, _node: NodeView, _sibling: (), _ctx: &Self::Ctx) -> bool {
|
||||
self.0 += 1;
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dependancies_order_independant() {
|
||||
#[allow(non_snake_case)]
|
||||
fn Base(cx: Scope) -> Element {
|
||||
render!(div {
|
||||
width: "100%",
|
||||
p{
|
||||
"hello"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let mut vdom = VirtualDom::new(Base);
|
||||
|
||||
let mut dom: RealDom<UnorderedDependanciesState> = RealDom::new();
|
||||
|
||||
let mutations = vdom.rebuild();
|
||||
let (nodes_updated, _) = dom.apply_mutations(mutations);
|
||||
let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::new());
|
||||
|
||||
let c = CDepCallCounter(1);
|
||||
let b = BDepCallCounter(1, c.clone());
|
||||
let a = ADepCallCounter(1, b.clone());
|
||||
dom.traverse_depth_first(|n| {
|
||||
assert_eq!(&n.state.a, &a);
|
||||
assert_eq!(&n.state.b, &b);
|
||||
assert_eq!(&n.state.c, &c);
|
||||
});
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "dioxus-native-core"
|
||||
version = "0.3.0"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
license = "MIT/Apache-2.0"
|
||||
repository = "https://github.com/DioxusLabs/dioxus/"
|
||||
|
@ -11,24 +11,34 @@ keywords = ["dom", "ui", "gui", "react"]
|
|||
|
||||
|
||||
[dependencies]
|
||||
dioxus-core = { path = "../core", version = "^0.3.0" }
|
||||
dioxus-html = { path = "../html", version = "^0.3.0" }
|
||||
dioxus-core-macro = { path = "../core-macro", version = "^0.3.0" }
|
||||
dioxus-core = { path = "../core", version = "^0.3.0", optional = true }
|
||||
|
||||
keyboard-types = "0.6.2"
|
||||
taffy = "0.2.1"
|
||||
smallvec = "1.6"
|
||||
rustc-hash = "1.1.0"
|
||||
anymap = "1.0.0-beta.2"
|
||||
slab = "0.4"
|
||||
parking_lot = "0.12.1"
|
||||
parking_lot = { version = "0.12.1", features = ["send_guard"] }
|
||||
crossbeam-deque = "0.8.2"
|
||||
dashmap = "5.4.0"
|
||||
hashbrown = { version = "0.13.2", features = ["raw"] }
|
||||
|
||||
# for parsing attributes
|
||||
lightningcss = "1.0.0-alpha.39"
|
||||
|
||||
rayon = "1.6.1"
|
||||
shipyard = { version = "0.6.2", features = ["proc", "std"], default-features = false }
|
||||
shipyard_hierarchy = "0.6.0"
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8.5"
|
||||
dioxus = { path = "../dioxus", version = "^0.3.0" }
|
||||
tokio = { version = "*", features = ["full"] }
|
||||
dioxus-native-core = { path = ".", features = ["dioxus"] }
|
||||
dioxus-native-core-macro = { path = "../native-core-macro" }
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
dioxus = ["dioxus-core"]
|
||||
parallel = ["shipyard/parallel"]
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
[discord-url]: https://discord.gg/XgGxMSkvUM
|
||||
|
||||
[Website](https://dioxuslabs.com) |
|
||||
[Guides](https://dioxuslabs.com/docs/0.3/guide/en/) |
|
||||
[Guides](https://dioxuslabs.com/guide/) |
|
||||
[API Docs](https://docs.rs/dioxus-native-core/latest/dioxus_native_core) |
|
||||
[Chat](https://discord.gg/XgGxMSkvUM)
|
||||
|
||||
|
|
231
packages/native-core/examples/custom_attr.rs
Normal file
231
packages/native-core/examples/custom_attr.rs
Normal file
|
@ -0,0 +1,231 @@
|
|||
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;
|
||||
|
||||
struct FontSize(f64);
|
||||
|
||||
// All states need to derive Component
|
||||
#[derive(Default, Debug, Copy, Clone, Component)]
|
||||
struct Size(f64, f64);
|
||||
|
||||
#[derive(Default, Debug, Copy, Clone, Component)]
|
||||
struct X;
|
||||
|
||||
impl FromAnyValue for X {
|
||||
fn from_any_value(_: &dyn std::any::Any) -> Self {
|
||||
X
|
||||
}
|
||||
}
|
||||
|
||||
/// Derive some of the boilerplate for the State implementation
|
||||
#[partial_derive_state]
|
||||
impl State<X> for Size {
|
||||
type ParentDependencies = ();
|
||||
|
||||
// The size of the current node depends on the size of its children
|
||||
type ChildDependencies = (Self,);
|
||||
|
||||
type NodeDependencies = ();
|
||||
|
||||
// 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<X>,
|
||||
_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 font_size = context.get::<FontSize>().unwrap().0;
|
||||
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, Default, Component)]
|
||||
struct TextColor {
|
||||
r: u8,
|
||||
g: u8,
|
||||
b: u8,
|
||||
}
|
||||
|
||||
#[partial_derive_state]
|
||||
impl State<X> for TextColor {
|
||||
// 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(&["color"]));
|
||||
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
node_view: NodeView<X>,
|
||||
_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 {
|
||||
// TextColor only depends on the color tag, so getting the first tag is equivilent to looking through all tags
|
||||
let new = match node_view
|
||||
.attributes()
|
||||
.and_then(|mut attrs| attrs.next())
|
||||
.and_then(|attr| attr.value.as_text())
|
||||
{
|
||||
// if there is a color tag, translate it
|
||||
Some("red") => TextColor { r: 255, g: 0, b: 0 },
|
||||
Some("green") => TextColor { r: 0, g: 255, b: 0 },
|
||||
Some("blue") => TextColor { r: 0, g: 0, b: 255 },
|
||||
Some(color) => panic!("unknown color {color}"),
|
||||
// otherwise check if the node has a parent and inherit that color
|
||||
None => match parent {
|
||||
Some((parent,)) => *parent,
|
||||
None => Self::default(),
|
||||
},
|
||||
};
|
||||
// check if the member has changed
|
||||
let changed = new != *self;
|
||||
*self = new;
|
||||
changed
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default, Component)]
|
||||
struct Border(bool);
|
||||
|
||||
#[partial_derive_state]
|
||||
impl State<X> for Border {
|
||||
// TextColor depends on the TextColor part of the parent
|
||||
type ParentDependencies = (Self,);
|
||||
|
||||
type ChildDependencies = ();
|
||||
|
||||
type NodeDependencies = ();
|
||||
|
||||
// Border does not depended on any other member in the current node
|
||||
const NODE_MASK: NodeMaskBuilder<'static> =
|
||||
// Get access to the border attribute
|
||||
NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["border"]));
|
||||
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
node_view: NodeView<X>,
|
||||
_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 {
|
||||
// check if the node contians a border attribute
|
||||
let new = Self(
|
||||
node_view
|
||||
.attributes()
|
||||
.and_then(|mut attrs| attrs.next().map(|a| a.attribute.name == "border"))
|
||||
.is_some(),
|
||||
);
|
||||
// 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<X> = RealDom::new([
|
||||
Border::to_type_erased(),
|
||||
TextColor::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());
|
||||
}
|
||||
root.add_child(text_id);
|
||||
|
||||
let mut ctx = SendAnyMap::new();
|
||||
// set the font size to 3.3
|
||||
ctx.insert(FontSize(3.3));
|
||||
// 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
|
||||
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}");
|
||||
}
|
||||
|
||||
let mut ctx = SendAnyMap::new();
|
||||
ctx.insert(FontSize(3.3));
|
||||
let _to_rerender = rdom.update_state(ctx);
|
||||
|
||||
// render...
|
||||
rdom.traverse_depth_first(|node| {
|
||||
let indent = " ".repeat(node.height() as usize);
|
||||
let color = *node.get::<TextColor>().unwrap();
|
||||
let size = *node.get::<Size>().unwrap();
|
||||
let border = *node.get::<Border>().unwrap();
|
||||
let id = node.id();
|
||||
println!("{indent}{id:?} {color:?} {size:?} {border:?}");
|
||||
});
|
||||
|
||||
// wait 1 second
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
}
|
||||
})
|
||||
}
|
222
packages/native-core/examples/simple.rs
Normal file
222
packages/native-core/examples/simple.rs
Normal file
|
@ -0,0 +1,222 @@
|
|||
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;
|
||||
|
||||
struct FontSize(f64);
|
||||
|
||||
// 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 = ();
|
||||
|
||||
// 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<()>,
|
||||
_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 font_size = context.get::<FontSize>().unwrap().0;
|
||||
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, Default, Component)]
|
||||
struct TextColor {
|
||||
r: u8,
|
||||
g: u8,
|
||||
b: u8,
|
||||
}
|
||||
|
||||
#[partial_derive_state]
|
||||
impl State for TextColor {
|
||||
// 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(&["color"]));
|
||||
|
||||
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 {
|
||||
// TextColor only depends on the color tag, so getting the first tag is equivilent to looking through all tags
|
||||
let new = match node_view
|
||||
.attributes()
|
||||
.and_then(|mut attrs| attrs.next())
|
||||
.and_then(|attr| attr.value.as_text())
|
||||
{
|
||||
// if there is a color tag, translate it
|
||||
Some("red") => TextColor { r: 255, g: 0, b: 0 },
|
||||
Some("green") => TextColor { r: 0, g: 255, b: 0 },
|
||||
Some("blue") => TextColor { r: 0, g: 0, b: 255 },
|
||||
Some(color) => panic!("unknown color {color}"),
|
||||
// otherwise check if the node has a parent and inherit that color
|
||||
None => match parent {
|
||||
Some((parent,)) => *parent,
|
||||
None => Self::default(),
|
||||
},
|
||||
};
|
||||
// check if the member has changed
|
||||
let changed = new != *self;
|
||||
*self = new;
|
||||
changed
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default, Component)]
|
||||
struct Border(bool);
|
||||
|
||||
#[partial_derive_state]
|
||||
impl State for Border {
|
||||
// TextColor depends on the TextColor part of the parent
|
||||
type ParentDependencies = (Self,);
|
||||
|
||||
type ChildDependencies = ();
|
||||
|
||||
type NodeDependencies = ();
|
||||
|
||||
// Border does not depended on any other member in the current node
|
||||
const NODE_MASK: NodeMaskBuilder<'static> =
|
||||
// Get access to the border attribute
|
||||
NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["border"]));
|
||||
|
||||
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 {
|
||||
// check if the node contians a border attribute
|
||||
let new = Self(
|
||||
node_view
|
||||
.attributes()
|
||||
.and_then(|mut attrs| attrs.next().map(|a| a.attribute.name == "border"))
|
||||
.is_some(),
|
||||
);
|
||||
// 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([
|
||||
Border::to_type_erased(),
|
||||
TextColor::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());
|
||||
}
|
||||
root.add_child(text_id);
|
||||
|
||||
let mut ctx = SendAnyMap::new();
|
||||
// set the font size to 3.3
|
||||
ctx.insert(FontSize(3.3));
|
||||
// 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
|
||||
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}");
|
||||
}
|
||||
|
||||
let mut ctx = SendAnyMap::new();
|
||||
ctx.insert(FontSize(3.3));
|
||||
let _to_rerender = rdom.update_state(ctx);
|
||||
|
||||
// render...
|
||||
rdom.traverse_depth_first(|node| {
|
||||
let indent = " ".repeat(node.height() as usize);
|
||||
let color = *node.get::<TextColor>().unwrap();
|
||||
let size = *node.get::<Size>().unwrap();
|
||||
let border = *node.get::<Border>().unwrap();
|
||||
let id = node.id();
|
||||
println!("{indent}{id:?} {color:?} {size:?} {border:?}");
|
||||
});
|
||||
|
||||
// wait 1 second
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
}
|
||||
})
|
||||
}
|
249
packages/native-core/examples/simple_dioxus.rs
Normal file
249
packages/native-core/examples/simple_dioxus.rs
Normal file
|
@ -0,0 +1,249 @@
|
|||
#![allow(non_snake_case)]
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_native_core::exports::shipyard::Component;
|
||||
use dioxus_native_core::node_ref::*;
|
||||
use dioxus_native_core::prelude::*;
|
||||
use dioxus_native_core_macro::partial_derive_state;
|
||||
|
||||
struct FontSize(f64);
|
||||
|
||||
// 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 = ();
|
||||
|
||||
// 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<()>,
|
||||
_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 font_size = context.get::<FontSize>().unwrap().0;
|
||||
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, Default, Component)]
|
||||
struct TextColor {
|
||||
r: u8,
|
||||
g: u8,
|
||||
b: u8,
|
||||
}
|
||||
|
||||
#[partial_derive_state]
|
||||
impl State for TextColor {
|
||||
// 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(&["color"]));
|
||||
|
||||
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 {
|
||||
// TextColor only depends on the color tag, so getting the first tag is equivilent to looking through all tags
|
||||
let new = match node_view
|
||||
.attributes()
|
||||
.and_then(|mut attrs| attrs.next())
|
||||
.and_then(|attr| attr.value.as_text())
|
||||
{
|
||||
// if there is a color tag, translate it
|
||||
Some("red") => TextColor { r: 255, g: 0, b: 0 },
|
||||
Some("green") => TextColor { r: 0, g: 255, b: 0 },
|
||||
Some("blue") => TextColor { r: 0, g: 0, b: 255 },
|
||||
Some(color) => panic!("unknown color {color}"),
|
||||
// otherwise check if the node has a parent and inherit that color
|
||||
None => match parent {
|
||||
Some((parent,)) => *parent,
|
||||
None => Self::default(),
|
||||
},
|
||||
};
|
||||
// check if the member has changed
|
||||
let changed = new != *self;
|
||||
*self = new;
|
||||
changed
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default, Component)]
|
||||
struct Border(bool);
|
||||
|
||||
#[partial_derive_state]
|
||||
impl State for Border {
|
||||
// TextColor depends on the TextColor part of the parent
|
||||
type ParentDependencies = (Self,);
|
||||
|
||||
type ChildDependencies = ();
|
||||
|
||||
type NodeDependencies = ();
|
||||
|
||||
// Border does not depended on any other member in the current node
|
||||
const NODE_MASK: NodeMaskBuilder<'static> =
|
||||
// Get access to the border attribute
|
||||
NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["border"]));
|
||||
|
||||
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 {
|
||||
// check if the node contians a border attribute
|
||||
let new = Self(
|
||||
node_view
|
||||
.attributes()
|
||||
.and_then(|mut attrs| attrs.next().map(|a| a.attribute.name == "border"))
|
||||
.is_some(),
|
||||
);
|
||||
// check if the member has changed
|
||||
let changed = new != *self;
|
||||
*self = new;
|
||||
changed
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
fn app(cx: Scope) -> Element {
|
||||
let count = use_state(cx, || 0);
|
||||
|
||||
use_future(cx, (count,), |(count,)| async move {
|
||||
loop {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
count.set(*count + 1);
|
||||
}
|
||||
});
|
||||
|
||||
cx.render(rsx! {
|
||||
div{
|
||||
color: "red",
|
||||
"{count}",
|
||||
Comp {}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn Comp(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div{
|
||||
border: "",
|
||||
"hello world"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// create the vdom, the real_dom, and the binding layer between them
|
||||
let mut vdom = VirtualDom::new(app);
|
||||
let mut rdom: RealDom = RealDom::new([
|
||||
Border::to_type_erased(),
|
||||
TextColor::to_type_erased(),
|
||||
Size::to_type_erased(),
|
||||
]);
|
||||
let mut dioxus_intigration_state = DioxusState::create(&mut rdom);
|
||||
|
||||
let mutations = vdom.rebuild();
|
||||
// update the structure of the real_dom tree
|
||||
dioxus_intigration_state.apply_mutations(&mut rdom, mutations);
|
||||
let mut ctx = SendAnyMap::new();
|
||||
// set the font size to 3.3
|
||||
ctx.insert(FontSize(3.3));
|
||||
// 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 {
|
||||
// wait for the vdom to update
|
||||
vdom.wait_for_work().await;
|
||||
|
||||
// get the mutations from the vdom
|
||||
let mutations = vdom.render_immediate();
|
||||
|
||||
// update the structure of the real_dom tree
|
||||
dioxus_intigration_state.apply_mutations(&mut rdom, mutations);
|
||||
|
||||
// update the state of the real_dom tree
|
||||
let mut ctx = SendAnyMap::new();
|
||||
// set the font size to 3.3
|
||||
ctx.insert(FontSize(3.3));
|
||||
let _to_rerender = rdom.update_state(ctx);
|
||||
|
||||
// render...
|
||||
rdom.traverse_depth_first(|node| {
|
||||
let indent = " ".repeat(node.height() as usize);
|
||||
let color = *node.get::<TextColor>().unwrap();
|
||||
let size = *node.get::<Size>().unwrap();
|
||||
let border = *node.get::<Border>().unwrap();
|
||||
let id = node.id();
|
||||
let node = node.node_type();
|
||||
let node_type = &*node;
|
||||
println!("{indent}{id:?} {color:?} {size:?} {border:?} {node_type:?}");
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
296
packages/native-core/src/dioxus.rs
Normal file
296
packages/native-core/src/dioxus.rs
Normal file
|
@ -0,0 +1,296 @@
|
|||
//! Integration between Dioxus and the RealDom
|
||||
|
||||
use crate::tree::TreeMut;
|
||||
use dioxus_core::{BorrowedAttributeValue, ElementId, Mutations, TemplateNode};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use shipyard::Component;
|
||||
|
||||
use crate::{
|
||||
node::{
|
||||
ElementNode, FromAnyValue, NodeType, OwnedAttributeDiscription, OwnedAttributeValue,
|
||||
TextNode,
|
||||
},
|
||||
prelude::*,
|
||||
real_dom::NodeTypeMut,
|
||||
NodeId,
|
||||
};
|
||||
|
||||
#[derive(Component)]
|
||||
struct ElementIdComponent(ElementId);
|
||||
|
||||
/// The state of the Dioxus integration with the RealDom
|
||||
pub struct DioxusState {
|
||||
templates: FxHashMap<String, Vec<NodeId>>,
|
||||
stack: Vec<NodeId>,
|
||||
node_id_mapping: Vec<Option<NodeId>>,
|
||||
}
|
||||
|
||||
impl DioxusState {
|
||||
/// Initialize the DioxusState in the RealDom
|
||||
pub fn create<V: FromAnyValue + Send + Sync>(rdom: &mut RealDom<V>) -> Self {
|
||||
let root_id = rdom.root_id();
|
||||
let mut root = rdom.get_mut(root_id).unwrap();
|
||||
root.insert(ElementIdComponent(ElementId(0)));
|
||||
Self {
|
||||
templates: FxHashMap::default(),
|
||||
stack: vec![root_id],
|
||||
node_id_mapping: vec![Some(root_id)],
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert an ElementId to a NodeId
|
||||
pub fn element_to_node_id(&self, element_id: ElementId) -> NodeId {
|
||||
self.try_element_to_node_id(element_id).unwrap()
|
||||
}
|
||||
|
||||
/// Attempt to convert an ElementId to a NodeId. This will return None if the ElementId is not in the RealDom.
|
||||
pub fn try_element_to_node_id(&self, element_id: ElementId) -> Option<NodeId> {
|
||||
self.node_id_mapping.get(element_id.0).copied().flatten()
|
||||
}
|
||||
|
||||
fn set_element_id<V: FromAnyValue + Send + Sync>(
|
||||
&mut self,
|
||||
mut node: NodeMut<V>,
|
||||
element_id: ElementId,
|
||||
) {
|
||||
let node_id = node.id();
|
||||
node.insert(ElementIdComponent(element_id));
|
||||
if self.node_id_mapping.len() <= element_id.0 {
|
||||
self.node_id_mapping.resize(element_id.0 + 1, None);
|
||||
}
|
||||
self.node_id_mapping[element_id.0] = Some(node_id);
|
||||
}
|
||||
|
||||
fn load_child<V: FromAnyValue + Send + Sync>(&self, rdom: &RealDom<V>, path: &[u8]) -> NodeId {
|
||||
let mut current = rdom.get(*self.stack.last().unwrap()).unwrap();
|
||||
for i in path {
|
||||
let new_id = current.child_ids()[*i as usize];
|
||||
current = rdom.get(new_id).unwrap();
|
||||
}
|
||||
current.id()
|
||||
}
|
||||
|
||||
/// 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<V: FromAnyValue + Send + Sync>(
|
||||
&mut self,
|
||||
rdom: &mut RealDom<V>,
|
||||
mutations: Mutations,
|
||||
) {
|
||||
for template in mutations.templates {
|
||||
let mut template_root_ids = Vec::new();
|
||||
for root in template.roots {
|
||||
let id = create_template_node(rdom, root);
|
||||
template_root_ids.push(id);
|
||||
}
|
||||
self.templates
|
||||
.insert(template.name.to_string(), template_root_ids);
|
||||
}
|
||||
|
||||
for e in mutations.edits {
|
||||
use dioxus_core::Mutation::*;
|
||||
match e {
|
||||
AppendChildren { id, m } => {
|
||||
let children = self.stack.split_off(self.stack.len() - m);
|
||||
let parent = self.element_to_node_id(id);
|
||||
for child in children {
|
||||
rdom.get_mut(parent).unwrap().add_child(child);
|
||||
}
|
||||
}
|
||||
AssignId { path, id } => {
|
||||
let node_id = self.load_child(rdom, path);
|
||||
self.set_element_id(rdom.get_mut(node_id).unwrap(), id);
|
||||
}
|
||||
CreatePlaceholder { id } => {
|
||||
let node = NodeType::Placeholder;
|
||||
let node = rdom.create_node(node);
|
||||
let node_id = node.id();
|
||||
self.set_element_id(node, id);
|
||||
self.stack.push(node_id);
|
||||
}
|
||||
CreateTextNode { value, id } => {
|
||||
let node_data = NodeType::Text(TextNode {
|
||||
listeners: FxHashSet::default(),
|
||||
text: value.to_string(),
|
||||
});
|
||||
let node = rdom.create_node(node_data);
|
||||
let node_id = node.id();
|
||||
self.set_element_id(node, id);
|
||||
self.stack.push(node_id);
|
||||
}
|
||||
HydrateText { path, value, id } => {
|
||||
let node_id = self.load_child(rdom, path);
|
||||
let node = rdom.get_mut(node_id).unwrap();
|
||||
self.set_element_id(node, id);
|
||||
let mut node = rdom.get_mut(node_id).unwrap();
|
||||
let node_type_mut = node.node_type_mut();
|
||||
if let NodeTypeMut::Text(mut text) = node_type_mut {
|
||||
*text.text_mut() = value.to_string();
|
||||
} else {
|
||||
drop(node_type_mut);
|
||||
node.set_type(NodeType::Text(TextNode {
|
||||
text: value.to_string(),
|
||||
listeners: FxHashSet::default(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
LoadTemplate { name, index, id } => {
|
||||
let template_id = self.templates[name][index];
|
||||
let clone_id = rdom.get_mut(template_id).unwrap().clone_node();
|
||||
let clone = rdom.get_mut(clone_id).unwrap();
|
||||
self.set_element_id(clone, id);
|
||||
self.stack.push(clone_id);
|
||||
}
|
||||
ReplaceWith { id, m } => {
|
||||
let new_nodes = self.stack.split_off(self.stack.len() - m);
|
||||
let old_node_id = self.element_to_node_id(id);
|
||||
for new in new_nodes {
|
||||
let mut node = rdom.get_mut(new).unwrap();
|
||||
node.insert_before(old_node_id);
|
||||
}
|
||||
rdom.get_mut(old_node_id).unwrap().remove();
|
||||
}
|
||||
ReplacePlaceholder { path, m } => {
|
||||
let new_nodes = self.stack.split_off(self.stack.len() - m);
|
||||
let old_node_id = self.load_child(rdom, path);
|
||||
for new in new_nodes {
|
||||
let mut node = rdom.get_mut(new).unwrap();
|
||||
node.insert_before(old_node_id);
|
||||
}
|
||||
rdom.get_mut(old_node_id).unwrap().remove();
|
||||
}
|
||||
InsertAfter { id, m } => {
|
||||
let new_nodes = self.stack.split_off(self.stack.len() - m);
|
||||
let old_node_id = self.element_to_node_id(id);
|
||||
for new in new_nodes.into_iter().rev() {
|
||||
let mut node = rdom.get_mut(new).unwrap();
|
||||
node.insert_after(old_node_id);
|
||||
}
|
||||
}
|
||||
InsertBefore { id, m } => {
|
||||
let new_nodes = self.stack.split_off(self.stack.len() - m);
|
||||
let old_node_id = self.element_to_node_id(id);
|
||||
for new in new_nodes {
|
||||
rdom.tree_mut().insert_before(old_node_id, new);
|
||||
}
|
||||
}
|
||||
SetAttribute {
|
||||
name,
|
||||
value,
|
||||
id,
|
||||
ns,
|
||||
} => {
|
||||
let node_id = self.element_to_node_id(id);
|
||||
let mut node = rdom.get_mut(node_id).unwrap();
|
||||
let mut node_type_mut = node.node_type_mut();
|
||||
if let NodeTypeMut::Element(element) = &mut node_type_mut {
|
||||
if let BorrowedAttributeValue::None = &value {
|
||||
element.remove_attribute(&OwnedAttributeDiscription {
|
||||
name: name.to_string(),
|
||||
namespace: ns.map(|s| s.to_string()),
|
||||
});
|
||||
} else {
|
||||
element.set_attribute(
|
||||
OwnedAttributeDiscription {
|
||||
name: name.to_string(),
|
||||
namespace: ns.map(|s| s.to_string()),
|
||||
},
|
||||
OwnedAttributeValue::from(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
SetText { value, id } => {
|
||||
let node_id = self.element_to_node_id(id);
|
||||
let mut node = rdom.get_mut(node_id).unwrap();
|
||||
let node_type_mut = node.node_type_mut();
|
||||
if let NodeTypeMut::Text(mut text) = node_type_mut {
|
||||
*text.text_mut() = value.to_string();
|
||||
}
|
||||
}
|
||||
NewEventListener { name, id } => {
|
||||
let node_id = self.element_to_node_id(id);
|
||||
let mut node = rdom.get_mut(node_id).unwrap();
|
||||
node.add_event_listener(name);
|
||||
}
|
||||
RemoveEventListener { id, name } => {
|
||||
let node_id = self.element_to_node_id(id);
|
||||
let mut node = rdom.get_mut(node_id).unwrap();
|
||||
node.remove_event_listener(name);
|
||||
}
|
||||
Remove { id } => {
|
||||
let node_id = self.element_to_node_id(id);
|
||||
rdom.get_mut(node_id).unwrap().remove();
|
||||
}
|
||||
PushRoot { id } => {
|
||||
let node_id = self.element_to_node_id(id);
|
||||
self.stack.push(node_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_template_node<V: FromAnyValue + Send + Sync>(
|
||||
rdom: &mut RealDom<V>,
|
||||
node: &TemplateNode,
|
||||
) -> NodeId {
|
||||
match node {
|
||||
TemplateNode::Element {
|
||||
tag,
|
||||
namespace,
|
||||
attrs,
|
||||
children,
|
||||
} => {
|
||||
let node = NodeType::Element(ElementNode {
|
||||
tag: tag.to_string(),
|
||||
namespace: namespace.map(|s| s.to_string()),
|
||||
attributes: attrs
|
||||
.iter()
|
||||
.filter_map(|attr| match attr {
|
||||
dioxus_core::TemplateAttribute::Static {
|
||||
name,
|
||||
value,
|
||||
namespace,
|
||||
} => Some((
|
||||
OwnedAttributeDiscription {
|
||||
namespace: namespace.map(|s| s.to_string()),
|
||||
name: name.to_string(),
|
||||
},
|
||||
OwnedAttributeValue::Text(value.to_string()),
|
||||
)),
|
||||
dioxus_core::TemplateAttribute::Dynamic { .. } => None,
|
||||
})
|
||||
.collect(),
|
||||
listeners: FxHashSet::default(),
|
||||
});
|
||||
let node_id = rdom.create_node(node).id();
|
||||
for child in *children {
|
||||
let child_id = create_template_node(rdom, child);
|
||||
rdom.get_mut(node_id).unwrap().add_child(child_id);
|
||||
}
|
||||
node_id
|
||||
}
|
||||
TemplateNode::Text { text } => rdom
|
||||
.create_node(NodeType::Text(TextNode {
|
||||
text: text.to_string(),
|
||||
..Default::default()
|
||||
}))
|
||||
.id(),
|
||||
TemplateNode::Dynamic { .. } => rdom.create_node(NodeType::Placeholder).id(),
|
||||
TemplateNode::DynamicText { .. } => {
|
||||
rdom.create_node(NodeType::Text(TextNode::default())).id()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait that extends the `NodeImmutable` trait with methods that are useful for dioxus.
|
||||
pub trait NodeImmutableDioxusExt<V: FromAnyValue + Send + Sync>: NodeImmutable<V> {
|
||||
/// Returns the id of the element that this node is mounted to.
|
||||
/// Not all nodes are mounted to an element, only nodes with dynamic content that have been renderered will have an id.
|
||||
fn mounted_id(&self) -> Option<ElementId> {
|
||||
let id = self.get::<ElementIdComponent>();
|
||||
id.map(|id| id.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: NodeImmutable<V>, V: FromAnyValue + Send + Sync> NodeImmutableDioxusExt<V> for T {}
|
|
@ -1,3 +1,5 @@
|
|||
//! Utility functions for applying layout attributes to taffy layout
|
||||
|
||||
/*
|
||||
- [ ] pub display: Display, ----> taffy doesnt support all display types
|
||||
- [x] pub position_type: PositionType, --> taffy doesnt support everything
|
||||
|
@ -46,12 +48,14 @@ use taffy::{
|
|||
style::{FlexDirection, PositionType},
|
||||
};
|
||||
|
||||
/// Default values for layout attributes
|
||||
#[derive(Default)]
|
||||
pub struct LayoutConfigeration {
|
||||
/// the default border widths to use
|
||||
pub border_widths: BorderWidths,
|
||||
}
|
||||
|
||||
/// Default border widths
|
||||
pub struct BorderWidths {
|
||||
/// the default border width to use for thin borders
|
||||
pub thin: f32,
|
||||
|
|
|
@ -1,51 +1,49 @@
|
|||
#![doc = include_str!("../README.md")]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use std::any::Any;
|
||||
use std::hash::BuildHasherDefault;
|
||||
|
||||
pub use node_ref::NodeMask;
|
||||
pub use passes::{
|
||||
AnyPass, DownwardPass, MemberMask, NodePass, Pass, PassId, PassReturn, UpwardPass,
|
||||
};
|
||||
use node_ref::NodeMask;
|
||||
use rustc_hash::FxHasher;
|
||||
pub use tree::NodeId;
|
||||
|
||||
#[cfg(feature = "dioxus")]
|
||||
pub mod dioxus;
|
||||
pub mod layout_attributes;
|
||||
pub mod node;
|
||||
pub mod node_ref;
|
||||
pub mod passes;
|
||||
pub mod node_watcher;
|
||||
mod passes;
|
||||
pub mod real_dom;
|
||||
pub mod state;
|
||||
pub mod tree;
|
||||
pub mod utils;
|
||||
pub use shipyard::EntityId as NodeId;
|
||||
|
||||
/// A id for a node that lives in the real dom.
|
||||
pub type RealNodeId = NodeId;
|
||||
pub mod exports {
|
||||
//! Important dependencies that are used by the rest of the library
|
||||
//! Feel free to just add the dependencies in your own Crates.toml
|
||||
// exported for the macro
|
||||
#[doc(hidden)]
|
||||
pub use rustc_hash::FxHashSet;
|
||||
pub use shipyard;
|
||||
}
|
||||
|
||||
/// A prelude of commonly used items
|
||||
pub mod prelude {
|
||||
#[cfg(feature = "dioxus")]
|
||||
pub use crate::dioxus::*;
|
||||
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::real_dom::{NodeImmutable, NodeMut, NodeRef, RealDom};
|
||||
pub use crate::NodeId;
|
||||
pub use crate::SendAnyMap;
|
||||
}
|
||||
|
||||
/// A map that can be sent between threads
|
||||
pub type FxDashMap<K, V> = dashmap::DashMap<K, V, BuildHasherDefault<FxHasher>>;
|
||||
/// A set that can be sent between threads
|
||||
pub type FxDashSet<K> = dashmap::DashSet<K, BuildHasherDefault<FxHasher>>;
|
||||
/// A map of types that can be sent between threads
|
||||
pub type SendAnyMap = anymap::Map<dyn Any + Send + Sync + 'static>;
|
||||
|
||||
/// Used in derived state macros
|
||||
#[derive(Eq, PartialEq)]
|
||||
#[doc(hidden)]
|
||||
pub struct HeightOrdering {
|
||||
pub height: u16,
|
||||
pub id: RealNodeId,
|
||||
}
|
||||
|
||||
impl HeightOrdering {
|
||||
pub fn new(height: u16, id: RealNodeId) -> Self {
|
||||
HeightOrdering { height, id }
|
||||
}
|
||||
}
|
||||
|
||||
// not the ordering after height is just for deduplication it can be any ordering as long as it is consistent
|
||||
impl Ord for HeightOrdering {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.height.cmp(&other.height).then(self.id.cmp(&other.id))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for HeightOrdering {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,65 +1,113 @@
|
|||
use crate::{state::State, tree::NodeId};
|
||||
use dioxus_core::{AnyValue, BorrowedAttributeValue, ElementId};
|
||||
//! Items related to Nodes in the RealDom
|
||||
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use std::fmt::Debug;
|
||||
use shipyard::Component;
|
||||
use std::{
|
||||
any::Any,
|
||||
fmt::{Debug, Display},
|
||||
};
|
||||
|
||||
/// The node is stored client side and stores only basic data about the node.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Node<S: State<V>, V: FromAnyValue + 'static = ()> {
|
||||
/// The transformed state of the node.
|
||||
pub state: S,
|
||||
/// The raw data for the node
|
||||
pub node_data: NodeData<V>,
|
||||
/// A element node in the RealDom
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ElementNode<V: FromAnyValue = ()> {
|
||||
/// The [tag](https://developer.mozilla.org/en-US/docs/Web/API/Element/tagName) of the element
|
||||
pub tag: String,
|
||||
/// The [namespace](https://developer.mozilla.org/en-US/docs/Web/API/Element/namespaceURI) of the element
|
||||
pub namespace: Option<String>,
|
||||
/// The attributes of the element
|
||||
pub attributes: FxHashMap<OwnedAttributeDiscription, OwnedAttributeValue<V>>,
|
||||
/// The events the element is listening for
|
||||
pub listeners: FxHashSet<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NodeData<V: FromAnyValue = ()> {
|
||||
/// The id of the node
|
||||
pub node_id: NodeId,
|
||||
/// The id of the node in the vdom.
|
||||
pub element_id: Option<ElementId>,
|
||||
/// Additional inforation specific to the node type
|
||||
pub node_type: NodeType<V>,
|
||||
impl ElementNode {
|
||||
/// Create a new element node
|
||||
pub fn new(tag: impl Into<String>, namespace: impl Into<Option<String>>) -> Self {
|
||||
Self {
|
||||
tag: tag.into(),
|
||||
namespace: namespace.into(),
|
||||
attributes: Default::default(),
|
||||
listeners: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A type of node with data specific to the node type. The types are a subset of the [VNode] types.
|
||||
#[derive(Debug, Clone)]
|
||||
/// A text node in the RealDom
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct TextNode {
|
||||
/// The text of the node
|
||||
pub text: String,
|
||||
/// The events the node is listening for
|
||||
pub listeners: FxHashSet<String>,
|
||||
}
|
||||
|
||||
impl TextNode {
|
||||
/// Create a new text node
|
||||
pub fn new(text: String) -> Self {
|
||||
Self {
|
||||
text,
|
||||
listeners: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A type of node with data specific to the node type.
|
||||
#[derive(Debug, Clone, Component)]
|
||||
pub enum NodeType<V: FromAnyValue = ()> {
|
||||
Text {
|
||||
text: String,
|
||||
},
|
||||
Element {
|
||||
tag: String,
|
||||
namespace: Option<String>,
|
||||
attributes: FxHashMap<OwnedAttributeDiscription, OwnedAttributeValue<V>>,
|
||||
listeners: FxHashSet<String>,
|
||||
},
|
||||
/// A text node
|
||||
Text(TextNode),
|
||||
/// An element node
|
||||
Element(ElementNode<V>),
|
||||
/// A placeholder node. This can be used as a cheaper placeholder for a node that will be created later
|
||||
Placeholder,
|
||||
}
|
||||
|
||||
impl<S: State<V>, V: FromAnyValue> Node<S, V> {
|
||||
pub(crate) fn new(node_type: NodeType<V>) -> Self {
|
||||
Node {
|
||||
state: S::default(),
|
||||
node_data: NodeData {
|
||||
element_id: None,
|
||||
node_type,
|
||||
node_id: NodeId(0),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// get the mounted id of the node
|
||||
pub fn mounted_id(&self) -> Option<ElementId> {
|
||||
self.node_data.element_id
|
||||
impl<V: FromAnyValue, S: Into<String>> From<S> for NodeType<V> {
|
||||
fn from(text: S) -> Self {
|
||||
Self::Text(TextNode::new(text.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: FromAnyValue> From<TextNode> for NodeType<V> {
|
||||
fn from(text: TextNode) -> Self {
|
||||
Self::Text(text)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: FromAnyValue> From<ElementNode<V>> for NodeType<V> {
|
||||
fn from(element: ElementNode<V>) -> Self {
|
||||
Self::Element(element)
|
||||
}
|
||||
}
|
||||
|
||||
/// A discription of an attribute on a DOM node, such as `id` or `href`.
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub struct OwnedAttributeDiscription {
|
||||
/// The name of the attribute.
|
||||
pub name: String,
|
||||
/// The namespace of the attribute used to identify what kind of attribute it is.
|
||||
///
|
||||
/// For renderers that use HTML, this can be used to identify if the attribute is a style attribute.
|
||||
/// Instead of parsing the style attribute every time a style is changed, you can set an attribute with the `style` namespace.
|
||||
pub namespace: Option<String>,
|
||||
pub volatile: bool,
|
||||
}
|
||||
|
||||
impl From<String> for OwnedAttributeDiscription {
|
||||
fn from(name: String) -> Self {
|
||||
Self {
|
||||
name,
|
||||
namespace: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Into<String>, N: Into<String>> From<(S, N)> for OwnedAttributeDiscription {
|
||||
fn from(name: (S, N)) -> Self {
|
||||
Self {
|
||||
name: name.0.into(),
|
||||
namespace: Some(name.1.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An attribute on a DOM node, such as `id="my-thing"` or
|
||||
|
@ -73,21 +121,59 @@ pub struct OwnedAttributeView<'a, V: FromAnyValue = ()> {
|
|||
pub value: &'a OwnedAttributeValue<V>,
|
||||
}
|
||||
|
||||
/// The value of an attribute on a DOM node. This contains non-text values to allow users to skip parsing attribute values in some cases.
|
||||
#[derive(Clone)]
|
||||
pub enum OwnedAttributeValue<V: FromAnyValue = ()> {
|
||||
/// A string value. This is the most common type of attribute.
|
||||
Text(String),
|
||||
/// A floating point value.
|
||||
Float(f64),
|
||||
/// An integer value.
|
||||
Int(i64),
|
||||
/// A boolean value.
|
||||
Bool(bool),
|
||||
/// A custom value specific to the renderer
|
||||
Custom(V),
|
||||
}
|
||||
|
||||
pub trait FromAnyValue: Clone {
|
||||
fn from_any_value(value: &dyn AnyValue) -> Self;
|
||||
impl<V: FromAnyValue> From<String> for OwnedAttributeValue<V> {
|
||||
fn from(value: String) -> Self {
|
||||
Self::Text(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: FromAnyValue> From<f64> for OwnedAttributeValue<V> {
|
||||
fn from(value: f64) -> Self {
|
||||
Self::Float(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: FromAnyValue> From<i64> for OwnedAttributeValue<V> {
|
||||
fn from(value: i64) -> Self {
|
||||
Self::Int(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: FromAnyValue> From<bool> for OwnedAttributeValue<V> {
|
||||
fn from(value: bool) -> Self {
|
||||
Self::Bool(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: FromAnyValue> From<V> for OwnedAttributeValue<V> {
|
||||
fn from(value: V) -> Self {
|
||||
Self::Custom(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Something that can be converted from a borrowed [Any] value.
|
||||
pub trait FromAnyValue: Clone + 'static {
|
||||
/// Convert from an [Any] value.
|
||||
fn from_any_value(value: &dyn Any) -> Self;
|
||||
}
|
||||
|
||||
impl FromAnyValue for () {
|
||||
fn from_any_value(_: &dyn AnyValue) -> Self {}
|
||||
fn from_any_value(_: &dyn Any) -> Self {}
|
||||
}
|
||||
|
||||
impl<V: FromAnyValue> Debug for OwnedAttributeValue<V> {
|
||||
|
@ -102,20 +188,34 @@ impl<V: FromAnyValue> Debug for OwnedAttributeValue<V> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<V: FromAnyValue> From<BorrowedAttributeValue<'_>> for OwnedAttributeValue<V> {
|
||||
fn from(value: BorrowedAttributeValue<'_>) -> Self {
|
||||
impl<V: FromAnyValue> Display for OwnedAttributeValue<V> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Text(arg0) => f.write_str(arg0),
|
||||
Self::Float(arg0) => f.write_str(&arg0.to_string()),
|
||||
Self::Int(arg0) => f.write_str(&arg0.to_string()),
|
||||
Self::Bool(arg0) => f.write_str(&arg0.to_string()),
|
||||
Self::Custom(_) => f.write_str("custom"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "dioxus")]
|
||||
impl<V: FromAnyValue> From<dioxus_core::BorrowedAttributeValue<'_>> for OwnedAttributeValue<V> {
|
||||
fn from(value: dioxus_core::BorrowedAttributeValue<'_>) -> Self {
|
||||
match value {
|
||||
BorrowedAttributeValue::Text(text) => Self::Text(text.to_string()),
|
||||
BorrowedAttributeValue::Float(float) => Self::Float(float),
|
||||
BorrowedAttributeValue::Int(int) => Self::Int(int),
|
||||
BorrowedAttributeValue::Bool(bool) => Self::Bool(bool),
|
||||
BorrowedAttributeValue::Any(any) => Self::Custom(V::from_any_value(&*any)),
|
||||
BorrowedAttributeValue::None => panic!("None attribute values result in removing the attribute, not converting it to a None value.")
|
||||
dioxus_core::BorrowedAttributeValue::Text(text) => Self::Text(text.to_string()),
|
||||
dioxus_core::BorrowedAttributeValue::Float(float) => Self::Float(float),
|
||||
dioxus_core::BorrowedAttributeValue::Int(int) => Self::Int(int),
|
||||
dioxus_core::BorrowedAttributeValue::Bool(bool) => Self::Bool(bool),
|
||||
dioxus_core::BorrowedAttributeValue::Any(any) => Self::Custom(V::from_any_value(any.as_any())),
|
||||
dioxus_core::BorrowedAttributeValue::None => panic!("None attribute values result in removing the attribute, not converting it to a None value.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: FromAnyValue> OwnedAttributeValue<V> {
|
||||
/// Attempt to convert the attribute value to a string.
|
||||
pub fn as_text(&self) -> Option<&str> {
|
||||
match self {
|
||||
OwnedAttributeValue::Text(text) => Some(text),
|
||||
|
@ -123,20 +223,25 @@ impl<V: FromAnyValue> OwnedAttributeValue<V> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Attempt to convert the attribute value to a float.
|
||||
pub fn as_float(&self) -> Option<f64> {
|
||||
match self {
|
||||
OwnedAttributeValue::Float(float) => Some(*float),
|
||||
OwnedAttributeValue::Int(int) => Some(*int as f64),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to convert the attribute value to an integer.
|
||||
pub fn as_int(&self) -> Option<i64> {
|
||||
match self {
|
||||
OwnedAttributeValue::Float(float) => Some(*float as i64),
|
||||
OwnedAttributeValue::Int(int) => Some(*int),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to convert the attribute value to a boolean.
|
||||
pub fn as_bool(&self) -> Option<bool> {
|
||||
match self {
|
||||
OwnedAttributeValue::Bool(bool) => Some(*bool),
|
||||
|
@ -144,6 +249,7 @@ impl<V: FromAnyValue> OwnedAttributeValue<V> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Attempt to convert the attribute value to a custom value.
|
||||
pub fn as_custom(&self) -> Option<&V> {
|
||||
match self {
|
||||
OwnedAttributeValue::Custom(custom) => Some(custom),
|
||||
|
|
|
@ -1,43 +1,41 @@
|
|||
use dioxus_core::ElementId;
|
||||
//! Utilities that provide limited access to nodes
|
||||
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use crate::{
|
||||
node::{FromAnyValue, NodeData, NodeType, OwnedAttributeView},
|
||||
state::union_ordered_iter,
|
||||
RealNodeId,
|
||||
node::{ElementNode, FromAnyValue, NodeType, OwnedAttributeView},
|
||||
NodeId,
|
||||
};
|
||||
|
||||
/// A view into a [VNode] with limited access.
|
||||
/// A view into a [NodeType] with a mask that determines what is visible.
|
||||
#[derive(Debug)]
|
||||
pub struct NodeView<'a, V: FromAnyValue = ()> {
|
||||
inner: &'a NodeData<V>,
|
||||
mask: NodeMask,
|
||||
id: NodeId,
|
||||
inner: &'a NodeType<V>,
|
||||
mask: &'a NodeMask,
|
||||
}
|
||||
|
||||
impl<'a, V: FromAnyValue> NodeView<'a, V> {
|
||||
/// Create a new NodeView from a VNode, and mask.
|
||||
pub fn new(node: &'a NodeData<V>, view: NodeMask) -> Self {
|
||||
pub fn new(id: NodeId, node: &'a NodeType<V>, view: &'a NodeMask) -> Self {
|
||||
Self {
|
||||
inner: node,
|
||||
mask: view,
|
||||
id,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the id of the node
|
||||
pub fn id(&self) -> Option<ElementId> {
|
||||
self.inner.element_id
|
||||
}
|
||||
|
||||
/// Get the node id of the node
|
||||
pub fn node_id(&self) -> RealNodeId {
|
||||
self.inner.node_id
|
||||
pub fn node_id(&self) -> NodeId {
|
||||
self.id
|
||||
}
|
||||
|
||||
/// Get the tag of the node if the tag is enabled in the mask
|
||||
pub fn tag(&self) -> Option<&'a str> {
|
||||
self.mask
|
||||
.tag
|
||||
.then_some(match &self.inner.node_type {
|
||||
NodeType::Element { tag, .. } => Some(&**tag),
|
||||
.then_some(match &self.inner {
|
||||
NodeType::Element(ElementNode { tag, .. }) => Some(&**tag),
|
||||
_ => None,
|
||||
})
|
||||
.flatten()
|
||||
|
@ -47,8 +45,8 @@ impl<'a, V: FromAnyValue> NodeView<'a, V> {
|
|||
pub fn namespace(&self) -> Option<&'a str> {
|
||||
self.mask
|
||||
.namespace
|
||||
.then_some(match &self.inner.node_type {
|
||||
NodeType::Element { namespace, .. } => namespace.as_deref(),
|
||||
.then_some(match &self.inner {
|
||||
NodeType::Element(ElementNode { namespace, .. }) => namespace.as_deref(),
|
||||
_ => None,
|
||||
})
|
||||
.flatten()
|
||||
|
@ -58,8 +56,8 @@ impl<'a, V: FromAnyValue> NodeView<'a, V> {
|
|||
pub fn attributes<'b>(
|
||||
&'b self,
|
||||
) -> Option<impl Iterator<Item = OwnedAttributeView<'a, V>> + 'b> {
|
||||
match &self.inner.node_type {
|
||||
NodeType::Element { attributes, .. } => Some(
|
||||
match &self.inner {
|
||||
NodeType::Element(ElementNode { attributes, .. }) => Some(
|
||||
attributes
|
||||
.iter()
|
||||
.filter(move |(attr, _)| self.mask.attritutes.contains_attribute(&attr.name))
|
||||
|
@ -76,18 +74,21 @@ impl<'a, V: FromAnyValue> NodeView<'a, V> {
|
|||
pub fn text(&self) -> Option<&str> {
|
||||
self.mask
|
||||
.text
|
||||
.then_some(match &self.inner.node_type {
|
||||
NodeType::Text { text } => Some(&**text),
|
||||
.then_some(match &self.inner {
|
||||
NodeType::Text(text) => Some(&text.text),
|
||||
_ => None,
|
||||
})
|
||||
.flatten()
|
||||
.map(|x| &**x)
|
||||
}
|
||||
|
||||
/// Get the listeners if it is enabled in the mask
|
||||
pub fn listeners(&self) -> Option<impl Iterator<Item = &'a str> + '_> {
|
||||
if self.mask.listeners {
|
||||
match &self.inner.node_type {
|
||||
NodeType::Element { listeners, .. } => Some(listeners.iter().map(|l| &**l)),
|
||||
match &self.inner {
|
||||
NodeType::Element(ElementNode { listeners, .. }) => {
|
||||
Some(listeners.iter().map(|l| &**l))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
|
@ -99,125 +100,54 @@ impl<'a, V: FromAnyValue> NodeView<'a, V> {
|
|||
/// A mask that contains a list of attributes that are visible.
|
||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||
pub enum AttributeMask {
|
||||
/// All attributes are visible
|
||||
All,
|
||||
/// A list of attribute names that are visible, this list must be sorted
|
||||
Dynamic(Vec<String>),
|
||||
/// A list of attribute names that are visible, this list must be sorted
|
||||
Static(&'static [&'static str]),
|
||||
/// Only the given attributes are visible
|
||||
Some(FxHashSet<Box<str>>),
|
||||
}
|
||||
|
||||
impl AttributeMask {
|
||||
/// A empty attribute mask
|
||||
pub const NONE: Self = Self::Static(&[]);
|
||||
|
||||
/// Check if the given attribute is visible
|
||||
fn contains_attribute(&self, attr: &str) -> bool {
|
||||
match self {
|
||||
AttributeMask::All => true,
|
||||
AttributeMask::Dynamic(l) => l.binary_search_by_key(&attr, |s| s.as_str()).is_ok(),
|
||||
AttributeMask::Static(l) => l.binary_search(&attr).is_ok(),
|
||||
AttributeMask::Some(attrs) => attrs.contains(attr),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new dynamic attribute mask with a single attribute
|
||||
pub fn single(new: &str) -> Self {
|
||||
Self::Dynamic(vec![new.to_string()])
|
||||
}
|
||||
|
||||
/// Ensure the attribute list is sorted.
|
||||
pub fn verify(&self) {
|
||||
match &self {
|
||||
AttributeMask::Static(attrs) => debug_assert!(
|
||||
attrs.windows(2).all(|w| w[0] < w[1]),
|
||||
"attritutes must be increasing"
|
||||
),
|
||||
AttributeMask::Dynamic(attrs) => debug_assert!(
|
||||
attrs.windows(2).all(|w| w[0] < w[1]),
|
||||
"attritutes must be increasing"
|
||||
),
|
||||
_ => (),
|
||||
}
|
||||
let mut set = FxHashSet::default();
|
||||
set.insert(new.into());
|
||||
Self::Some(set)
|
||||
}
|
||||
|
||||
/// Combine two attribute masks
|
||||
pub fn union(&self, other: &Self) -> Self {
|
||||
let new = match (self, other) {
|
||||
(AttributeMask::Dynamic(s), AttributeMask::Dynamic(o)) => {
|
||||
AttributeMask::Dynamic(union_ordered_iter(
|
||||
s.iter().map(|s| s.as_str()),
|
||||
o.iter().map(|s| s.as_str()),
|
||||
s.len() + o.len(),
|
||||
))
|
||||
match (self, other) {
|
||||
(AttributeMask::Some(s), AttributeMask::Some(o)) => {
|
||||
AttributeMask::Some(s.union(o).cloned().collect())
|
||||
}
|
||||
(AttributeMask::Static(s), AttributeMask::Dynamic(o)) => {
|
||||
AttributeMask::Dynamic(union_ordered_iter(
|
||||
s.iter().copied(),
|
||||
o.iter().map(|s| s.as_str()),
|
||||
s.len() + o.len(),
|
||||
))
|
||||
}
|
||||
(AttributeMask::Dynamic(s), AttributeMask::Static(o)) => {
|
||||
AttributeMask::Dynamic(union_ordered_iter(
|
||||
s.iter().map(|s| s.as_str()),
|
||||
o.iter().copied(),
|
||||
s.len() + o.len(),
|
||||
))
|
||||
}
|
||||
(AttributeMask::Static(s), AttributeMask::Static(o)) => AttributeMask::Dynamic(
|
||||
union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()),
|
||||
),
|
||||
_ => AttributeMask::All,
|
||||
};
|
||||
new.verify();
|
||||
new
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if two attribute masks overlap
|
||||
fn overlaps(&self, other: &Self) -> bool {
|
||||
fn overlaps_iter<'a>(
|
||||
self_iter: impl Iterator<Item = &'a str>,
|
||||
mut other_iter: impl Iterator<Item = &'a str>,
|
||||
) -> bool {
|
||||
if let Some(mut other_attr) = other_iter.next() {
|
||||
for self_attr in self_iter {
|
||||
while other_attr < self_attr {
|
||||
if let Some(attr) = other_iter.next() {
|
||||
other_attr = attr;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if other_attr == self_attr {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
match (self, other) {
|
||||
(AttributeMask::All, AttributeMask::All) => true,
|
||||
(AttributeMask::All, AttributeMask::Dynamic(v)) => !v.is_empty(),
|
||||
(AttributeMask::All, AttributeMask::Static(s)) => !s.is_empty(),
|
||||
(AttributeMask::Dynamic(v), AttributeMask::All) => !v.is_empty(),
|
||||
(AttributeMask::Static(s), AttributeMask::All) => !s.is_empty(),
|
||||
(AttributeMask::Dynamic(v1), AttributeMask::Dynamic(v2)) => {
|
||||
overlaps_iter(v1.iter().map(|s| s.as_str()), v2.iter().map(|s| s.as_str()))
|
||||
}
|
||||
(AttributeMask::Dynamic(v), AttributeMask::Static(s)) => {
|
||||
overlaps_iter(v.iter().map(|s| s.as_str()), s.iter().copied())
|
||||
}
|
||||
(AttributeMask::Static(s), AttributeMask::Dynamic(v)) => {
|
||||
overlaps_iter(v.iter().map(|s| s.as_str()), s.iter().copied())
|
||||
}
|
||||
(AttributeMask::Static(s1), AttributeMask::Static(s2)) => {
|
||||
overlaps_iter(s1.iter().copied(), s2.iter().copied())
|
||||
(AttributeMask::All, AttributeMask::Some(attrs)) => !attrs.is_empty(),
|
||||
(AttributeMask::Some(attrs), AttributeMask::All) => !attrs.is_empty(),
|
||||
(AttributeMask::Some(attrs1), AttributeMask::Some(attrs2)) => {
|
||||
!attrs1.is_disjoint(attrs2)
|
||||
}
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AttributeMask {
|
||||
fn default() -> Self {
|
||||
AttributeMask::Static(&[])
|
||||
AttributeMask::Some(FxHashSet::default())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -232,14 +162,6 @@ pub struct NodeMask {
|
|||
}
|
||||
|
||||
impl NodeMask {
|
||||
/// A node mask with no parts visible.
|
||||
pub const NONE: Self = Self::new();
|
||||
/// A node mask with every part visible.
|
||||
pub const ALL: Self = Self::new_with_attrs(AttributeMask::All)
|
||||
.with_text()
|
||||
.with_element()
|
||||
.with_listeners();
|
||||
|
||||
/// Check if two masks overlap
|
||||
pub fn overlaps(&self, other: &Self) -> bool {
|
||||
(self.tag && other.tag)
|
||||
|
@ -260,10 +182,71 @@ impl NodeMask {
|
|||
}
|
||||
}
|
||||
|
||||
/// Create a new node mask with the given attributes
|
||||
pub const fn new_with_attrs(attritutes: AttributeMask) -> Self {
|
||||
/// Allow the mask to view the given attributes
|
||||
pub fn add_attributes(&mut self, attributes: AttributeMask) {
|
||||
self.attritutes = self.attritutes.union(&attributes);
|
||||
}
|
||||
|
||||
/// Set the mask to view the tag
|
||||
pub fn set_tag(&mut self) {
|
||||
self.tag = true;
|
||||
}
|
||||
|
||||
/// Set the mask to view the namespace
|
||||
pub fn set_namespace(&mut self) {
|
||||
self.namespace = true;
|
||||
}
|
||||
|
||||
/// Set the mask to view the text
|
||||
pub fn set_text(&mut self) {
|
||||
self.text = true;
|
||||
}
|
||||
|
||||
/// Set the mask to view the listeners
|
||||
pub fn set_listeners(&mut self) {
|
||||
self.listeners = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder for a mask that controls what attributes are visible.
|
||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||
pub enum AttributeMaskBuilder<'a> {
|
||||
/// All attributes are visible
|
||||
All,
|
||||
/// Only the given attributes are visible
|
||||
Some(&'a [&'a str]),
|
||||
}
|
||||
|
||||
impl Default for AttributeMaskBuilder<'_> {
|
||||
fn default() -> Self {
|
||||
AttributeMaskBuilder::Some(&[])
|
||||
}
|
||||
}
|
||||
|
||||
/// A mask that limits what parts of a node a dependency can see.
|
||||
#[derive(Default, PartialEq, Eq, Clone, Debug)]
|
||||
pub struct NodeMaskBuilder<'a> {
|
||||
attritutes: AttributeMaskBuilder<'a>,
|
||||
tag: bool,
|
||||
namespace: bool,
|
||||
text: bool,
|
||||
listeners: bool,
|
||||
}
|
||||
|
||||
impl<'a> NodeMaskBuilder<'a> {
|
||||
/// A node mask with no parts visible.
|
||||
pub const NONE: Self = Self::new();
|
||||
/// A node mask with every part visible.
|
||||
pub const ALL: Self = Self::new()
|
||||
.with_attrs(AttributeMaskBuilder::All)
|
||||
.with_text()
|
||||
.with_element()
|
||||
.with_listeners();
|
||||
|
||||
/// Create a empty node mask
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
attritutes,
|
||||
attritutes: AttributeMaskBuilder::Some(&[]),
|
||||
tag: false,
|
||||
namespace: false,
|
||||
text: false,
|
||||
|
@ -271,9 +254,10 @@ impl NodeMask {
|
|||
}
|
||||
}
|
||||
|
||||
/// Create a empty node mask
|
||||
pub const fn new() -> Self {
|
||||
Self::new_with_attrs(AttributeMask::NONE)
|
||||
/// Allow the mask to view the given attributes
|
||||
pub const fn with_attrs(mut self, attritutes: AttributeMaskBuilder<'a>) -> Self {
|
||||
self.attritutes = attritutes;
|
||||
self
|
||||
}
|
||||
|
||||
/// Allow the mask to view the tag
|
||||
|
@ -304,4 +288,20 @@ impl NodeMask {
|
|||
self.listeners = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the mask
|
||||
pub fn build(self) -> NodeMask {
|
||||
NodeMask {
|
||||
attritutes: match self.attritutes {
|
||||
AttributeMaskBuilder::All => AttributeMask::All,
|
||||
AttributeMaskBuilder::Some(attrs) => {
|
||||
AttributeMask::Some(attrs.iter().map(|s| (*s).into()).collect())
|
||||
}
|
||||
},
|
||||
tag: self.tag,
|
||||
namespace: self.namespace,
|
||||
text: self.text,
|
||||
listeners: self.listeners,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
17
packages/native-core/src/node_watcher.rs
Normal file
17
packages/native-core/src/node_watcher.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
//! Helpers for watching for changes in the DOM tree.
|
||||
|
||||
use crate::{node::FromAnyValue, prelude::*};
|
||||
|
||||
/// A trait for watching for changes in the DOM tree.
|
||||
pub trait NodeWatcher<V: FromAnyValue + Send + Sync> {
|
||||
/// Called after a node is added to the tree.
|
||||
fn on_node_added(&self, _node: NodeMut<V>) {}
|
||||
/// Called before a node is removed from the tree.
|
||||
fn on_node_removed(&self, _node: NodeMut<V>) {}
|
||||
/// Called after a node is moved to a new parent.
|
||||
fn on_node_moved(&self, _node: NodeMut<V>) {}
|
||||
// /// Called after the text content of a node is changed.
|
||||
// fn on_text_changed(&self, _node: NodeMut<V>) {}
|
||||
// /// Called after an attribute of an element is changed.
|
||||
// fn on_attribute_changed(&self, _node: NodeMut<V>, attribute: &str) {}
|
||||
}
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,305 +0,0 @@
|
|||
use std::cmp::Ordering;
|
||||
|
||||
use crate::node::{FromAnyValue, Node};
|
||||
use crate::node_ref::{NodeMask, NodeView};
|
||||
use crate::passes::{resolve_passes, resolve_passes_single_threaded, AnyPass, DirtyNodeStates};
|
||||
use crate::tree::TreeView;
|
||||
use crate::{FxDashSet, RealNodeId, SendAnyMap};
|
||||
|
||||
/// Join two sorted iterators
|
||||
pub(crate) fn union_ordered_iter<'a>(
|
||||
s_iter: impl Iterator<Item = &'a str>,
|
||||
o_iter: impl Iterator<Item = &'a str>,
|
||||
new_len_guess: usize,
|
||||
) -> Vec<String> {
|
||||
let mut s_peekable = s_iter.peekable();
|
||||
let mut o_peekable = o_iter.peekable();
|
||||
let mut v = Vec::with_capacity(new_len_guess);
|
||||
while let Some(s_i) = s_peekable.peek() {
|
||||
while let Some(o_i) = o_peekable.peek() {
|
||||
match o_i.cmp(s_i) {
|
||||
Ordering::Greater => {
|
||||
break;
|
||||
}
|
||||
Ordering::Less => {
|
||||
v.push(o_peekable.next().unwrap().to_string());
|
||||
}
|
||||
Ordering::Equal => {
|
||||
o_peekable.next();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
v.push(s_peekable.next().unwrap().to_string());
|
||||
}
|
||||
for o_i in o_peekable {
|
||||
v.push(o_i.to_string());
|
||||
}
|
||||
for w in v.windows(2) {
|
||||
debug_assert!(w[1] > w[0]);
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
/// This state is derived from children. For example a node's size could be derived from the size of children.
|
||||
/// Called when the current node's node properties are modified, a child's [ChildDepState] is modified or a child is removed.
|
||||
/// Called at most once per update.
|
||||
/// ```rust
|
||||
/// # use dioxus_native_core::node_ref::NodeView;
|
||||
/// # use dioxus_native_core::state::ChildDepState;
|
||||
/// #[derive(Clone, Copy, PartialEq, Default)]
|
||||
/// struct Layout {
|
||||
/// width: u32,
|
||||
/// height: u32,
|
||||
/// }
|
||||
///
|
||||
/// impl ChildDepState for Layout {
|
||||
/// type Ctx = ();
|
||||
/// // The layout depends on the layout of the children.
|
||||
/// type DepState = (Layout,);
|
||||
/// fn reduce<'a>(
|
||||
/// &mut self,
|
||||
/// _node: NodeView,
|
||||
/// children: impl Iterator<Item = (&'a Self,)>,
|
||||
/// _ctx: &Self::Ctx,
|
||||
/// ) -> bool
|
||||
/// where
|
||||
/// Self::DepState: 'a{
|
||||
/// /// Children are layed out form left to right. The width of the parent is the sum of the widths and the max of the heights.
|
||||
/// let new = children.map(|(&c,)|c).reduce(|c1, c2| Layout{
|
||||
/// width: c1.width + c2.width,
|
||||
/// height: c1.height.max(c2.height)
|
||||
/// }).unwrap_or_default();
|
||||
/// let changed = new != *self;
|
||||
/// *self = new;
|
||||
/// changed
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait ChildDepState<V: FromAnyValue = ()> {
|
||||
/// The context is passed to the [ChildDepState::reduce] when it is resolved.
|
||||
type Ctx;
|
||||
/// A state from each child node that this node depends on. Typically this is Self, but it could be any state that is within the state tree.
|
||||
/// Depstate must be a tuple containing any number of borrowed elements that are either [ChildDepState] or [NodeDepState].
|
||||
type DepState: ElementBorrowable;
|
||||
/// The part of a node that this state cares about. This is used to determine if the state should be updated when a node is updated.
|
||||
const NODE_MASK: NodeMask = NodeMask::NONE;
|
||||
/// Resolve the state current node's state from the state of the children, the state of the node, and some external context.
|
||||
fn reduce<'a>(
|
||||
&mut self,
|
||||
node: NodeView<'a, V>,
|
||||
children: impl Iterator<Item = <Self::DepState as ElementBorrowable>::ElementBorrowed<'a>>,
|
||||
ctx: &Self::Ctx,
|
||||
) -> bool
|
||||
where
|
||||
Self::DepState: 'a;
|
||||
}
|
||||
|
||||
/// This state that is passed down to children. For example text properties (`<b>` `<i>` `<u>`) would be passed to children.
|
||||
/// Called when the current node's node properties are modified or a parrent's [ParentDepState] is modified.
|
||||
/// Called at most once per update.
|
||||
/// ```rust
|
||||
/// use dioxus_native_core::node_ref::{NodeMask, AttributeMask, NodeView};
|
||||
/// use dioxus_native_core::state::*;
|
||||
///
|
||||
/// #[derive(Clone, Copy, PartialEq)]
|
||||
/// struct FontSize(usize);
|
||||
///
|
||||
/// impl ParentDepState for FontSize {
|
||||
/// type Ctx = ();
|
||||
/// // The font size depends on the font size of the parent element.
|
||||
/// type DepState = (Self,);
|
||||
/// const NODE_MASK: NodeMask =
|
||||
/// NodeMask::new_with_attrs(AttributeMask::Static(&[
|
||||
/// "font-size"
|
||||
/// ]));
|
||||
/// fn reduce<'a>(
|
||||
/// &mut self,
|
||||
/// node: NodeView,
|
||||
/// parent: Option<(&'a Self,)>,
|
||||
/// ctx: &Self::Ctx,
|
||||
/// ) -> bool{
|
||||
/// let old = *self;
|
||||
/// // If the font size was set on the parent, it is passed down to the current element
|
||||
/// if let Some((parent,)) = parent {
|
||||
/// *self = *parent;
|
||||
/// }
|
||||
/// // If the current node overrides the font size, use that size insead.
|
||||
/// for attr in node.attributes().unwrap() {
|
||||
/// match attr.attribute.name.as_str() {
|
||||
/// "font-size" => {
|
||||
/// self.0 = attr.value.as_text().unwrap().parse().unwrap();
|
||||
/// }
|
||||
/// // font-size is the only attribute we specified in the mask, so it is the only one we can see
|
||||
/// _ => unreachable!(),
|
||||
/// }
|
||||
/// }
|
||||
/// old != *self
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait ParentDepState<V: FromAnyValue = ()> {
|
||||
/// The context is passed to the [ParentDepState::reduce] when it is resolved.
|
||||
type Ctx;
|
||||
/// A state from from the parent node that this node depends on. Typically this is Self, but it could be any state that is within the state tree.
|
||||
/// Depstate must be a tuple containing any number of borrowed elements that are either [ParentDepState] or [NodeDepState].
|
||||
type DepState: ElementBorrowable;
|
||||
/// The part of a node that this state cares about. This is used to determine if the state should be updated when a node is updated.
|
||||
const NODE_MASK: NodeMask = NodeMask::NONE;
|
||||
/// Resolve the state current node's state from the state of the parent node, the state of the node, and some external context.
|
||||
fn reduce<'a>(
|
||||
&mut self,
|
||||
node: NodeView<'a, V>,
|
||||
parent: Option<<Self::DepState as ElementBorrowable>::ElementBorrowed<'a>>,
|
||||
ctx: &Self::Ctx,
|
||||
) -> bool;
|
||||
}
|
||||
|
||||
/// This state that is upadated lazily. For example any propertys that do not effect other parts of the dom like bg-color.
|
||||
/// Called when the current node's node properties are modified or a one of its dependanices are modified.
|
||||
/// Called at most once per update.
|
||||
/// NodeDepState is the only state that can accept multiple dependancies, but only from the current node.
|
||||
/// ```rust
|
||||
/// use dioxus_native_core::node_ref::{NodeMask, AttributeMask, NodeView};
|
||||
/// use dioxus_native_core::state::*;
|
||||
///
|
||||
/// #[derive(Clone, Copy, PartialEq)]
|
||||
/// struct TabIndex(usize);
|
||||
///
|
||||
/// impl NodeDepState for TabIndex {
|
||||
/// type Ctx = ();
|
||||
/// type DepState = ();
|
||||
/// const NODE_MASK: NodeMask =
|
||||
/// NodeMask::new_with_attrs(AttributeMask::Static(&[
|
||||
/// "tabindex"
|
||||
/// ]));
|
||||
/// fn reduce(
|
||||
/// &mut self,
|
||||
/// node: NodeView,
|
||||
/// siblings: (),
|
||||
/// ctx: &(),
|
||||
/// ) -> bool {
|
||||
/// let old = self.clone();
|
||||
/// for attr in node.attributes().unwrap() {
|
||||
/// match attr.attribute.name.as_str() {
|
||||
/// "tabindex" => {
|
||||
/// self.0 = attr.value.as_text().unwrap().parse().unwrap();
|
||||
/// }
|
||||
/// // tabindex is the only attribute we specified in the mask, so it is the only one we can see
|
||||
/// _ => unreachable!(),
|
||||
/// }
|
||||
/// }
|
||||
/// old != *self
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait NodeDepState<V: FromAnyValue = ()> {
|
||||
/// Depstate must be a tuple containing any number of borrowed elements that are either [ChildDepState], [ParentDepState] or [NodeDepState].
|
||||
type DepState: ElementBorrowable;
|
||||
/// The state passed to [NodeDepState::reduce] when it is resolved.
|
||||
type Ctx;
|
||||
/// The part of a node that this state cares about. This is used to determine if the state should be updated when a node is updated.
|
||||
const NODE_MASK: NodeMask = NodeMask::NONE;
|
||||
/// Resolve the state current node's state from the state of the sibling states, the state of the node, and some external context.
|
||||
fn reduce<'a>(
|
||||
&mut self,
|
||||
node: NodeView<'a, V>,
|
||||
node_state: <Self::DepState as ElementBorrowable>::ElementBorrowed<'a>,
|
||||
ctx: &Self::Ctx,
|
||||
) -> bool;
|
||||
}
|
||||
|
||||
/// Do not implement this trait. It is only meant to be derived and used through [crate::real_dom::RealDom].
|
||||
pub trait State<V: FromAnyValue + 'static>: Default + Clone + 'static {
|
||||
#[doc(hidden)]
|
||||
const PASSES: &'static [AnyPass<Node<Self, V>>];
|
||||
#[doc(hidden)]
|
||||
const MASKS: &'static [NodeMask];
|
||||
|
||||
#[doc(hidden)]
|
||||
fn update<T: TreeView<Node<Self, V>> + Sync + Send>(
|
||||
dirty: DirtyNodeStates,
|
||||
tree: &mut T,
|
||||
ctx: SendAnyMap,
|
||||
) -> FxDashSet<RealNodeId> {
|
||||
let passes = Self::PASSES.iter().collect();
|
||||
resolve_passes(tree, dirty, passes, ctx)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
fn update_single_threaded<T: TreeView<Node<Self, V>>>(
|
||||
dirty: DirtyNodeStates,
|
||||
tree: &mut T,
|
||||
ctx: SendAnyMap,
|
||||
) -> FxDashSet<RealNodeId> {
|
||||
let passes = Self::PASSES.iter().collect();
|
||||
resolve_passes_single_threaded(tree, dirty, passes, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
impl ChildDepState for () {
|
||||
type Ctx = ();
|
||||
type DepState = ();
|
||||
fn reduce<'a>(&mut self, _: NodeView<'a>, _: impl Iterator<Item = ()>, _: &Self::Ctx) -> bool
|
||||
where
|
||||
Self::DepState: 'a,
|
||||
{
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentDepState for () {
|
||||
type Ctx = ();
|
||||
type DepState = ();
|
||||
fn reduce(&mut self, _: NodeView, _: Option<()>, _: &Self::Ctx) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl NodeDepState for () {
|
||||
type DepState = ();
|
||||
type Ctx = ();
|
||||
fn reduce(&mut self, _: NodeView, _sibling: (), _: &Self::Ctx) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ElementBorrowable {
|
||||
type ElementBorrowed<'a>
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
fn borrow_elements(&self) -> Self::ElementBorrowed<'_>;
|
||||
}
|
||||
|
||||
macro_rules! impl_element_borrowable {
|
||||
($($t:ident),*) => {
|
||||
impl< $($t),* > ElementBorrowable for ($($t,)*) {
|
||||
type ElementBorrowed<'a> = ($(&'a $t,)*) where Self: 'a;
|
||||
|
||||
#[allow(clippy::unused_unit, non_snake_case)]
|
||||
fn borrow_elements<'a>(&'a self) -> Self::ElementBorrowed<'a> {
|
||||
let ($($t,)*) = self;
|
||||
($(&$t,)*)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_element_borrowable!();
|
||||
impl_element_borrowable!(A);
|
||||
impl_element_borrowable!(A, B);
|
||||
impl_element_borrowable!(A, B, C);
|
||||
impl_element_borrowable!(A, B, C, D);
|
||||
impl_element_borrowable!(A, B, C, D, E);
|
||||
impl_element_borrowable!(A, B, C, D, E, F);
|
||||
impl_element_borrowable!(A, B, C, D, E, F, G);
|
||||
impl_element_borrowable!(A, B, C, D, E, F, G, H);
|
||||
impl_element_borrowable!(A, B, C, D, E, F, G, H, I);
|
||||
impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J);
|
||||
impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J, K);
|
||||
impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J, K, L);
|
||||
impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J, K, L, M);
|
||||
impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
|
||||
impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
|
||||
impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
|
|
@ -1,669 +1,323 @@
|
|||
use std::collections::VecDeque;
|
||||
use std::marker::PhantomData;
|
||||
//! A tree of nodes intigated with shipyard
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug, PartialOrd, Ord)]
|
||||
pub struct NodeId(pub usize);
|
||||
use crate::NodeId;
|
||||
use shipyard::{Component, EntitiesViewMut, Get, View, ViewMut};
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||
pub struct Node<T> {
|
||||
value: T,
|
||||
/// A node in a tree.
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Component)]
|
||||
pub struct Node {
|
||||
parent: Option<NodeId>,
|
||||
children: Vec<NodeId>,
|
||||
height: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Tree<T> {
|
||||
nodes: Slab<Node<T>>,
|
||||
root: NodeId,
|
||||
}
|
||||
|
||||
impl<T> Tree<T> {
|
||||
fn try_remove(&mut self, id: NodeId) -> Option<Node<T>> {
|
||||
self.nodes.try_remove(id.0).map(|node| {
|
||||
if let Some(parent) = node.parent {
|
||||
self.nodes
|
||||
.get_mut(parent.0)
|
||||
.unwrap()
|
||||
.children
|
||||
.retain(|child| child != &id);
|
||||
}
|
||||
for child in &node.children {
|
||||
self.remove_recursive(*child);
|
||||
}
|
||||
node
|
||||
})
|
||||
}
|
||||
|
||||
fn remove_recursive(&mut self, node: NodeId) {
|
||||
let node = self.nodes.remove(node.0);
|
||||
for child in node.children {
|
||||
self.remove_recursive(child);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_height(&mut self, node: NodeId, height: u16) {
|
||||
let self_mut = self as *mut Self;
|
||||
let node = self.nodes.get_mut(node.0).unwrap();
|
||||
node.height = height;
|
||||
unsafe {
|
||||
// Safety: No node has itself as a child
|
||||
for child in &node.children {
|
||||
(*self_mut).set_height(*child, height + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TreeView<T>: Sized {
|
||||
type Iterator<'a>: Iterator<Item = &'a T>
|
||||
where
|
||||
T: 'a,
|
||||
Self: 'a;
|
||||
type IteratorMut<'a>: Iterator<Item = &'a mut T>
|
||||
where
|
||||
T: 'a,
|
||||
Self: 'a;
|
||||
|
||||
fn root(&self) -> NodeId;
|
||||
|
||||
fn contains(&self, id: NodeId) -> bool {
|
||||
self.get(id).is_some()
|
||||
}
|
||||
|
||||
fn get(&self, id: NodeId) -> Option<&T>;
|
||||
|
||||
fn get_unchecked(&self, id: NodeId) -> &T {
|
||||
unsafe { self.get(id).unwrap_unchecked() }
|
||||
}
|
||||
|
||||
fn get_mut(&mut self, id: NodeId) -> Option<&mut T>;
|
||||
|
||||
fn get_mut_unchecked(&mut self, id: NodeId) -> &mut T {
|
||||
unsafe { self.get_mut(id).unwrap_unchecked() }
|
||||
}
|
||||
|
||||
fn children(&self, id: NodeId) -> Option<Self::Iterator<'_>>;
|
||||
|
||||
fn children_mut(&mut self, id: NodeId) -> Option<Self::IteratorMut<'_>>;
|
||||
|
||||
fn parent_child_mut(&mut self, id: NodeId) -> Option<(&mut T, Self::IteratorMut<'_>)>;
|
||||
|
||||
fn children_ids(&self, id: NodeId) -> Option<&[NodeId]>;
|
||||
|
||||
fn parent(&self, id: NodeId) -> Option<&T>;
|
||||
|
||||
fn parent_mut(&mut self, id: NodeId) -> Option<&mut T>;
|
||||
|
||||
fn node_parent_mut(&mut self, id: NodeId) -> Option<(&mut T, Option<&mut T>)>;
|
||||
/// A view of a tree.
|
||||
pub type TreeRefView<'a> = View<'a, Node>;
|
||||
/// A mutable view of a tree.
|
||||
pub type TreeMutView<'a> = (EntitiesViewMut<'a>, ViewMut<'a, Node>);
|
||||
|
||||
/// A immutable view of a tree.
|
||||
pub trait TreeRef {
|
||||
/// The parent id of the node.
|
||||
fn parent_id(&self, id: NodeId) -> Option<NodeId>;
|
||||
|
||||
/// The children ids of the node.
|
||||
fn children_ids(&self, id: NodeId) -> Vec<NodeId>;
|
||||
/// The height of the node.
|
||||
fn height(&self, id: NodeId) -> Option<u16>;
|
||||
|
||||
fn size(&self) -> usize;
|
||||
|
||||
fn traverse_depth_first(&self, mut f: impl FnMut(&T)) {
|
||||
let mut stack = vec![self.root()];
|
||||
while let Some(id) = stack.pop() {
|
||||
if let Some(node) = self.get(id) {
|
||||
f(node);
|
||||
if let Some(children) = self.children_ids(id) {
|
||||
stack.extend(children.iter().copied().rev());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn traverse_depth_first_mut(&mut self, mut f: impl FnMut(&mut T)) {
|
||||
let mut stack = vec![self.root()];
|
||||
while let Some(id) = stack.pop() {
|
||||
if let Some(node) = self.get_mut(id) {
|
||||
f(node);
|
||||
if let Some(children) = self.children_ids(id) {
|
||||
stack.extend(children.iter().copied().rev());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn traverse_breadth_first(&self, mut f: impl FnMut(&T)) {
|
||||
let mut queue = VecDeque::new();
|
||||
queue.push_back(self.root());
|
||||
while let Some(id) = queue.pop_front() {
|
||||
if let Some(node) = self.get(id) {
|
||||
f(node);
|
||||
if let Some(children) = self.children_ids(id) {
|
||||
for id in children {
|
||||
queue.push_back(*id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn traverse_breadth_first_mut(&mut self, mut f: impl FnMut(&mut T)) {
|
||||
let mut queue = VecDeque::new();
|
||||
queue.push_back(self.root());
|
||||
while let Some(id) = queue.pop_front() {
|
||||
if let Some(node) = self.get_mut(id) {
|
||||
f(node);
|
||||
if let Some(children) = self.children_ids(id) {
|
||||
for id in children {
|
||||
queue.push_back(*id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Returns true if the node exists.
|
||||
fn contains(&self, id: NodeId) -> bool;
|
||||
}
|
||||
|
||||
pub trait TreeLike<T>: TreeView<T> {
|
||||
fn new(root: T) -> Self;
|
||||
|
||||
fn create_node(&mut self, value: T) -> NodeId;
|
||||
|
||||
fn add_child(&mut self, parent: NodeId, child: NodeId);
|
||||
|
||||
fn remove(&mut self, id: NodeId) -> Option<T>;
|
||||
|
||||
fn remove_all_children(&mut self, id: NodeId) -> Vec<T>;
|
||||
|
||||
fn replace(&mut self, old: NodeId, new: NodeId);
|
||||
|
||||
fn insert_before(&mut self, id: NodeId, new: NodeId);
|
||||
|
||||
fn insert_after(&mut self, id: NodeId, new: NodeId);
|
||||
/// A mutable view of a tree.
|
||||
pub trait TreeMut: TreeRef {
|
||||
/// Removes the node and all of its children.
|
||||
fn remove(&mut self, id: NodeId);
|
||||
/// Removes the node and all of its children.
|
||||
fn remove_single(&mut self, id: NodeId);
|
||||
/// Adds a new node to the tree.
|
||||
fn create_node(&mut self, id: NodeId);
|
||||
/// Adds a child to the node.
|
||||
fn add_child(&mut self, parent: NodeId, new: NodeId);
|
||||
/// Replaces the node with a new node.
|
||||
fn replace(&mut self, old_id: NodeId, new_id: NodeId);
|
||||
/// Inserts a node before another node.
|
||||
fn insert_before(&mut self, old_id: NodeId, new_id: NodeId);
|
||||
/// Inserts a node after another node.
|
||||
fn insert_after(&mut self, old_id: NodeId, new_id: NodeId);
|
||||
}
|
||||
|
||||
pub struct ChildNodeIterator<'a, T> {
|
||||
nodes: &'a Slab<Node<T>>,
|
||||
children_ids: Vec<NodeId>,
|
||||
index: usize,
|
||||
node_type: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: 'a> Iterator for ChildNodeIterator<'a, T> {
|
||||
type Item = &'a T;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.children_ids.get(self.index).map(|id| {
|
||||
self.index += 1;
|
||||
&self.nodes.get(id.0).unwrap().value
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ChildNodeIteratorMut<'a, T> {
|
||||
nodes: Vec<&'a mut Node<T>>,
|
||||
}
|
||||
|
||||
impl<'a, T: 'a> Iterator for ChildNodeIteratorMut<'a, T> {
|
||||
type Item = &'a mut T;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.nodes.pop().map(|node| &mut node.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TreeView<T> for Tree<T> {
|
||||
type Iterator<'a> = ChildNodeIterator<'a, T> where T: 'a;
|
||||
type IteratorMut<'a> = ChildNodeIteratorMut<'a, T> where T: 'a;
|
||||
|
||||
fn root(&self) -> NodeId {
|
||||
self.root
|
||||
}
|
||||
|
||||
fn get(&self, id: NodeId) -> Option<&T> {
|
||||
self.nodes.get(id.0).map(|node| &node.value)
|
||||
}
|
||||
|
||||
fn get_mut(&mut self, id: NodeId) -> Option<&mut T> {
|
||||
self.nodes.get_mut(id.0).map(|node| &mut node.value)
|
||||
}
|
||||
|
||||
fn children(&self, id: NodeId) -> Option<Self::Iterator<'_>> {
|
||||
self.children_ids(id).map(|children_ids| ChildNodeIterator {
|
||||
nodes: &self.nodes,
|
||||
children_ids: children_ids.to_vec(),
|
||||
index: 0,
|
||||
node_type: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
fn children_mut(&mut self, id: NodeId) -> Option<Self::IteratorMut<'_>> {
|
||||
// Safety: No node has itself as a parent.
|
||||
if let Some(children_ids) = self.children_ids(id) {
|
||||
let children_ids = children_ids.to_vec();
|
||||
Some(ChildNodeIteratorMut {
|
||||
nodes: unsafe {
|
||||
self.nodes
|
||||
.get_many_mut_unchecked(children_ids.into_iter().rev().map(|id| id.0))
|
||||
.unwrap()
|
||||
},
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn children_ids(&self, id: NodeId) -> Option<&[NodeId]> {
|
||||
self.nodes.get(id.0).map(|node| node.children.as_slice())
|
||||
}
|
||||
|
||||
fn parent(&self, id: NodeId) -> Option<&T> {
|
||||
self.nodes
|
||||
.get(id.0)
|
||||
.and_then(|node| node.parent.map(|id| self.nodes.get(id.0).unwrap()))
|
||||
.map(|node| &node.value)
|
||||
}
|
||||
|
||||
fn parent_mut(&mut self, id: NodeId) -> Option<&mut T> {
|
||||
let self_ptr = self as *mut Self;
|
||||
unsafe {
|
||||
// Safety: No node has itself as a parent.
|
||||
self.nodes
|
||||
.get_mut(id.0)
|
||||
.and_then(move |node| {
|
||||
node.parent
|
||||
.map(move |id| (*self_ptr).nodes.get_mut(id.0).unwrap())
|
||||
})
|
||||
.map(|node| &mut node.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TreeRef for TreeRefView<'a> {
|
||||
fn parent_id(&self, id: NodeId) -> Option<NodeId> {
|
||||
self.nodes.get(id.0).and_then(|node| node.parent)
|
||||
self.get(id).ok()?.parent
|
||||
}
|
||||
|
||||
fn children_ids(&self, id: NodeId) -> Vec<NodeId> {
|
||||
self.get(id)
|
||||
.map(|node| node.children.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn height(&self, id: NodeId) -> Option<u16> {
|
||||
self.nodes.get(id.0).map(|n| n.height)
|
||||
Some(self.get(id).ok()?.height)
|
||||
}
|
||||
|
||||
fn get_unchecked(&self, id: NodeId) -> &T {
|
||||
unsafe { &self.nodes.get_unchecked(id.0).value }
|
||||
}
|
||||
|
||||
fn get_mut_unchecked(&mut self, id: NodeId) -> &mut T {
|
||||
unsafe { &mut self.nodes.get_unchecked_mut(id.0).value }
|
||||
}
|
||||
|
||||
fn size(&self) -> usize {
|
||||
self.nodes.len()
|
||||
}
|
||||
|
||||
fn node_parent_mut(&mut self, id: NodeId) -> Option<(&mut T, Option<&mut T>)> {
|
||||
if let Some(parent_id) = self.parent_id(id) {
|
||||
self.nodes
|
||||
.get2_mut(id.0, parent_id.0)
|
||||
.map(|(node, parent)| (&mut node.value, Some(&mut parent.value)))
|
||||
} else {
|
||||
self.nodes.get_mut(id.0).map(|node| (&mut node.value, None))
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_child_mut(&mut self, id: NodeId) -> Option<(&mut T, Self::IteratorMut<'_>)> {
|
||||
// Safety: No node will appear as a child twice
|
||||
if let Some(children_ids) = self.children_ids(id) {
|
||||
debug_assert!(!children_ids.iter().any(|child_id| *child_id == id));
|
||||
let mut borrowed = unsafe {
|
||||
let as_vec = children_ids.to_vec();
|
||||
self.nodes
|
||||
.get_many_mut_unchecked(
|
||||
as_vec
|
||||
.into_iter()
|
||||
.rev()
|
||||
.map(|id| id.0)
|
||||
.chain(std::iter::once(id.0)),
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
let node = &mut borrowed.pop().unwrap().value;
|
||||
Some((node, ChildNodeIteratorMut { nodes: borrowed }))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
fn contains(&self, id: NodeId) -> bool {
|
||||
self.get(id).is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TreeLike<T> for Tree<T> {
|
||||
fn new(root: T) -> Self {
|
||||
let mut nodes = Slab::default();
|
||||
let root = NodeId(nodes.insert(Node {
|
||||
value: root,
|
||||
parent: None,
|
||||
children: Vec::new(),
|
||||
height: 0,
|
||||
}));
|
||||
Self { nodes, root }
|
||||
impl<'a> TreeMut for TreeMutView<'a> {
|
||||
fn remove(&mut self, id: NodeId) {
|
||||
fn recurse(tree: &mut TreeMutView<'_>, id: NodeId) {
|
||||
let children = tree.children_ids(id);
|
||||
for child in children {
|
||||
recurse(tree, child);
|
||||
}
|
||||
}
|
||||
{
|
||||
let mut node_data_mut = &mut self.1;
|
||||
if let Some(parent) = node_data_mut.get(id).unwrap().parent {
|
||||
let parent = (&mut node_data_mut).get(parent).unwrap();
|
||||
parent.children.retain(|&child| child != id);
|
||||
}
|
||||
}
|
||||
|
||||
recurse(self, id);
|
||||
}
|
||||
|
||||
fn create_node(&mut self, value: T) -> NodeId {
|
||||
NodeId(self.nodes.insert(Node {
|
||||
value,
|
||||
parent: None,
|
||||
children: Vec::new(),
|
||||
height: 0,
|
||||
}))
|
||||
fn remove_single(&mut self, id: NodeId) {
|
||||
{
|
||||
let mut node_data_mut = &mut self.1;
|
||||
if let Some(parent) = node_data_mut.get(id).unwrap().parent {
|
||||
let parent = (&mut node_data_mut).get(parent).unwrap();
|
||||
parent.children.retain(|&child| child != id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_node(&mut self, id: NodeId) {
|
||||
let (entities, node_data_mut) = self;
|
||||
entities.add_component(
|
||||
id,
|
||||
node_data_mut,
|
||||
Node {
|
||||
parent: None,
|
||||
children: Vec::new(),
|
||||
height: 0,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn add_child(&mut self, parent: NodeId, new: NodeId) {
|
||||
self.nodes.get_mut(new.0).unwrap().parent = Some(parent);
|
||||
let parent = self.nodes.get_mut(parent.0).unwrap();
|
||||
parent.children.push(new);
|
||||
let height = parent.height + 1;
|
||||
self.set_height(new, height);
|
||||
}
|
||||
|
||||
fn remove(&mut self, id: NodeId) -> Option<T> {
|
||||
self.try_remove(id).map(|node| node.value)
|
||||
}
|
||||
|
||||
fn remove_all_children(&mut self, id: NodeId) -> Vec<T> {
|
||||
let mut children = Vec::new();
|
||||
let self_mut = self as *mut Self;
|
||||
for child in self.children_ids(id).unwrap() {
|
||||
unsafe {
|
||||
// Safety: No node has itself as a child
|
||||
children.push((*self_mut).remove(*child).unwrap());
|
||||
}
|
||||
let height;
|
||||
{
|
||||
let mut node_state = &mut self.1;
|
||||
(&mut node_state).get(new).unwrap().parent = Some(parent);
|
||||
let parent = (&mut node_state).get(parent).unwrap();
|
||||
parent.children.push(new);
|
||||
height = parent.height + 1;
|
||||
}
|
||||
children
|
||||
set_height(self, new, height);
|
||||
}
|
||||
|
||||
fn replace(&mut self, old_id: NodeId, new_id: NodeId) {
|
||||
// remove the old node
|
||||
let old = self
|
||||
.try_remove(old_id)
|
||||
.expect("tried to replace a node that doesn't exist");
|
||||
// update the parent's link to the child
|
||||
if let Some(parent_id) = old.parent {
|
||||
let parent = self.nodes.get_mut(parent_id.0).unwrap();
|
||||
for id in &mut parent.children {
|
||||
if *id == old_id {
|
||||
*id = new_id;
|
||||
{
|
||||
let mut node_state = &mut self.1;
|
||||
// update the parent's link to the child
|
||||
if let Some(parent_id) = node_state.get(old_id).unwrap().parent {
|
||||
let parent = (&mut node_state).get(parent_id).unwrap();
|
||||
for id in &mut parent.children {
|
||||
if *id == old_id {
|
||||
*id = new_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
let height = parent.height + 1;
|
||||
set_height(self, new_id, height);
|
||||
}
|
||||
let height = parent.height + 1;
|
||||
self.set_height(new_id, height);
|
||||
}
|
||||
// remove the old node
|
||||
self.remove(old_id);
|
||||
}
|
||||
|
||||
fn insert_before(&mut self, id: NodeId, new: NodeId) {
|
||||
let node = self.nodes.get(id.0).unwrap();
|
||||
let parent_id = node.parent.expect("tried to insert before root");
|
||||
self.nodes.get_mut(new.0).unwrap().parent = Some(parent_id);
|
||||
let parent = self.nodes.get_mut(parent_id.0).unwrap();
|
||||
fn insert_before(&mut self, old_id: NodeId, new_id: NodeId) {
|
||||
let mut node_state = &mut self.1;
|
||||
let old_node = node_state.get(old_id).unwrap();
|
||||
let parent_id = old_node.parent.expect("tried to insert before root");
|
||||
(&mut node_state).get(new_id).unwrap().parent = Some(parent_id);
|
||||
let parent = (&mut node_state).get(parent_id).unwrap();
|
||||
let index = parent
|
||||
.children
|
||||
.iter()
|
||||
.position(|child| child == &id)
|
||||
.position(|child| *child == old_id)
|
||||
.unwrap();
|
||||
parent.children.insert(index, new);
|
||||
parent.children.insert(index, new_id);
|
||||
let height = parent.height + 1;
|
||||
self.set_height(new, height);
|
||||
set_height(self, new_id, height);
|
||||
}
|
||||
|
||||
fn insert_after(&mut self, id: NodeId, new: NodeId) {
|
||||
let node = self.nodes.get(id.0).unwrap();
|
||||
let parent_id = node.parent.expect("tried to insert before root");
|
||||
self.nodes.get_mut(new.0).unwrap().parent = Some(parent_id);
|
||||
let parent = self.nodes.get_mut(parent_id.0).unwrap();
|
||||
fn insert_after(&mut self, old_id: NodeId, new_id: NodeId) {
|
||||
let mut node_state = &mut self.1;
|
||||
let old_node = node_state.get(old_id).unwrap();
|
||||
let parent_id = old_node.parent.expect("tried to insert before root");
|
||||
(&mut node_state).get(new_id).unwrap().parent = Some(parent_id);
|
||||
let parent = (&mut node_state).get(parent_id).unwrap();
|
||||
let index = parent
|
||||
.children
|
||||
.iter()
|
||||
.position(|child| child == &id)
|
||||
.position(|child| *child == old_id)
|
||||
.unwrap();
|
||||
parent.children.insert(index + 1, new);
|
||||
parent.children.insert(index + 1, new_id);
|
||||
let height = parent.height + 1;
|
||||
self.set_height(new, height);
|
||||
set_height(self, new_id, height);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the height of a node and updates the height of all its children
|
||||
fn set_height(tree: &mut TreeMutView<'_>, node: NodeId, height: u16) {
|
||||
let children = {
|
||||
let mut node_data_mut = &mut tree.1;
|
||||
let mut node = (&mut node_data_mut).get(node).unwrap();
|
||||
node.height = height;
|
||||
node.children.clone()
|
||||
};
|
||||
for child in children {
|
||||
set_height(tree, child, height + 1);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TreeRef for TreeMutView<'a> {
|
||||
fn parent_id(&self, id: NodeId) -> Option<NodeId> {
|
||||
let node_data = &self.1;
|
||||
node_data.get(id).unwrap().parent
|
||||
}
|
||||
|
||||
fn children_ids(&self, id: NodeId) -> Vec<NodeId> {
|
||||
let node_data = &self.1;
|
||||
node_data
|
||||
.get(id)
|
||||
.map(|node| node.children.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn height(&self, id: NodeId) -> Option<u16> {
|
||||
let node_data = &self.1;
|
||||
node_data.get(id).map(|node| node.height).ok()
|
||||
}
|
||||
|
||||
fn contains(&self, id: NodeId) -> bool {
|
||||
self.1.get(id).is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn creation() {
|
||||
let mut tree = Tree::new(1);
|
||||
let parent = tree.root();
|
||||
let child = tree.create_node(0);
|
||||
tree.add_child(parent, child);
|
||||
use shipyard::World;
|
||||
#[derive(Component)]
|
||||
struct Num(i32);
|
||||
|
||||
println!("Tree: {tree:#?}");
|
||||
assert_eq!(tree.size(), 2);
|
||||
assert_eq!(tree.height(parent), Some(0));
|
||||
assert_eq!(tree.height(child), Some(1));
|
||||
assert_eq!(*tree.get(parent).unwrap(), 1);
|
||||
assert_eq!(*tree.get(child).unwrap(), 0);
|
||||
assert_eq!(tree.parent_id(parent), None);
|
||||
assert_eq!(tree.parent_id(child).unwrap(), parent);
|
||||
assert_eq!(tree.children_ids(parent).unwrap(), &[child]);
|
||||
let mut world = World::new();
|
||||
let parent_id = world.add_entity(Num(1i32));
|
||||
let child_id = world.add_entity(Num(0i32));
|
||||
|
||||
let mut tree = world.borrow::<TreeMutView>().unwrap();
|
||||
|
||||
tree.create_node(parent_id);
|
||||
tree.create_node(child_id);
|
||||
|
||||
tree.add_child(parent_id, child_id);
|
||||
|
||||
assert_eq!(tree.height(parent_id), Some(0));
|
||||
assert_eq!(tree.height(child_id), Some(1));
|
||||
assert_eq!(tree.parent_id(parent_id), None);
|
||||
assert_eq!(tree.parent_id(child_id).unwrap(), parent_id);
|
||||
assert_eq!(tree.children_ids(parent_id), &[child_id]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insertion() {
|
||||
let mut tree = Tree::new(0);
|
||||
let parent = tree.root();
|
||||
let child = tree.create_node(2);
|
||||
use shipyard::World;
|
||||
#[derive(Component)]
|
||||
struct Num(i32);
|
||||
|
||||
let mut world = World::new();
|
||||
let parent = world.add_entity(Num(0));
|
||||
let child = world.add_entity(Num(2));
|
||||
let before = world.add_entity(Num(1));
|
||||
let after = world.add_entity(Num(3));
|
||||
|
||||
let mut tree = world.borrow::<TreeMutView>().unwrap();
|
||||
|
||||
tree.create_node(parent);
|
||||
tree.create_node(child);
|
||||
tree.create_node(before);
|
||||
tree.create_node(after);
|
||||
|
||||
tree.add_child(parent, child);
|
||||
let before = tree.create_node(1);
|
||||
tree.insert_before(child, before);
|
||||
let after = tree.create_node(3);
|
||||
tree.insert_after(child, after);
|
||||
|
||||
println!("Tree: {tree:#?}");
|
||||
assert_eq!(tree.size(), 4);
|
||||
assert_eq!(tree.height(parent), Some(0));
|
||||
assert_eq!(tree.height(child), Some(1));
|
||||
assert_eq!(tree.height(before), Some(1));
|
||||
assert_eq!(tree.height(after), Some(1));
|
||||
assert_eq!(*tree.get(parent).unwrap(), 0);
|
||||
assert_eq!(*tree.get(before).unwrap(), 1);
|
||||
assert_eq!(*tree.get(child).unwrap(), 2);
|
||||
assert_eq!(*tree.get(after).unwrap(), 3);
|
||||
assert_eq!(tree.parent_id(before).unwrap(), parent);
|
||||
assert_eq!(tree.parent_id(child).unwrap(), parent);
|
||||
assert_eq!(tree.parent_id(after).unwrap(), parent);
|
||||
assert_eq!(tree.children_ids(parent).unwrap(), &[before, child, after]);
|
||||
assert_eq!(tree.children_ids(parent), &[before, child, after]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deletion() {
|
||||
let mut tree = Tree::new(0);
|
||||
let parent = tree.root();
|
||||
let child = tree.create_node(2);
|
||||
use shipyard::World;
|
||||
#[derive(Component)]
|
||||
struct Num(i32);
|
||||
|
||||
let mut world = World::new();
|
||||
let parent = world.add_entity(Num(0));
|
||||
let child = world.add_entity(Num(2));
|
||||
let before = world.add_entity(Num(1));
|
||||
let after = world.add_entity(Num(3));
|
||||
|
||||
let mut tree = world.borrow::<TreeMutView>().unwrap();
|
||||
|
||||
tree.create_node(parent);
|
||||
tree.create_node(child);
|
||||
tree.create_node(before);
|
||||
tree.create_node(after);
|
||||
|
||||
tree.add_child(parent, child);
|
||||
let before = tree.create_node(1);
|
||||
tree.insert_before(child, before);
|
||||
let after = tree.create_node(3);
|
||||
tree.insert_after(child, after);
|
||||
|
||||
println!("Tree: {tree:#?}");
|
||||
assert_eq!(tree.size(), 4);
|
||||
assert_eq!(tree.height(parent), Some(0));
|
||||
assert_eq!(tree.height(child), Some(1));
|
||||
assert_eq!(tree.height(before), Some(1));
|
||||
assert_eq!(tree.height(after), Some(1));
|
||||
assert_eq!(*tree.get(parent).unwrap(), 0);
|
||||
assert_eq!(*tree.get(before).unwrap(), 1);
|
||||
assert_eq!(*tree.get(child).unwrap(), 2);
|
||||
assert_eq!(*tree.get(after).unwrap(), 3);
|
||||
assert_eq!(tree.parent_id(before).unwrap(), parent);
|
||||
assert_eq!(tree.parent_id(child).unwrap(), parent);
|
||||
assert_eq!(tree.parent_id(after).unwrap(), parent);
|
||||
assert_eq!(tree.children_ids(parent).unwrap(), &[before, child, after]);
|
||||
assert_eq!(tree.children_ids(parent), &[before, child, after]);
|
||||
|
||||
tree.remove(child);
|
||||
|
||||
println!("Tree: {tree:#?}");
|
||||
assert_eq!(tree.size(), 3);
|
||||
assert_eq!(tree.height(parent), Some(0));
|
||||
assert_eq!(tree.height(before), Some(1));
|
||||
assert_eq!(tree.height(after), Some(1));
|
||||
assert_eq!(*tree.get(parent).unwrap(), 0);
|
||||
assert_eq!(*tree.get(before).unwrap(), 1);
|
||||
assert_eq!(tree.get(child), None);
|
||||
assert_eq!(*tree.get(after).unwrap(), 3);
|
||||
assert_eq!(tree.parent_id(before).unwrap(), parent);
|
||||
assert_eq!(tree.parent_id(after).unwrap(), parent);
|
||||
assert_eq!(tree.children_ids(parent).unwrap(), &[before, after]);
|
||||
assert_eq!(tree.children_ids(parent), &[before, after]);
|
||||
|
||||
tree.remove(before);
|
||||
|
||||
println!("Tree: {tree:#?}");
|
||||
assert_eq!(tree.size(), 2);
|
||||
assert_eq!(tree.height(parent), Some(0));
|
||||
assert_eq!(tree.height(after), Some(1));
|
||||
assert_eq!(*tree.get(parent).unwrap(), 0);
|
||||
assert_eq!(tree.get(before), None);
|
||||
assert_eq!(*tree.get(after).unwrap(), 3);
|
||||
assert_eq!(tree.parent_id(after).unwrap(), parent);
|
||||
assert_eq!(tree.children_ids(parent).unwrap(), &[after]);
|
||||
assert_eq!(tree.children_ids(parent), &[after]);
|
||||
|
||||
tree.remove(after);
|
||||
|
||||
println!("Tree: {tree:#?}");
|
||||
assert_eq!(tree.size(), 1);
|
||||
assert_eq!(tree.height(parent), Some(0));
|
||||
assert_eq!(*tree.get(parent).unwrap(), 0);
|
||||
assert_eq!(tree.get(after), None);
|
||||
assert_eq!(tree.children_ids(parent).unwrap(), &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn traverse_depth_first() {
|
||||
let mut tree = Tree::new(0);
|
||||
let parent = tree.root();
|
||||
let child1 = tree.create_node(1);
|
||||
tree.add_child(parent, child1);
|
||||
let grandchild1 = tree.create_node(2);
|
||||
tree.add_child(child1, grandchild1);
|
||||
let child2 = tree.create_node(3);
|
||||
tree.add_child(parent, child2);
|
||||
let grandchild2 = tree.create_node(4);
|
||||
tree.add_child(child2, grandchild2);
|
||||
|
||||
let mut node_count = 0;
|
||||
tree.traverse_depth_first(move |node| {
|
||||
assert_eq!(*node, node_count);
|
||||
node_count += 1;
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_node_children_mut() {
|
||||
let mut tree = Tree::new(0);
|
||||
let parent = tree.root();
|
||||
let child1 = tree.create_node(1);
|
||||
tree.add_child(parent, child1);
|
||||
let child2 = tree.create_node(2);
|
||||
tree.add_child(parent, child2);
|
||||
let child3 = tree.create_node(3);
|
||||
tree.add_child(parent, child3);
|
||||
|
||||
let (parent, children) = tree.parent_child_mut(parent).unwrap();
|
||||
for (i, child) in children.enumerate() {
|
||||
assert_eq!(*child, i + 1);
|
||||
}
|
||||
println!("Parent: {parent:#?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_many_mut_unchecked() {
|
||||
let mut slab = Slab::new();
|
||||
let parent = slab.insert(0);
|
||||
let child = slab.insert(1);
|
||||
let grandchild = slab.insert(2);
|
||||
|
||||
let all =
|
||||
unsafe { slab.get_many_mut_unchecked([parent, child, grandchild].into_iter()) }.unwrap();
|
||||
println!("All: {all:#?}");
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Slab<T> {
|
||||
data: Vec<Option<T>>,
|
||||
free: VecDeque<usize>,
|
||||
}
|
||||
|
||||
impl<T> Default for Slab<T> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Slab<T> {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
data: Vec::new(),
|
||||
free: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get(&self, id: usize) -> Option<&T> {
|
||||
self.data.get(id).and_then(|x| x.as_ref())
|
||||
}
|
||||
|
||||
unsafe fn get_unchecked(&self, id: usize) -> &T {
|
||||
self.data.get_unchecked(id).as_ref().unwrap()
|
||||
}
|
||||
|
||||
fn get_mut(&mut self, id: usize) -> Option<&mut T> {
|
||||
self.data.get_mut(id).and_then(|x| x.as_mut())
|
||||
}
|
||||
|
||||
unsafe fn get_unchecked_mut(&mut self, id: usize) -> &mut T {
|
||||
self.data.get_unchecked_mut(id).as_mut().unwrap()
|
||||
}
|
||||
|
||||
fn get2_mut(&mut self, id1: usize, id2: usize) -> Option<(&mut T, &mut T)> {
|
||||
assert!(id1 != id2);
|
||||
let ptr = self.data.as_mut_ptr();
|
||||
let first = unsafe { &mut *ptr.add(id1) };
|
||||
let second = unsafe { &mut *ptr.add(id2) };
|
||||
if let (Some(first), Some(second)) = (first, second) {
|
||||
Some((first, second))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn get_many_mut_unchecked(
|
||||
&mut self,
|
||||
ids: impl Iterator<Item = usize>,
|
||||
) -> Option<Vec<&mut T>> {
|
||||
let ptr = self.data.as_mut_ptr();
|
||||
let mut result = Vec::new();
|
||||
for id in ids {
|
||||
let item = unsafe { &mut *ptr.add(id) };
|
||||
if let Some(item) = item {
|
||||
result.push(item);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(result)
|
||||
}
|
||||
|
||||
fn insert(&mut self, value: T) -> usize {
|
||||
if let Some(id) = self.free.pop_front() {
|
||||
self.data[id] = Some(value);
|
||||
id
|
||||
} else {
|
||||
self.data.push(Some(value));
|
||||
self.data.len() - 1
|
||||
}
|
||||
}
|
||||
|
||||
fn try_remove(&mut self, id: usize) -> Option<T> {
|
||||
self.data.get_mut(id).and_then(|x| {
|
||||
self.free.push_back(id);
|
||||
x.take()
|
||||
})
|
||||
}
|
||||
|
||||
fn remove(&mut self, id: usize) -> T {
|
||||
self.try_remove(id).unwrap()
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.data.len() - self.free.len()
|
||||
}
|
||||
assert_eq!(tree.children_ids(parent), &[]);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
//! A cursor implementation that can be used to navigate and edit text.
|
||||
|
||||
use std::{cmp::Ordering, ops::Range};
|
||||
|
||||
use dioxus_html::input_data::keyboard_types::{Code, Key, Modifiers};
|
||||
use keyboard_types::{Code, Key, Modifiers};
|
||||
|
||||
/// This contains the information about the text that is used by the cursor to handle navigation.
|
||||
pub trait Text {
|
||||
|
@ -221,26 +223,28 @@ impl Cursor {
|
|||
/// Handle moving the cursor with the given key.
|
||||
pub fn handle_input<T: Text + ?Sized>(
|
||||
&mut self,
|
||||
data: &dioxus_html::KeyboardData,
|
||||
code: &Code,
|
||||
key: &Key,
|
||||
modifiers: &Modifiers,
|
||||
text: &mut impl TextEditable<T>,
|
||||
max_text_length: usize,
|
||||
) {
|
||||
use Code::*;
|
||||
match data.code() {
|
||||
match code {
|
||||
ArrowUp => {
|
||||
self.move_cursor(
|
||||
|c| c.up(text.as_ref()),
|
||||
data.modifiers().contains(Modifiers::SHIFT),
|
||||
modifiers.contains(Modifiers::SHIFT),
|
||||
);
|
||||
}
|
||||
ArrowDown => {
|
||||
self.move_cursor(
|
||||
|c| c.down(text.as_ref()),
|
||||
data.modifiers().contains(Modifiers::SHIFT),
|
||||
modifiers.contains(Modifiers::SHIFT),
|
||||
);
|
||||
}
|
||||
ArrowRight => {
|
||||
if data.modifiers().contains(Modifiers::CONTROL) {
|
||||
if modifiers.contains(Modifiers::CONTROL) {
|
||||
self.move_cursor(
|
||||
|c| {
|
||||
let mut change = 1;
|
||||
|
@ -255,17 +259,17 @@ impl Cursor {
|
|||
}
|
||||
c.move_col(change as i32, text.as_ref());
|
||||
},
|
||||
data.modifiers().contains(Modifiers::SHIFT),
|
||||
modifiers.contains(Modifiers::SHIFT),
|
||||
);
|
||||
} else {
|
||||
self.move_cursor(
|
||||
|c| c.right(text.as_ref()),
|
||||
data.modifiers().contains(Modifiers::SHIFT),
|
||||
modifiers.contains(Modifiers::SHIFT),
|
||||
);
|
||||
}
|
||||
}
|
||||
ArrowLeft => {
|
||||
if data.modifiers().contains(Modifiers::CONTROL) {
|
||||
if modifiers.contains(Modifiers::CONTROL) {
|
||||
self.move_cursor(
|
||||
|c| {
|
||||
let mut change = -1;
|
||||
|
@ -279,23 +283,23 @@ impl Cursor {
|
|||
}
|
||||
c.move_col(change, text.as_ref());
|
||||
},
|
||||
data.modifiers().contains(Modifiers::SHIFT),
|
||||
modifiers.contains(Modifiers::SHIFT),
|
||||
);
|
||||
} else {
|
||||
self.move_cursor(
|
||||
|c| c.left(text.as_ref()),
|
||||
data.modifiers().contains(Modifiers::SHIFT),
|
||||
modifiers.contains(Modifiers::SHIFT),
|
||||
);
|
||||
}
|
||||
}
|
||||
End => {
|
||||
self.move_cursor(
|
||||
|c| c.col = c.len_line(text.as_ref()),
|
||||
data.modifiers().contains(Modifiers::SHIFT),
|
||||
modifiers.contains(Modifiers::SHIFT),
|
||||
);
|
||||
}
|
||||
Home => {
|
||||
self.move_cursor(|c| c.col = 0, data.modifiers().contains(Modifiers::SHIFT));
|
||||
self.move_cursor(|c| c.col = 0, modifiers.contains(Modifiers::SHIFT));
|
||||
}
|
||||
Backspace => {
|
||||
self.start.realize_col(text.as_ref());
|
||||
|
@ -305,7 +309,7 @@ impl Cursor {
|
|||
} else if start_idx > 0 {
|
||||
self.start.left(text.as_ref());
|
||||
text.delete_range(start_idx - 1..start_idx);
|
||||
if data.modifiers().contains(Modifiers::CONTROL) {
|
||||
if modifiers.contains(Modifiers::CONTROL) {
|
||||
start_idx = self.start.idx(text.as_ref());
|
||||
while start_idx > 0
|
||||
&& text
|
||||
|
@ -340,7 +344,7 @@ impl Cursor {
|
|||
}
|
||||
_ => {
|
||||
self.start.realize_col(text.as_ref());
|
||||
if let Key::Character(character) = data.key() {
|
||||
if let Key::Character(character) = key {
|
||||
if text.as_ref().length() + 1 - self.selection_len(text.as_ref())
|
||||
<= max_text_length
|
||||
{
|
||||
|
@ -460,13 +464,9 @@ fn cursor_input() {
|
|||
|
||||
for _ in 0..5 {
|
||||
cursor.handle_input(
|
||||
&dioxus_html::KeyboardData::new(
|
||||
dioxus_html::input_data::keyboard_types::Key::ArrowRight,
|
||||
dioxus_html::input_data::keyboard_types::Code::ArrowRight,
|
||||
dioxus_html::input_data::keyboard_types::Location::Standard,
|
||||
false,
|
||||
Modifiers::empty(),
|
||||
),
|
||||
&keyboard_types::Code::ArrowRight,
|
||||
&keyboard_types::Key::ArrowRight,
|
||||
&Modifiers::empty(),
|
||||
&mut text,
|
||||
10,
|
||||
);
|
||||
|
@ -474,13 +474,9 @@ fn cursor_input() {
|
|||
|
||||
for _ in 0..5 {
|
||||
cursor.handle_input(
|
||||
&dioxus_html::KeyboardData::new(
|
||||
dioxus_html::input_data::keyboard_types::Key::Backspace,
|
||||
dioxus_html::input_data::keyboard_types::Code::Backspace,
|
||||
dioxus_html::input_data::keyboard_types::Location::Standard,
|
||||
false,
|
||||
Modifiers::empty(),
|
||||
),
|
||||
&keyboard_types::Code::Backspace,
|
||||
&keyboard_types::Key::Backspace,
|
||||
&Modifiers::empty(),
|
||||
&mut text,
|
||||
10,
|
||||
);
|
||||
|
@ -491,61 +487,41 @@ fn cursor_input() {
|
|||
let goal_text = "hello world\nhi";
|
||||
let max_width = goal_text.len();
|
||||
cursor.handle_input(
|
||||
&dioxus_html::KeyboardData::new(
|
||||
dioxus_html::input_data::keyboard_types::Key::Character("h".to_string()),
|
||||
dioxus_html::input_data::keyboard_types::Code::KeyH,
|
||||
dioxus_html::input_data::keyboard_types::Location::Standard,
|
||||
false,
|
||||
Modifiers::empty(),
|
||||
),
|
||||
&keyboard_types::Code::KeyH,
|
||||
&keyboard_types::Key::Character("h".to_string()),
|
||||
&Modifiers::empty(),
|
||||
&mut text,
|
||||
max_width,
|
||||
);
|
||||
|
||||
cursor.handle_input(
|
||||
&dioxus_html::KeyboardData::new(
|
||||
dioxus_html::input_data::keyboard_types::Key::Character("e".to_string()),
|
||||
dioxus_html::input_data::keyboard_types::Code::KeyE,
|
||||
dioxus_html::input_data::keyboard_types::Location::Standard,
|
||||
false,
|
||||
Modifiers::empty(),
|
||||
),
|
||||
&keyboard_types::Code::KeyE,
|
||||
&keyboard_types::Key::Character("e".to_string()),
|
||||
&Modifiers::empty(),
|
||||
&mut text,
|
||||
max_width,
|
||||
);
|
||||
|
||||
cursor.handle_input(
|
||||
&dioxus_html::KeyboardData::new(
|
||||
dioxus_html::input_data::keyboard_types::Key::Character("l".to_string()),
|
||||
dioxus_html::input_data::keyboard_types::Code::KeyL,
|
||||
dioxus_html::input_data::keyboard_types::Location::Standard,
|
||||
false,
|
||||
Modifiers::empty(),
|
||||
),
|
||||
&keyboard_types::Code::KeyL,
|
||||
&keyboard_types::Key::Character("l".to_string()),
|
||||
&Modifiers::empty(),
|
||||
&mut text,
|
||||
max_width,
|
||||
);
|
||||
|
||||
cursor.handle_input(
|
||||
&dioxus_html::KeyboardData::new(
|
||||
dioxus_html::input_data::keyboard_types::Key::Character("l".to_string()),
|
||||
dioxus_html::input_data::keyboard_types::Code::KeyL,
|
||||
dioxus_html::input_data::keyboard_types::Location::Standard,
|
||||
false,
|
||||
Modifiers::empty(),
|
||||
),
|
||||
&keyboard_types::Code::KeyL,
|
||||
&keyboard_types::Key::Character("l".to_string()),
|
||||
&Modifiers::empty(),
|
||||
&mut text,
|
||||
max_width,
|
||||
);
|
||||
|
||||
cursor.handle_input(
|
||||
&dioxus_html::KeyboardData::new(
|
||||
dioxus_html::input_data::keyboard_types::Key::Character("o".to_string()),
|
||||
dioxus_html::input_data::keyboard_types::Code::KeyO,
|
||||
dioxus_html::input_data::keyboard_types::Location::Standard,
|
||||
false,
|
||||
Modifiers::empty(),
|
||||
),
|
||||
&keyboard_types::Code::KeyO,
|
||||
&keyboard_types::Key::Character("o".to_string()),
|
||||
&Modifiers::empty(),
|
||||
&mut text,
|
||||
max_width,
|
||||
);
|
||||
|
@ -553,13 +529,9 @@ fn cursor_input() {
|
|||
// these should be ignored
|
||||
for _ in 0..10 {
|
||||
cursor.handle_input(
|
||||
&dioxus_html::KeyboardData::new(
|
||||
dioxus_html::input_data::keyboard_types::Key::Character("o".to_string()),
|
||||
dioxus_html::input_data::keyboard_types::Code::KeyO,
|
||||
dioxus_html::input_data::keyboard_types::Location::Standard,
|
||||
false,
|
||||
Modifiers::empty(),
|
||||
),
|
||||
&keyboard_types::Code::KeyO,
|
||||
&keyboard_types::Key::Character("o".to_string()),
|
||||
&Modifiers::empty(),
|
||||
&mut text,
|
||||
max_width,
|
||||
);
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
//! # Utilities for renders using the RealDOM
|
||||
//!
|
||||
//! This includes an iterator that can be used to iterate over the children of a node that persists changes in the struture of the DOM, and a cursor for text editing.
|
||||
|
||||
mod persistant_iterator;
|
||||
pub use persistant_iterator::*;
|
||||
pub mod cursor;
|
||||
|
|
|
@ -1,252 +1,204 @@
|
|||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{
|
||||
node::{FromAnyValue, NodeType},
|
||||
real_dom::RealDom,
|
||||
state::State,
|
||||
tree::TreeView,
|
||||
NodeId, RealNodeId,
|
||||
node::FromAnyValue,
|
||||
node_watcher::NodeWatcher,
|
||||
prelude::{NodeMut, NodeRef},
|
||||
real_dom::{NodeImmutable, RealDom},
|
||||
NodeId,
|
||||
};
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use dioxus_core::{Mutation, Mutations};
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ElementProduced {
|
||||
/// The iterator produced an element by progressing to the next node in a depth first order.
|
||||
Progressed(RealNodeId),
|
||||
/// The element produced by the iterator
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct ElementProduced {
|
||||
id: NodeId,
|
||||
movement: IteratorMovement,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
/// The method by which the iterator produced an element
|
||||
pub enum IteratorMovement {
|
||||
/// The iterator produced an element by progressing to the next node
|
||||
Progressed,
|
||||
/// The iterator reached the end of the tree and looped back to the root
|
||||
Looped(RealNodeId),
|
||||
Looped,
|
||||
}
|
||||
|
||||
impl ElementProduced {
|
||||
pub fn id(&self) -> RealNodeId {
|
||||
match self {
|
||||
ElementProduced::Progressed(id) => *id,
|
||||
ElementProduced::Looped(id) => *id,
|
||||
/// Get the id of the element produced
|
||||
pub fn id(&self) -> NodeId {
|
||||
self.id
|
||||
}
|
||||
|
||||
/// The movement the iterator made to produce the element
|
||||
pub fn movement(&self) -> &IteratorMovement {
|
||||
&self.movement
|
||||
}
|
||||
|
||||
fn looped(id: NodeId) -> Self {
|
||||
Self {
|
||||
id,
|
||||
movement: IteratorMovement::Looped,
|
||||
}
|
||||
}
|
||||
|
||||
fn progressed(id: NodeId) -> Self {
|
||||
Self {
|
||||
id,
|
||||
movement: IteratorMovement::Progressed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum NodePosition {
|
||||
AtNode,
|
||||
InChild(usize),
|
||||
struct PersistantElementIterUpdater<V> {
|
||||
stack: Arc<Mutex<smallvec::SmallVec<[NodeId; 5]>>>,
|
||||
phantom: std::marker::PhantomData<V>,
|
||||
}
|
||||
|
||||
impl NodePosition {
|
||||
fn map(&self, mut f: impl FnMut(usize) -> usize) -> Self {
|
||||
match self {
|
||||
Self::AtNode => Self::AtNode,
|
||||
Self::InChild(i) => Self::InChild(f(*i)),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_or_insert(&mut self, child_idx: usize) -> usize {
|
||||
match self {
|
||||
Self::AtNode => {
|
||||
*self = Self::InChild(child_idx);
|
||||
child_idx
|
||||
impl<V: FromAnyValue + Sync + Send> NodeWatcher<V> for PersistantElementIterUpdater<V> {
|
||||
fn on_node_moved(&self, node: NodeMut<V>) {
|
||||
// if any element is moved, update its parents in the stack
|
||||
let mut stack = self.stack.lock().unwrap();
|
||||
let moved = node.id();
|
||||
let rdom = node.real_dom();
|
||||
if let Some(r) = stack.iter().position(|el_id| *el_id == moved) {
|
||||
let back = &stack[r..];
|
||||
let mut new = SmallVec::new();
|
||||
let mut parent = node.parent_id();
|
||||
while let Some(p) = parent.and_then(|id| rdom.get(id)) {
|
||||
new.push(p.id());
|
||||
parent = p.parent_id();
|
||||
}
|
||||
Self::InChild(i) => *i,
|
||||
new.extend(back.iter().copied());
|
||||
*stack = new;
|
||||
}
|
||||
}
|
||||
|
||||
fn on_node_removed(&self, node: NodeMut<V>) {
|
||||
// if any element is removed in the chain, remove it and its children from the stack
|
||||
let mut stack = self.stack.lock().unwrap();
|
||||
let removed = node.id();
|
||||
if let Some(r) = stack.iter().position(|el_id| *el_id == removed) {
|
||||
stack.truncate(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Focus systems need a iterator that can persist through changes in the [dioxus_core::VirtualDom].
|
||||
/// Focus systems need a iterator that can persist through changes in the [crate::prelude::RealDom]
|
||||
/// This iterator traverses the tree depth first.
|
||||
/// Iterate through it with [PersistantElementIter::next] [PersistantElementIter::prev], and update it with [PersistantElementIter::prune] (with data from [`dioxus_core::prelude::VirtualDom::work_with_deadline`]).
|
||||
/// You can iterate through it with [PersistantElementIter::next] and [PersistantElementIter::prev].
|
||||
/// The iterator loops around when it reaches the end or the beginning.
|
||||
pub struct PersistantElementIter {
|
||||
// stack of elements and fragments
|
||||
stack: smallvec::SmallVec<[(RealNodeId, NodePosition); 5]>,
|
||||
}
|
||||
|
||||
impl Default for PersistantElementIter {
|
||||
fn default() -> Self {
|
||||
PersistantElementIter {
|
||||
stack: smallvec::smallvec![(NodeId(0), NodePosition::AtNode)],
|
||||
}
|
||||
}
|
||||
// stack of elements and fragments, the last element is the last element that was yielded
|
||||
stack: Arc<Mutex<smallvec::SmallVec<[NodeId; 5]>>>,
|
||||
}
|
||||
|
||||
impl PersistantElementIter {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
/// Create a new iterator in the RealDom
|
||||
pub fn create<V: FromAnyValue + Send + Sync>(rdom: &mut RealDom<V>) -> Self {
|
||||
let inner = Arc::new(Mutex::new(smallvec::smallvec![rdom.root_id()]));
|
||||
|
||||
/// remove stale element refreneces
|
||||
/// returns true if the focused element is removed
|
||||
pub fn prune<S: State<V>, V: FromAnyValue>(
|
||||
&mut self,
|
||||
mutations: &Mutations,
|
||||
rdom: &RealDom<S, V>,
|
||||
) -> bool {
|
||||
let mut changed = false;
|
||||
let ids_removed: Vec<_> = mutations
|
||||
.edits
|
||||
.iter()
|
||||
.filter_map(|m| {
|
||||
// nodes within templates will never be removed
|
||||
match m {
|
||||
Mutation::Remove { id } => Some(rdom.element_to_node_id(*id)),
|
||||
Mutation::ReplaceWith { id, .. } => Some(rdom.element_to_node_id(*id)),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
// if any element is removed in the chain, remove it and its children from the stack
|
||||
if let Some(r) = self
|
||||
.stack
|
||||
.iter()
|
||||
.position(|(el_id, _)| ids_removed.iter().any(|id| el_id == id))
|
||||
{
|
||||
self.stack.truncate(r);
|
||||
changed = true;
|
||||
}
|
||||
// if a child is removed or inserted before or at the current element, update the child index
|
||||
for (el_id, child_idx) in self.stack.iter_mut() {
|
||||
if let NodePosition::InChild(child_idx) = child_idx {
|
||||
if let Some(children) = &rdom.tree.children_ids(*el_id) {
|
||||
for m in &mutations.edits {
|
||||
match m {
|
||||
Mutation::Remove { id } => {
|
||||
let id = rdom.element_to_node_id(*id);
|
||||
if children.iter().take(*child_idx + 1).any(|c| *c == id) {
|
||||
*child_idx -= 1;
|
||||
}
|
||||
}
|
||||
Mutation::InsertBefore { id, m } => {
|
||||
let id = rdom.element_to_node_id(*id);
|
||||
if children.iter().take(*child_idx + 1).any(|c| *c == id) {
|
||||
*child_idx += *m;
|
||||
}
|
||||
}
|
||||
Mutation::InsertAfter { id, m } => {
|
||||
let id = rdom.element_to_node_id(*id);
|
||||
if children.iter().take(*child_idx).any(|c| *c == id) {
|
||||
*child_idx += *m;
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
changed
|
||||
rdom.add_node_watcher(PersistantElementIterUpdater {
|
||||
stack: inner.clone(),
|
||||
phantom: std::marker::PhantomData,
|
||||
});
|
||||
|
||||
PersistantElementIter { stack: inner }
|
||||
}
|
||||
|
||||
/// get the next element
|
||||
pub fn next<S: State<V>, V: FromAnyValue>(&mut self, rdom: &RealDom<S, V>) -> ElementProduced {
|
||||
if self.stack.is_empty() {
|
||||
let id = NodeId(0);
|
||||
let new = (id, NodePosition::AtNode);
|
||||
self.stack.push(new);
|
||||
ElementProduced::Looped(id)
|
||||
pub fn next<V: FromAnyValue + Send + Sync>(&mut self, rdom: &RealDom<V>) -> ElementProduced {
|
||||
let mut stack = self.stack.lock().unwrap();
|
||||
if stack.is_empty() {
|
||||
let id = rdom.root_id();
|
||||
let new = id;
|
||||
stack.push(new);
|
||||
ElementProduced::looped(id)
|
||||
} else {
|
||||
let (last, old_child_idx) = self.stack.last_mut().unwrap();
|
||||
let node = &rdom[*last];
|
||||
match &node.node_data.node_type {
|
||||
NodeType::Element { .. } => {
|
||||
let children = rdom.tree.children_ids(*last).unwrap();
|
||||
*old_child_idx = old_child_idx.map(|i| i + 1);
|
||||
// if we have children, go to the next child
|
||||
let child_idx = old_child_idx.get_or_insert(0);
|
||||
if child_idx >= children.len() {
|
||||
self.pop();
|
||||
self.next(rdom)
|
||||
} else {
|
||||
let id = children[child_idx];
|
||||
if let NodeType::Element { .. } = &rdom[id].node_data.node_type {
|
||||
self.stack.push((id, NodePosition::AtNode));
|
||||
let mut look_in_children = true;
|
||||
loop {
|
||||
if let Some(current) = stack.last().and_then(|last| rdom.get(*last)) {
|
||||
// if the current element has children, add the first child to the stack and return it
|
||||
if look_in_children {
|
||||
if let Some(first) = current.children().first() {
|
||||
let new = first.id();
|
||||
stack.push(new);
|
||||
return ElementProduced::progressed(new);
|
||||
}
|
||||
ElementProduced::Progressed(id)
|
||||
}
|
||||
stack.pop();
|
||||
if let Some(new) = current.next() {
|
||||
// the next element exists, add it to the stack and return it
|
||||
let new = new.id();
|
||||
stack.push(new);
|
||||
return ElementProduced::progressed(new);
|
||||
}
|
||||
// otherwise, continue the loop and go to the parent
|
||||
} else {
|
||||
// if there is no parent, loop back to the root
|
||||
let new = rdom.root_id();
|
||||
stack.clear();
|
||||
stack.push(new);
|
||||
return ElementProduced::looped(new);
|
||||
}
|
||||
|
||||
NodeType::Text { .. } | NodeType::Placeholder { .. } => {
|
||||
// we are at a leaf, so we are done
|
||||
ElementProduced::Progressed(self.pop())
|
||||
}
|
||||
look_in_children = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// get the previous element
|
||||
pub fn prev<S: State<V>, V: FromAnyValue>(&mut self, rdom: &RealDom<S, V>) -> ElementProduced {
|
||||
pub fn prev<V: FromAnyValue + Send + Sync>(&mut self, rdom: &RealDom<V>) -> ElementProduced {
|
||||
// recursively add the last child element to the stack
|
||||
fn push_back<S: State<V>, V: FromAnyValue>(
|
||||
stack: &mut smallvec::SmallVec<[(RealNodeId, NodePosition); 5]>,
|
||||
new_node: RealNodeId,
|
||||
rdom: &RealDom<S, V>,
|
||||
) -> RealNodeId {
|
||||
match &rdom[new_node].node_data.node_type {
|
||||
NodeType::Element { .. } => {
|
||||
let children = rdom.tree.children_ids(new_node).unwrap();
|
||||
if children.is_empty() {
|
||||
new_node
|
||||
} else {
|
||||
stack.push((new_node, NodePosition::InChild(children.len() - 1)));
|
||||
push_back(stack, *children.last().unwrap(), rdom)
|
||||
}
|
||||
}
|
||||
_ => new_node,
|
||||
fn push_back<V: FromAnyValue + Send + Sync>(
|
||||
stack: &mut smallvec::SmallVec<[NodeId; 5]>,
|
||||
node: NodeRef<V>,
|
||||
) -> NodeId {
|
||||
stack.push(node.id());
|
||||
if let Some(last) = node.children().last() {
|
||||
push_back(stack, *last)
|
||||
} else {
|
||||
node.id()
|
||||
}
|
||||
}
|
||||
if self.stack.is_empty() {
|
||||
let new_node = NodeId(0);
|
||||
ElementProduced::Looped(push_back(&mut self.stack, new_node, rdom))
|
||||
let mut stack = self.stack.lock().unwrap();
|
||||
if stack.is_empty() {
|
||||
let id = rdom.root_id();
|
||||
let last = push_back(&mut stack, rdom.get(id).unwrap());
|
||||
ElementProduced::looped(last)
|
||||
} else if let Some(current) = stack.pop().and_then(|last| rdom.get(last)) {
|
||||
if let Some(new) = current.prev() {
|
||||
// the next element exists, add it to the stack and return it
|
||||
let new = push_back(&mut stack, new);
|
||||
ElementProduced::progressed(new)
|
||||
}
|
||||
// otherwise, yeild the parent
|
||||
else if let Some(parent) = stack.last() {
|
||||
// if there is a parent, return it
|
||||
ElementProduced::progressed(*parent)
|
||||
} else {
|
||||
// if there is no parent, loop back to the root
|
||||
let id = rdom.root_id();
|
||||
let last = push_back(&mut stack, rdom.get(id).unwrap());
|
||||
ElementProduced::looped(last)
|
||||
}
|
||||
} else {
|
||||
let (last, old_child_idx) = self.stack.last_mut().unwrap();
|
||||
let node = &rdom[*last];
|
||||
match &node.node_data.node_type {
|
||||
NodeType::Element { .. } => {
|
||||
let children = rdom.tree.children_ids(*last).unwrap();
|
||||
// if we have children, go to the next child
|
||||
if let NodePosition::InChild(0) = old_child_idx {
|
||||
ElementProduced::Progressed(self.pop())
|
||||
} else {
|
||||
*old_child_idx = old_child_idx.map(|i| i - 1);
|
||||
if let NodePosition::InChild(child_idx) = old_child_idx {
|
||||
if *child_idx >= children.len() || children.is_empty() {
|
||||
self.pop();
|
||||
self.prev(rdom)
|
||||
} else {
|
||||
let new_node = children[*child_idx];
|
||||
ElementProduced::Progressed(push_back(
|
||||
&mut self.stack,
|
||||
new_node,
|
||||
rdom,
|
||||
))
|
||||
}
|
||||
} else {
|
||||
self.pop();
|
||||
self.prev(rdom)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NodeType::Text { .. } | NodeType::Placeholder { .. } => {
|
||||
// we are at a leaf, so we are done
|
||||
ElementProduced::Progressed(self.pop())
|
||||
}
|
||||
}
|
||||
// if there is no parent, loop back to the root
|
||||
let id = rdom.root_id();
|
||||
let last = push_back(&mut stack, rdom.get(id).unwrap());
|
||||
ElementProduced::looped(last)
|
||||
}
|
||||
}
|
||||
|
||||
fn pop(&mut self) -> RealNodeId {
|
||||
self.stack.pop().unwrap().0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
struct Empty {}
|
||||
impl State<()> for Empty {
|
||||
const PASSES: &'static [crate::AnyPass<crate::node::Node<Self, ()>>] = &[];
|
||||
|
||||
const MASKS: &'static [crate::NodeMask] = &[];
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(unused_variables)]
|
||||
fn traverse() {
|
||||
use crate::dioxus::DioxusState;
|
||||
use crate::prelude::*;
|
||||
use dioxus::prelude::*;
|
||||
#[allow(non_snake_case)]
|
||||
fn Base(cx: Scope) -> Element {
|
||||
|
@ -266,82 +218,85 @@ fn traverse() {
|
|||
let mut vdom = VirtualDom::new(Base);
|
||||
let mutations = vdom.rebuild();
|
||||
|
||||
let mut rdom: RealDom<Empty> = RealDom::new();
|
||||
let mut rdom: RealDom = RealDom::new([]);
|
||||
|
||||
let _to_update = rdom.apply_mutations(mutations);
|
||||
let mut iter = PersistantElementIter::create(&mut rdom);
|
||||
let mut dioxus_state = DioxusState::create(&mut rdom);
|
||||
dioxus_state.apply_mutations(&mut rdom, mutations);
|
||||
|
||||
let mut iter = PersistantElementIter::new();
|
||||
let div_tag = "div".to_string();
|
||||
assert!(matches!(
|
||||
&rdom[iter.next(&rdom).id()].node_data.node_type,
|
||||
NodeType::Element { tag: div_tag, .. }
|
||||
&*rdom.get(iter.next(&rdom).id()).unwrap().node_type(),
|
||||
NodeType::Element(ElementNode { tag: div_tag, .. })
|
||||
));
|
||||
assert!(matches!(
|
||||
&rdom[iter.next(&rdom).id()].node_data.node_type,
|
||||
NodeType::Element { tag: div_tag, .. }
|
||||
&*rdom.get(iter.next(&rdom).id()).unwrap().node_type(),
|
||||
NodeType::Element(ElementNode { tag: div_tag, .. })
|
||||
));
|
||||
let text1 = "hello".to_string();
|
||||
assert!(matches!(
|
||||
&rdom[iter.next(&rdom).id()].node_data.node_type,
|
||||
NodeType::Text { text: text1, .. }
|
||||
&*rdom.get(iter.next(&rdom).id()).unwrap().node_type(),
|
||||
NodeType::Text(text1)
|
||||
));
|
||||
let p_tag = "p".to_string();
|
||||
assert!(matches!(
|
||||
&rdom[iter.next(&rdom).id()].node_data.node_type,
|
||||
NodeType::Element { tag: p_tag, .. }
|
||||
&*rdom.get(iter.next(&rdom).id()).unwrap().node_type(),
|
||||
NodeType::Element(ElementNode { tag: p_tag, .. })
|
||||
));
|
||||
let text2 = "world".to_string();
|
||||
assert!(matches!(
|
||||
&rdom[iter.next(&rdom).id()].node_data.node_type,
|
||||
NodeType::Text { text: text2, .. }
|
||||
&*rdom.get(iter.next(&rdom).id()).unwrap().node_type(),
|
||||
NodeType::Text(text2)
|
||||
));
|
||||
let text3 = "hello world".to_string();
|
||||
assert!(matches!(
|
||||
&rdom[iter.next(&rdom).id()].node_data.node_type,
|
||||
NodeType::Text { text: text3, .. }
|
||||
&*rdom.get(iter.next(&rdom).id()).unwrap().node_type(),
|
||||
NodeType::Text(text3)
|
||||
));
|
||||
assert!(matches!(
|
||||
&rdom[iter.next(&rdom).id()].node_data.node_type,
|
||||
NodeType::Element { tag: div_tag, .. }
|
||||
&*rdom.get(iter.next(&rdom).id()).unwrap().node_type(),
|
||||
NodeType::Element(ElementNode { tag: div_tag, .. })
|
||||
));
|
||||
|
||||
assert!(matches!(
|
||||
&rdom[iter.prev(&rdom).id()].node_data.node_type,
|
||||
NodeType::Text { text: text3, .. }
|
||||
&*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(),
|
||||
NodeType::Text(text3)
|
||||
));
|
||||
assert!(matches!(
|
||||
&rdom[iter.prev(&rdom).id()].node_data.node_type,
|
||||
NodeType::Text { text: text2, .. }
|
||||
&*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(),
|
||||
NodeType::Text(text2)
|
||||
));
|
||||
assert!(matches!(
|
||||
&rdom[iter.prev(&rdom).id()].node_data.node_type,
|
||||
NodeType::Element { tag: p_tag, .. }
|
||||
&*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(),
|
||||
NodeType::Element(ElementNode { tag: p_tag, .. })
|
||||
));
|
||||
assert!(matches!(
|
||||
&rdom[iter.prev(&rdom).id()].node_data.node_type,
|
||||
NodeType::Text { text: text1, .. }
|
||||
&*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(),
|
||||
NodeType::Text(text1)
|
||||
));
|
||||
assert!(matches!(
|
||||
&rdom[iter.prev(&rdom).id()].node_data.node_type,
|
||||
NodeType::Element { tag: div_tag, .. }
|
||||
&*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(),
|
||||
NodeType::Element(ElementNode { tag: div_tag, .. })
|
||||
));
|
||||
assert!(matches!(
|
||||
&rdom[iter.prev(&rdom).id()].node_data.node_type,
|
||||
NodeType::Element { tag: div_tag, .. }
|
||||
&*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(),
|
||||
NodeType::Element(ElementNode { tag: div_tag, .. })
|
||||
));
|
||||
assert!(matches!(
|
||||
&rdom[iter.prev(&rdom).id()].node_data.node_type,
|
||||
NodeType::Element { tag: div_tag, .. }
|
||||
&*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(),
|
||||
NodeType::Element(ElementNode { tag: div_tag, .. })
|
||||
));
|
||||
assert!(matches!(
|
||||
&rdom[iter.prev(&rdom).id()].node_data.node_type,
|
||||
NodeType::Text { text: text3, .. }
|
||||
&*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(),
|
||||
NodeType::Text(text3)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(unused_variables)]
|
||||
fn persist_removes() {
|
||||
use crate::dioxus::DioxusState;
|
||||
use crate::prelude::*;
|
||||
use dioxus::prelude::*;
|
||||
#[allow(non_snake_case)]
|
||||
fn Base(cx: Scope) -> Element {
|
||||
|
@ -365,16 +320,17 @@ fn persist_removes() {
|
|||
}
|
||||
let mut vdom = VirtualDom::new(Base);
|
||||
|
||||
let mut rdom: RealDom<Empty> = RealDom::new();
|
||||
let mut rdom: RealDom = RealDom::new([]);
|
||||
|
||||
let build = vdom.rebuild();
|
||||
println!("{build:#?}");
|
||||
let _to_update = rdom.apply_mutations(build);
|
||||
|
||||
// this will end on the node that is removed
|
||||
let mut iter1 = PersistantElementIter::new();
|
||||
let mut iter1 = PersistantElementIter::create(&mut rdom);
|
||||
// this will end on the after node that is removed
|
||||
let mut iter2 = PersistantElementIter::new();
|
||||
let mut iter2 = PersistantElementIter::create(&mut rdom);
|
||||
let mut dioxus_state = DioxusState::create(&mut rdom);
|
||||
dioxus_state.apply_mutations(&mut rdom, build);
|
||||
|
||||
// root
|
||||
iter1.next(&rdom).id();
|
||||
iter2.next(&rdom).id();
|
||||
|
@ -401,29 +357,27 @@ fn persist_removes() {
|
|||
vdom.mark_dirty(ScopeId(0));
|
||||
let update = vdom.render_immediate();
|
||||
println!("{update:#?}");
|
||||
iter1.prune(&update, &rdom);
|
||||
iter2.prune(&update, &rdom);
|
||||
let _to_update = rdom.apply_mutations(update);
|
||||
dioxus_state.apply_mutations(&mut rdom, update);
|
||||
|
||||
let root_tag = "Root".to_string();
|
||||
let idx = iter1.next(&rdom).id();
|
||||
dbg!(&rdom[idx].node_data.node_type);
|
||||
assert!(matches!(
|
||||
&rdom[idx].node_data.node_type,
|
||||
NodeType::Element { tag: root_tag, .. }
|
||||
&*rdom.get(idx).unwrap().node_type(),
|
||||
NodeType::Element(ElementNode { tag: root_tag, .. })
|
||||
));
|
||||
|
||||
let idx = iter2.next(&rdom).id();
|
||||
dbg!(&rdom[idx].node_data.node_type);
|
||||
assert!(matches!(
|
||||
&rdom[idx].node_data.node_type,
|
||||
NodeType::Element { tag: root_tag, .. }
|
||||
&*rdom.get(idx).unwrap().node_type(),
|
||||
NodeType::Element(ElementNode { tag: root_tag, .. })
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(unused_variables)]
|
||||
fn persist_instertions_before() {
|
||||
use crate::dioxus::DioxusState;
|
||||
use crate::prelude::*;
|
||||
use dioxus::prelude::*;
|
||||
#[allow(non_snake_case)]
|
||||
fn Base(cx: Scope) -> Element {
|
||||
|
@ -447,12 +401,13 @@ fn persist_instertions_before() {
|
|||
}
|
||||
let mut vdom = VirtualDom::new(Base);
|
||||
|
||||
let mut rdom: RealDom<Empty> = RealDom::new();
|
||||
let mut rdom: RealDom = RealDom::new([]);
|
||||
let mut dioxus_state = DioxusState::create(&mut rdom);
|
||||
|
||||
let build = vdom.rebuild();
|
||||
let _to_update = rdom.apply_mutations(build);
|
||||
dioxus_state.apply_mutations(&mut rdom, build);
|
||||
|
||||
let mut iter = PersistantElementIter::new();
|
||||
let mut iter = PersistantElementIter::create(&mut rdom);
|
||||
// div
|
||||
iter.next(&rdom).id();
|
||||
// p
|
||||
|
@ -466,20 +421,21 @@ fn persist_instertions_before() {
|
|||
|
||||
vdom.mark_dirty(ScopeId(0));
|
||||
let update = vdom.render_immediate();
|
||||
iter.prune(&update, &rdom);
|
||||
let _to_update = rdom.apply_mutations(update);
|
||||
dioxus_state.apply_mutations(&mut rdom, update);
|
||||
|
||||
let p_tag = "div".to_string();
|
||||
let idx = iter.next(&rdom).id();
|
||||
assert!(matches!(
|
||||
&rdom[idx].node_data.node_type,
|
||||
NodeType::Element { tag: p_tag, .. }
|
||||
&*rdom.get(idx).unwrap().node_type(),
|
||||
NodeType::Element(ElementNode { tag: p_tag, .. })
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(unused_variables)]
|
||||
fn persist_instertions_after() {
|
||||
use crate::dioxus::DioxusState;
|
||||
use crate::prelude::*;
|
||||
use dioxus::prelude::*;
|
||||
#[allow(non_snake_case)]
|
||||
fn Base(cx: Scope) -> Element {
|
||||
|
@ -503,12 +459,13 @@ fn persist_instertions_after() {
|
|||
}
|
||||
let mut vdom = VirtualDom::new(Base);
|
||||
|
||||
let mut rdom: RealDom<Empty> = RealDom::new();
|
||||
let mut rdom: RealDom = RealDom::new([]);
|
||||
let mut iter = PersistantElementIter::create(&mut rdom);
|
||||
let mut dioxus_state = DioxusState::create(&mut rdom);
|
||||
|
||||
let build = vdom.rebuild();
|
||||
let _to_update = rdom.apply_mutations(build);
|
||||
dioxus_state.apply_mutations(&mut rdom, build);
|
||||
|
||||
let mut iter = PersistantElementIter::new();
|
||||
// div
|
||||
iter.next(&rdom).id();
|
||||
// p
|
||||
|
@ -521,19 +478,18 @@ fn persist_instertions_after() {
|
|||
iter.next(&rdom).id();
|
||||
|
||||
let update = vdom.rebuild();
|
||||
iter.prune(&update, &rdom);
|
||||
let _to_update = rdom.apply_mutations(update);
|
||||
dioxus_state.apply_mutations(&mut rdom, update);
|
||||
|
||||
let p_tag = "p".to_string();
|
||||
let idx = iter.next(&rdom).id();
|
||||
assert!(matches!(
|
||||
&rdom[idx].node_data.node_type,
|
||||
NodeType::Element { tag: p_tag, .. }
|
||||
&*rdom.get(idx).unwrap().node_type(),
|
||||
NodeType::Element(ElementNode { tag: p_tag, .. })
|
||||
));
|
||||
let text = "hello world".to_string();
|
||||
let idx = iter.next(&rdom).id();
|
||||
assert!(matches!(
|
||||
&rdom[idx].node_data.node_type,
|
||||
NodeType::Text { text, .. }
|
||||
&*rdom.get(idx).unwrap().node_type(),
|
||||
NodeType::Text(text)
|
||||
));
|
||||
}
|
||||
|
|
237
packages/native-core/tests/called_minimally_on_build.rs
Normal file
237
packages/native-core/tests/called_minimally_on_build.rs
Normal file
|
@ -0,0 +1,237 @@
|
|||
use dioxus::prelude::*;
|
||||
use dioxus_native_core::prelude::*;
|
||||
use dioxus_native_core_macro::partial_derive_state;
|
||||
use shipyard::Component;
|
||||
|
||||
macro_rules! dep {
|
||||
( child( $name:ty, $dep:ty ) ) => {
|
||||
#[partial_derive_state]
|
||||
impl State for $name {
|
||||
type ParentDependencies = ();
|
||||
type ChildDependencies = $dep;
|
||||
type NodeDependencies = ();
|
||||
|
||||
const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::ALL;
|
||||
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
_: NodeView,
|
||||
_: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
|
||||
_: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_: &SendAnyMap,
|
||||
) -> bool {
|
||||
self.0 += 1;
|
||||
true
|
||||
}
|
||||
|
||||
fn create<'a>(
|
||||
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,
|
||||
) -> Self {
|
||||
let mut myself = Self::default();
|
||||
myself.update(node_view, node, parent, children, context);
|
||||
myself
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
( parent( $name:ty, $dep:ty ) ) => {
|
||||
#[partial_derive_state]
|
||||
impl State for $name {
|
||||
type ParentDependencies = $dep;
|
||||
type ChildDependencies = ();
|
||||
type NodeDependencies = ();
|
||||
|
||||
const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::ALL;
|
||||
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
_: NodeView,
|
||||
_: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
|
||||
_: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_: &SendAnyMap,
|
||||
) -> bool {
|
||||
self.0 += 1;
|
||||
true
|
||||
}
|
||||
|
||||
fn create<'a>(
|
||||
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,
|
||||
) -> Self {
|
||||
let mut myself = Self::default();
|
||||
myself.update(node_view, node, parent, children, context);
|
||||
myself
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
( node( $name:ty, $dep:ty ) ) => {
|
||||
#[partial_derive_state]
|
||||
impl State for $name {
|
||||
type ParentDependencies = $dep;
|
||||
type ChildDependencies = ();
|
||||
type NodeDependencies = ();
|
||||
|
||||
const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::ALL;
|
||||
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
_: NodeView,
|
||||
_: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
|
||||
_: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_: &SendAnyMap,
|
||||
) -> bool {
|
||||
self.0 += 1;
|
||||
true
|
||||
}
|
||||
|
||||
fn create<'a>(
|
||||
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,
|
||||
) -> Self {
|
||||
let mut myself = Self::default();
|
||||
myself.update(node_view, node, parent, children, context);
|
||||
myself
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! test_state{
|
||||
( state: ( $( $state:ty ),* ) ) => {
|
||||
#[test]
|
||||
fn state_reduce_initally_called_minimally() {
|
||||
#[allow(non_snake_case)]
|
||||
fn Base(cx: Scope) -> Element {
|
||||
render!{
|
||||
div {
|
||||
div{
|
||||
div{
|
||||
p{}
|
||||
}
|
||||
p{
|
||||
"hello"
|
||||
}
|
||||
div{
|
||||
h1{}
|
||||
}
|
||||
p{
|
||||
"world"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut vdom = VirtualDom::new(Base);
|
||||
|
||||
let mutations = vdom.rebuild();
|
||||
|
||||
let mut dom: RealDom = RealDom::new([$( <$state>::to_type_erased() ),*]);
|
||||
let mut dioxus_state = DioxusState::create(&mut dom);
|
||||
|
||||
dioxus_state.apply_mutations(&mut dom, mutations);
|
||||
dom.update_state(SendAnyMap::new());
|
||||
|
||||
dom.traverse_depth_first(|n| {
|
||||
$(
|
||||
assert_eq!(n.get::<$state>().unwrap().0, 1);
|
||||
)*
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod node_depends_on_child_and_parent {
|
||||
use super::*;
|
||||
#[derive(Debug, Clone, Default, PartialEq, Component)]
|
||||
struct Node(i32);
|
||||
dep!(node(Node, (Child, Parent)));
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Component)]
|
||||
struct Child(i32);
|
||||
dep!(child(Child, (Child,)));
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Component)]
|
||||
struct Parent(i32);
|
||||
dep!(parent(Parent, (Parent,)));
|
||||
|
||||
test_state!(state: (Child, Node, Parent));
|
||||
}
|
||||
|
||||
mod child_depends_on_node_that_depends_on_parent {
|
||||
use super::*;
|
||||
#[derive(Debug, Clone, Default, PartialEq, Component)]
|
||||
struct Node(i32);
|
||||
dep!(node(Node, (Parent,)));
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Component)]
|
||||
struct Child(i32);
|
||||
dep!(child(Child, (Node,)));
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Component)]
|
||||
struct Parent(i32);
|
||||
dep!(parent(Parent, (Parent,)));
|
||||
|
||||
test_state!(state: (Child, Node, Parent));
|
||||
}
|
||||
|
||||
mod parent_depends_on_node_that_depends_on_child {
|
||||
use super::*;
|
||||
#[derive(Debug, Clone, Default, PartialEq, Component)]
|
||||
struct Node(i32);
|
||||
dep!(node(Node, (Child,)));
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Component)]
|
||||
struct Child(i32);
|
||||
dep!(child(Child, (Child,)));
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Component)]
|
||||
struct Parent(i32);
|
||||
dep!(parent(Parent, (Node,)));
|
||||
|
||||
test_state!(state: (Child, Node, Parent));
|
||||
}
|
||||
|
||||
mod node_depends_on_other_node_state {
|
||||
use super::*;
|
||||
#[derive(Debug, Clone, Default, PartialEq, Component)]
|
||||
struct Node1(i32);
|
||||
dep!(node(Node1, (Node2,)));
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Component)]
|
||||
struct Node2(i32);
|
||||
dep!(node(Node2, ()));
|
||||
|
||||
test_state!(state: (Node1, Node2));
|
||||
}
|
||||
|
||||
mod node_child_and_parent_state_depends_on_self {
|
||||
use super::*;
|
||||
#[derive(Debug, Clone, Default, PartialEq, Component)]
|
||||
struct Node(i32);
|
||||
dep!(node(Node, ()));
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Component)]
|
||||
struct Child(i32);
|
||||
dep!(child(Child, (Child,)));
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Component)]
|
||||
struct Parent(i32);
|
||||
dep!(parent(Parent, (Parent,)));
|
||||
|
||||
test_state!(state: (Child, Node, Parent));
|
||||
}
|
|
@ -1,12 +1,8 @@
|
|||
use dioxus::prelude::Props;
|
||||
use dioxus_core::*;
|
||||
use dioxus_native_core::{
|
||||
node_ref::{AttributeMask, NodeView},
|
||||
real_dom::RealDom,
|
||||
state::{ParentDepState, State},
|
||||
NodeMask, SendAnyMap,
|
||||
};
|
||||
use dioxus_native_core_macro::{sorted_str_slice, State};
|
||||
use dioxus_native_core::prelude::*;
|
||||
use dioxus_native_core_macro::partial_derive_state;
|
||||
use shipyard::Component;
|
||||
use std::cell::Cell;
|
||||
|
||||
fn random_ns() -> Option<&'static str> {
|
||||
|
@ -232,7 +228,7 @@ fn create_random_dynamic_attr(cx: &ScopeState) -> Attribute {
|
|||
|
||||
static mut TEMPLATE_COUNT: usize = 0;
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
#[derive(PartialEq, Props, Component)]
|
||||
struct DepthProps {
|
||||
depth: usize,
|
||||
root: bool,
|
||||
|
@ -294,26 +290,48 @@ fn create_random_element(cx: Scope<DepthProps>) -> Element {
|
|||
node
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
pub struct BlablaState {}
|
||||
|
||||
/// Font style are inherited by default if not specified otherwise by some of the supported attributes.
|
||||
impl ParentDepState for BlablaState {
|
||||
type Ctx = ();
|
||||
type DepState = (Self,);
|
||||
|
||||
const NODE_MASK: NodeMask =
|
||||
NodeMask::new_with_attrs(AttributeMask::Static(&sorted_str_slice!(["blabla",])));
|
||||
|
||||
fn reduce(&mut self, _node: NodeView, _parent: Option<(&Self,)>, _ctx: &Self::Ctx) -> bool {
|
||||
false
|
||||
}
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Component)]
|
||||
pub struct BlablaState {
|
||||
count: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, State, Default, Debug)]
|
||||
pub struct NodeState {
|
||||
#[parent_dep_state(blabla)]
|
||||
blabla: BlablaState,
|
||||
#[partial_derive_state]
|
||||
impl State for BlablaState {
|
||||
type ParentDependencies = (Self,);
|
||||
type ChildDependencies = ();
|
||||
type NodeDependencies = ();
|
||||
|
||||
const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new()
|
||||
.with_attrs(AttributeMaskBuilder::Some(&["blabla"]))
|
||||
.with_element();
|
||||
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
_: NodeView,
|
||||
_: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
|
||||
parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_: &SendAnyMap,
|
||||
) -> bool {
|
||||
if let Some((parent,)) = parent {
|
||||
if parent.count != 0 {
|
||||
self.count += 1;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn create<'a>(
|
||||
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,
|
||||
) -> Self {
|
||||
let mut myself = Self::default();
|
||||
myself.update(node_view, node, parent, children, context);
|
||||
myself
|
||||
}
|
||||
}
|
||||
|
||||
// test for panics when creating random nodes and templates
|
||||
|
@ -328,11 +346,12 @@ fn create() {
|
|||
},
|
||||
);
|
||||
let mutations = vdom.rebuild();
|
||||
let mut rdom: RealDom<NodeState> = RealDom::new();
|
||||
let (to_update, _diff) = rdom.apply_mutations(mutations);
|
||||
let mut rdom: RealDom = RealDom::new([BlablaState::to_type_erased()]);
|
||||
let mut dioxus_state = DioxusState::create(&mut rdom);
|
||||
dioxus_state.apply_mutations(&mut rdom, mutations);
|
||||
|
||||
let ctx = SendAnyMap::new();
|
||||
rdom.update_state(to_update, ctx);
|
||||
rdom.update_state(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -349,17 +368,18 @@ fn diff() {
|
|||
},
|
||||
);
|
||||
let mutations = vdom.rebuild();
|
||||
let mut rdom: RealDom<NodeState> = RealDom::new();
|
||||
let (to_update, _diff) = rdom.apply_mutations(mutations);
|
||||
let mut rdom: RealDom = RealDom::new([BlablaState::to_type_erased()]);
|
||||
let mut dioxus_state = DioxusState::create(&mut rdom);
|
||||
dioxus_state.apply_mutations(&mut rdom, mutations);
|
||||
|
||||
let ctx = SendAnyMap::new();
|
||||
rdom.update_state(to_update, ctx);
|
||||
rdom.update_state(ctx);
|
||||
for _ in 0..10 {
|
||||
let mutations = vdom.render_immediate();
|
||||
let (to_update, _diff) = rdom.apply_mutations(mutations);
|
||||
dioxus_state.apply_mutations(&mut rdom, mutations);
|
||||
|
||||
let ctx = SendAnyMap::new();
|
||||
rdom.update_state(to_update, ctx);
|
||||
rdom.update_state(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,34 +1,51 @@
|
|||
use dioxus::prelude::*;
|
||||
use dioxus_native_core::{
|
||||
node_ref::{AttributeMask, NodeView},
|
||||
real_dom::RealDom,
|
||||
state::{ParentDepState, State},
|
||||
NodeMask, SendAnyMap,
|
||||
};
|
||||
use dioxus_native_core_macro::{sorted_str_slice, State};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use dioxus_native_core::prelude::*;
|
||||
use dioxus_native_core_macro::partial_derive_state;
|
||||
use shipyard::Component;
|
||||
use tokio::time::sleep;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
pub struct BlablaState {}
|
||||
|
||||
/// Font style are inherited by default if not specified otherwise by some of the supported attributes.
|
||||
impl ParentDepState for BlablaState {
|
||||
type Ctx = ();
|
||||
type DepState = (Self,);
|
||||
|
||||
const NODE_MASK: NodeMask =
|
||||
NodeMask::new_with_attrs(AttributeMask::Static(&sorted_str_slice!(["blabla",])));
|
||||
|
||||
fn reduce(&mut self, _node: NodeView, _parent: Option<(&Self,)>, _ctx: &Self::Ctx) -> bool {
|
||||
false
|
||||
}
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Component)]
|
||||
pub struct BlablaState {
|
||||
count: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, State, Default, Debug)]
|
||||
pub struct NodeState {
|
||||
#[parent_dep_state(blabla)]
|
||||
blabla: BlablaState,
|
||||
#[partial_derive_state]
|
||||
impl State for BlablaState {
|
||||
type ParentDependencies = (Self,);
|
||||
type ChildDependencies = ();
|
||||
type NodeDependencies = ();
|
||||
|
||||
const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new()
|
||||
.with_attrs(AttributeMaskBuilder::Some(&["blabla"]))
|
||||
.with_element();
|
||||
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
_: NodeView,
|
||||
_: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
|
||||
parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_: &SendAnyMap,
|
||||
) -> bool {
|
||||
if let Some((parent,)) = parent {
|
||||
if parent.count != 0 {
|
||||
self.count += 1;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn create<'a>(
|
||||
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,
|
||||
) -> Self {
|
||||
let mut myself = Self::default();
|
||||
myself.update(node_view, node, parent, children, context);
|
||||
myself
|
||||
}
|
||||
}
|
||||
|
||||
mod dioxus_elements {
|
||||
|
@ -76,6 +93,7 @@ mod dioxus_elements {
|
|||
|
||||
#[test]
|
||||
fn native_core_is_okay() {
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
|
@ -113,23 +131,24 @@ fn native_core_is_okay() {
|
|||
.unwrap();
|
||||
|
||||
rt.block_on(async {
|
||||
let rdom = Arc::new(Mutex::new(RealDom::<NodeState>::new()));
|
||||
let rdom = Arc::new(Mutex::new(RealDom::new([BlablaState::to_type_erased()])));
|
||||
let mut dioxus_state = DioxusState::create(&mut rdom.lock().unwrap());
|
||||
let mut dom = VirtualDom::new(app);
|
||||
|
||||
let muts = dom.rebuild();
|
||||
let (to_update, _diff) = rdom.lock().unwrap().apply_mutations(muts);
|
||||
let mutations = dom.rebuild();
|
||||
dioxus_state.apply_mutations(&mut rdom.lock().unwrap(), mutations);
|
||||
|
||||
let ctx = SendAnyMap::new();
|
||||
rdom.lock().unwrap().update_state(to_update, ctx);
|
||||
rdom.lock().unwrap().update_state(ctx);
|
||||
|
||||
for _ in 0..10 {
|
||||
dom.wait_for_work().await;
|
||||
|
||||
let mutations = dom.render_immediate();
|
||||
let (to_update, _diff) = rdom.lock().unwrap().apply_mutations(mutations);
|
||||
dioxus_state.apply_mutations(&mut rdom.lock().unwrap(), mutations);
|
||||
|
||||
let ctx = SendAnyMap::new();
|
||||
rdom.lock().unwrap().update_state(to_update, ctx);
|
||||
rdom.lock().unwrap().update_state(ctx);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
440
packages/native-core/tests/passes.rs
Normal file
440
packages/native-core/tests/passes.rs
Normal file
|
@ -0,0 +1,440 @@
|
|||
use dioxus_native_core::node::NodeType;
|
||||
use dioxus_native_core::prelude::*;
|
||||
use dioxus_native_core_macro::partial_derive_state;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use shipyard::Component;
|
||||
|
||||
fn create_blank_element() -> NodeType {
|
||||
NodeType::Element(ElementNode {
|
||||
tag: "div".to_owned(),
|
||||
namespace: None,
|
||||
attributes: FxHashMap::default(),
|
||||
listeners: FxHashSet::default(),
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn node_pass() {
|
||||
#[derive(Debug, Default, Clone, PartialEq, Component)]
|
||||
struct Number(i32);
|
||||
|
||||
#[partial_derive_state]
|
||||
impl State for Number {
|
||||
type ChildDependencies = ();
|
||||
type NodeDependencies = ();
|
||||
type ParentDependencies = ();
|
||||
const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new();
|
||||
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
_: NodeView,
|
||||
_: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
|
||||
_: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_: &SendAnyMap,
|
||||
) -> bool {
|
||||
self.0 += 1;
|
||||
true
|
||||
}
|
||||
|
||||
fn create<'a>(
|
||||
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,
|
||||
) -> Self {
|
||||
let mut myself = Self::default();
|
||||
myself.update(node_view, node, parent, children, context);
|
||||
myself
|
||||
}
|
||||
}
|
||||
|
||||
let mut tree: RealDom = RealDom::new([Number::to_type_erased()]);
|
||||
tree.update_state(SendAnyMap::new());
|
||||
|
||||
assert_eq!(
|
||||
tree.get(tree.root_id()).unwrap().get().as_deref(),
|
||||
Some(&Number(1))
|
||||
);
|
||||
|
||||
// mark the node as dirty
|
||||
tree.get_mut(tree.root_id()).unwrap().get_mut::<Number>();
|
||||
|
||||
tree.update_state(SendAnyMap::new());
|
||||
assert_eq!(
|
||||
tree.get(tree.root_id()).unwrap().get().as_deref(),
|
||||
Some(&Number(2))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dependant_node_pass() {
|
||||
#[derive(Debug, Default, Clone, PartialEq, Component)]
|
||||
struct AddNumber(i32);
|
||||
|
||||
#[partial_derive_state]
|
||||
impl State for AddNumber {
|
||||
type ChildDependencies = ();
|
||||
type NodeDependencies = (SubtractNumber,);
|
||||
type ParentDependencies = ();
|
||||
const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new();
|
||||
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
_: NodeView,
|
||||
_: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
|
||||
_: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_: &SendAnyMap,
|
||||
) -> bool {
|
||||
self.0 += 1;
|
||||
true
|
||||
}
|
||||
|
||||
fn create<'a>(
|
||||
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,
|
||||
) -> Self {
|
||||
let mut myself = Self::default();
|
||||
myself.update(node_view, node, parent, children, context);
|
||||
myself
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Component)]
|
||||
struct SubtractNumber(i32);
|
||||
|
||||
#[partial_derive_state]
|
||||
impl State for SubtractNumber {
|
||||
type ChildDependencies = ();
|
||||
type NodeDependencies = ();
|
||||
type ParentDependencies = ();
|
||||
const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new();
|
||||
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
_: NodeView,
|
||||
_: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
|
||||
_: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_: &SendAnyMap,
|
||||
) -> bool {
|
||||
self.0 -= 1;
|
||||
true
|
||||
}
|
||||
|
||||
fn create<'a>(
|
||||
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,
|
||||
) -> Self {
|
||||
let mut myself = Self::default();
|
||||
myself.update(node_view, node, parent, children, context);
|
||||
myself
|
||||
}
|
||||
}
|
||||
|
||||
let mut tree: RealDom = RealDom::new([
|
||||
AddNumber::to_type_erased(),
|
||||
SubtractNumber::to_type_erased(),
|
||||
]);
|
||||
tree.update_state(SendAnyMap::new());
|
||||
|
||||
let root = tree.get(tree.root_id()).unwrap();
|
||||
assert_eq!(root.get().as_deref(), Some(&AddNumber(1)));
|
||||
assert_eq!(root.get().as_deref(), Some(&SubtractNumber(-1)));
|
||||
|
||||
// mark the subtract state as dirty, it should update the add state
|
||||
tree.get_mut(tree.root_id())
|
||||
.unwrap()
|
||||
.get_mut::<SubtractNumber>();
|
||||
|
||||
tree.update_state(SendAnyMap::new());
|
||||
let root = tree.get(tree.root_id()).unwrap();
|
||||
assert_eq!(root.get().as_deref(), Some(&AddNumber(2)));
|
||||
assert_eq!(root.get().as_deref(), Some(&SubtractNumber(-2)));
|
||||
|
||||
// mark the add state as dirty, it should ~not~ update the subtract state
|
||||
tree.get_mut(tree.root_id()).unwrap().get_mut::<AddNumber>();
|
||||
|
||||
tree.update_state(SendAnyMap::new());
|
||||
let root = tree.get(tree.root_id()).unwrap();
|
||||
assert_eq!(root.get().as_deref(), Some(&AddNumber(3)));
|
||||
assert_eq!(root.get().as_deref(), Some(&SubtractNumber(-2)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn independant_node_pass() {
|
||||
#[derive(Debug, Default, Clone, PartialEq, Component)]
|
||||
struct AddNumber(i32);
|
||||
|
||||
#[partial_derive_state]
|
||||
impl State for AddNumber {
|
||||
type ChildDependencies = ();
|
||||
type NodeDependencies = ();
|
||||
type ParentDependencies = ();
|
||||
|
||||
const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new();
|
||||
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
_: NodeView,
|
||||
_: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
|
||||
_: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_: &SendAnyMap,
|
||||
) -> bool {
|
||||
self.0 += 1;
|
||||
true
|
||||
}
|
||||
|
||||
fn create<'a>(
|
||||
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,
|
||||
) -> Self {
|
||||
let mut myself = Self::default();
|
||||
myself.update(node_view, node, parent, children, context);
|
||||
myself
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Component)]
|
||||
struct SubtractNumber(i32);
|
||||
|
||||
#[partial_derive_state]
|
||||
impl State for SubtractNumber {
|
||||
type ChildDependencies = ();
|
||||
type NodeDependencies = ();
|
||||
type ParentDependencies = ();
|
||||
|
||||
const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new();
|
||||
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
_: NodeView,
|
||||
_: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
|
||||
_: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_: &SendAnyMap,
|
||||
) -> bool {
|
||||
self.0 -= 1;
|
||||
true
|
||||
}
|
||||
|
||||
fn create<'a>(
|
||||
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,
|
||||
) -> Self {
|
||||
let mut myself = Self::default();
|
||||
myself.update(node_view, node, parent, children, context);
|
||||
myself
|
||||
}
|
||||
}
|
||||
|
||||
let mut tree: RealDom = RealDom::new([
|
||||
AddNumber::to_type_erased(),
|
||||
SubtractNumber::to_type_erased(),
|
||||
]);
|
||||
tree.update_state(SendAnyMap::new());
|
||||
|
||||
let root = tree.get(tree.root_id()).unwrap();
|
||||
assert_eq!(root.get().as_deref(), Some(&AddNumber(1)));
|
||||
assert_eq!(root.get().as_deref(), Some(&SubtractNumber(-1)));
|
||||
|
||||
// mark the subtract state as dirty, it should ~not~ update the add state
|
||||
tree.get_mut(tree.root_id())
|
||||
.unwrap()
|
||||
.get_mut::<SubtractNumber>();
|
||||
|
||||
tree.update_state(SendAnyMap::new());
|
||||
|
||||
let root = tree.get(tree.root_id()).unwrap();
|
||||
assert_eq!(root.get().as_deref(), Some(&AddNumber(1)));
|
||||
assert_eq!(root.get().as_deref(), Some(&SubtractNumber(-2)));
|
||||
|
||||
// mark the add state as dirty, it should ~not~ update the subtract state
|
||||
tree.get_mut(tree.root_id()).unwrap().get_mut::<AddNumber>();
|
||||
|
||||
tree.update_state(SendAnyMap::new());
|
||||
|
||||
let root = tree.get(tree.root_id()).unwrap();
|
||||
assert_eq!(root.get().as_deref(), Some(&AddNumber(2)));
|
||||
assert_eq!(root.get().as_deref(), Some(&SubtractNumber(-2)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn down_pass() {
|
||||
#[derive(Debug, Clone, PartialEq, Component)]
|
||||
struct AddNumber(i32);
|
||||
|
||||
impl Default for AddNumber {
|
||||
fn default() -> Self {
|
||||
Self(1)
|
||||
}
|
||||
}
|
||||
|
||||
#[partial_derive_state]
|
||||
impl State for AddNumber {
|
||||
type ChildDependencies = ();
|
||||
type NodeDependencies = ();
|
||||
type ParentDependencies = (AddNumber,);
|
||||
|
||||
const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new();
|
||||
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
_: NodeView,
|
||||
_: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
|
||||
parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_: &SendAnyMap,
|
||||
) -> bool {
|
||||
if let Some((parent,)) = parent {
|
||||
self.0 += parent.0;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn create<'a>(
|
||||
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,
|
||||
) -> Self {
|
||||
let mut myself = Self::default();
|
||||
myself.update(node_view, node, parent, children, context);
|
||||
myself
|
||||
}
|
||||
}
|
||||
|
||||
let mut tree: RealDom = RealDom::new([AddNumber::to_type_erased()]);
|
||||
let grandchild1 = tree.create_node(create_blank_element());
|
||||
let grandchild1 = grandchild1.id();
|
||||
let mut child1 = tree.create_node(create_blank_element());
|
||||
child1.add_child(grandchild1);
|
||||
let child1 = child1.id();
|
||||
let grandchild2 = tree.create_node(create_blank_element());
|
||||
let grandchild2 = grandchild2.id();
|
||||
let mut child2 = tree.create_node(create_blank_element());
|
||||
child2.add_child(grandchild2);
|
||||
let child2 = child2.id();
|
||||
let mut parent = tree.get_mut(tree.root_id()).unwrap();
|
||||
parent.add_child(child1);
|
||||
parent.add_child(child2);
|
||||
|
||||
tree.update_state(SendAnyMap::new());
|
||||
|
||||
let root = tree.get(tree.root_id()).unwrap();
|
||||
dbg!(root.id());
|
||||
assert_eq!(root.get().as_deref(), Some(&AddNumber(1)));
|
||||
|
||||
let child1 = tree.get(child1).unwrap();
|
||||
dbg!(child1.id());
|
||||
assert_eq!(child1.get().as_deref(), Some(&AddNumber(2)));
|
||||
|
||||
let grandchild1 = tree.get(grandchild1).unwrap();
|
||||
assert_eq!(grandchild1.get().as_deref(), Some(&AddNumber(3)));
|
||||
|
||||
let child2 = tree.get(child2).unwrap();
|
||||
assert_eq!(child2.get().as_deref(), Some(&AddNumber(2)));
|
||||
|
||||
let grandchild2 = tree.get(grandchild2).unwrap();
|
||||
assert_eq!(grandchild2.get().as_deref(), Some(&AddNumber(3)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn up_pass() {
|
||||
// Tree before:
|
||||
// 1=\
|
||||
// 1=\
|
||||
// 1
|
||||
// 1=\
|
||||
// 1
|
||||
// Tree after:
|
||||
// 4=\
|
||||
// 2=\
|
||||
// 1
|
||||
// 2=\
|
||||
// 1
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Component)]
|
||||
struct AddNumber(i32);
|
||||
|
||||
#[partial_derive_state]
|
||||
impl State for AddNumber {
|
||||
type ChildDependencies = (AddNumber,);
|
||||
type NodeDependencies = ();
|
||||
type ParentDependencies = ();
|
||||
|
||||
const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new();
|
||||
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
_: NodeView,
|
||||
_: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
|
||||
_: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_: &SendAnyMap,
|
||||
) -> bool {
|
||||
self.0 += children.iter().map(|(i,)| i.0).sum::<i32>();
|
||||
true
|
||||
}
|
||||
|
||||
fn create<'a>(
|
||||
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,
|
||||
) -> Self {
|
||||
let mut myself = Self(1);
|
||||
myself.update(node_view, node, parent, children, context);
|
||||
myself
|
||||
}
|
||||
}
|
||||
|
||||
let mut tree: RealDom = RealDom::new([AddNumber::to_type_erased()]);
|
||||
let grandchild1 = tree.create_node(create_blank_element());
|
||||
let grandchild1 = grandchild1.id();
|
||||
let mut child1 = tree.create_node(create_blank_element());
|
||||
child1.add_child(grandchild1);
|
||||
let child1 = child1.id();
|
||||
let grandchild2 = tree.create_node(create_blank_element());
|
||||
let grandchild2 = grandchild2.id();
|
||||
let mut child2 = tree.create_node(create_blank_element());
|
||||
child2.add_child(grandchild2);
|
||||
let child2 = child2.id();
|
||||
let mut parent = tree.get_mut(tree.root_id()).unwrap();
|
||||
parent.add_child(child1);
|
||||
parent.add_child(child2);
|
||||
|
||||
tree.update_state(SendAnyMap::new());
|
||||
|
||||
let root = tree.get(tree.root_id()).unwrap();
|
||||
assert_eq!(root.get().as_deref(), Some(&AddNumber(5)));
|
||||
|
||||
let child1 = tree.get(child1).unwrap();
|
||||
assert_eq!(child1.get().as_deref(), Some(&AddNumber(2)));
|
||||
|
||||
let grandchild1 = tree.get(grandchild1).unwrap();
|
||||
assert_eq!(grandchild1.get().as_deref(), Some(&AddNumber(1)));
|
||||
|
||||
let child2 = tree.get(child2).unwrap();
|
||||
assert_eq!(child2.get().as_deref(), Some(&AddNumber(2)));
|
||||
|
||||
let grandchild2 = tree.get(grandchild2).unwrap();
|
||||
assert_eq!(grandchild2.get().as_deref(), Some(&AddNumber(1)));
|
||||
}
|
2
packages/rink/.gitignore
vendored
Normal file
2
packages/rink/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
Cargo.lock
|
2
packages/rink/.vscode/spellright.dict
vendored
Normal file
2
packages/rink/.vscode/spellright.dict
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
esque
|
||||
Tui
|
39
packages/rink/Cargo.toml
Normal file
39
packages/rink/Cargo.toml
Normal file
|
@ -0,0 +1,39 @@
|
|||
[package]
|
||||
name = "rink"
|
||||
version = "0.2.2"
|
||||
authors = ["Jonathan Kelley, @dementhos"]
|
||||
edition = "2021"
|
||||
description = "TUI-based renderer for Dioxus"
|
||||
repository = "https://github.com/DioxusLabs/dioxus/"
|
||||
homepage = "https://dioxuslabs.com"
|
||||
documentation = "https://dioxuslabs.com"
|
||||
keywords = ["dom", "ui", "gui", "react", "terminal"]
|
||||
license = "MIT/Apache-2.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus-html = { path = "../html", version = "^0.3.0" }
|
||||
dioxus-native-core = { path = "../native-core", version = "^0.2.0" }
|
||||
dioxus-native-core-macro = { path = "../native-core-macro", version = "^0.3.0" }
|
||||
|
||||
tui = "0.17.0"
|
||||
crossterm = "0.23.0"
|
||||
anyhow = "1.0.42"
|
||||
tokio = { version = "1.15.0", features = ["full"] }
|
||||
futures = "0.3.19"
|
||||
taffy = "0.2.1"
|
||||
smallvec = "1.6"
|
||||
rustc-hash = "1.1.0"
|
||||
anymap = "1.0.0-beta.2"
|
||||
futures-channel = "0.3.25"
|
||||
shipyard = { version = "0.6.2", features = ["proc", "std"], default-features = false }
|
||||
once_cell = "1.17.1"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1" }
|
||||
criterion = "0.3.5"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
parallel = ["shipyard/parallel"]
|
74
packages/rink/README.md
Normal file
74
packages/rink/README.md
Normal file
|
@ -0,0 +1,74 @@
|
|||
<div align="center">
|
||||
<h1>Rink</h1>
|
||||
<p>
|
||||
<strong>A beautiful terminal user interfaces library in Rust.</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<!-- Crates version -->
|
||||
<a href="https://crates.io/crates/rink">
|
||||
<img src="https://img.shields.io/crates/v/rink.svg?style=flat-square"
|
||||
alt="Crates.io version" />
|
||||
</a>
|
||||
<!-- Downloads -->
|
||||
<a href="https://crates.io/crates/rink">
|
||||
<img src="https://img.shields.io/crates/d/rink.svg?style=flat-square"
|
||||
alt="Download" />
|
||||
</a>
|
||||
<!-- docs -->
|
||||
<a href="https://docs.rs/rink">
|
||||
<img src="https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square"
|
||||
alt="docs.rs docs" />
|
||||
</a>
|
||||
<!-- CI -->
|
||||
<a href="https://github.com/jkelleyrtp/rink/actions">
|
||||
<img src="https://github.com/dioxuslabs/rink/actions/workflows/main.yml/badge.svg"
|
||||
alt="CI status" />
|
||||
</a>
|
||||
<!-- Discord -->
|
||||
<a href="https://discord.gg/XgGxMSkvUM">
|
||||
<img src="https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square" alt="Discord Link" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
Leverage CSS, HTML, and Rust to build beautiful, portable, terminal user interfaces. Rink is the cross-framework library that powers [`Dioxus-TUI`](https://github.com/DioxusLabs/dioxus/tree/master/packages/dioxus-tui)
|
||||
|
||||
![demo app](examples/example.png)
|
||||
|
||||
## Background
|
||||
|
||||
You can use Html-like semantics with inline styles, tree hierarchy, components, and more in your [`text-based user interface (TUI)`](https://en.wikipedia.org/wiki/Text-based_user_interface) application.
|
||||
|
||||
Rink is essentially a port of [Ink](https://github.com/vadimdemedes/ink) but for [`Rust`](https://www.rust-lang.org/). Rink doesn't depend on Node.js or any other JavaScript runtime, so your binaries are portable and beautiful.
|
||||
|
||||
## Limitations
|
||||
|
||||
- **Subset of Html**
|
||||
Terminals can only render a subset of HTML. We support as much as we can.
|
||||
- **Particular frontend design**
|
||||
Terminals and browsers are and look different. Therefore, the same design might not be the best to cover both renderers.
|
||||
|
||||
## Status
|
||||
|
||||
**WARNING: Rink is currently under construction!**
|
||||
|
||||
Rendering a Dom works fine, but the ecosystem of widgets is not ready yet. Additionally, some bugs in the flexbox implementation might be quirky at times.
|
||||
|
||||
## Features
|
||||
|
||||
Rink features:
|
||||
|
||||
- [x] Flexbox-based layout system
|
||||
- [ ] CSS selectors
|
||||
- [x] inline CSS support
|
||||
- [x] Built-in focusing system
|
||||
|
||||
* [ ] Widgets
|
||||
* [ ] Support for events, hooks, and callbacks<sup>1</sup>
|
||||
* [ ] Html tags<sup>2</sup>
|
||||
|
||||
<sup>1</sup> Basic keyboard, mouse, and focus events are implemented.
|
||||
<sup>2</sup> Currently, most HTML tags don't translate into any meaning inside of Rink. So an `input` _element_ won't mean anything nor does it have any additional functionality.
|
104
packages/rink/examples/counter.rs
Normal file
104
packages/rink/examples/counter.rs
Normal file
|
@ -0,0 +1,104 @@
|
|||
use dioxus_html::EventData;
|
||||
use dioxus_native_core::{
|
||||
node::TextNode,
|
||||
prelude::*,
|
||||
real_dom::{NodeImmutable, NodeTypeMut},
|
||||
NodeId,
|
||||
};
|
||||
use rink::{render, Config, Driver};
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
#[derive(Default)]
|
||||
struct Counter {
|
||||
count: usize,
|
||||
counter_id: NodeId,
|
||||
button_id: NodeId,
|
||||
}
|
||||
|
||||
impl Counter {
|
||||
fn create(mut root: NodeMut) -> Self {
|
||||
let mut myself = Self::default();
|
||||
|
||||
let root_id = root.id();
|
||||
let rdom = root.real_dom_mut();
|
||||
|
||||
// create the counter
|
||||
let count = myself.count;
|
||||
myself.counter_id = rdom
|
||||
.create_node(NodeType::Text(TextNode::new(count.to_string())))
|
||||
.id();
|
||||
let mut button = rdom.create_node(NodeType::Element(ElementNode {
|
||||
tag: "div".to_string(),
|
||||
attributes: [
|
||||
("display".to_string().into(), "flex".to_string().into()),
|
||||
(
|
||||
("background-color", "style").into(),
|
||||
format!("rgb({}, {}, {})", count * 10, 0, 0,).into(),
|
||||
),
|
||||
(("width", "style").into(), "100%".to_string().into()),
|
||||
(("height", "style").into(), "100%".to_string().into()),
|
||||
(("flex-direction", "style").into(), "row".to_string().into()),
|
||||
(
|
||||
("justify-content", "style").into(),
|
||||
"center".to_string().into(),
|
||||
),
|
||||
(("align-items", "style").into(), "center".to_string().into()),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}));
|
||||
button.add_event_listener("click");
|
||||
button.add_event_listener("wheel");
|
||||
button.add_child(myself.counter_id);
|
||||
myself.button_id = button.id();
|
||||
rdom.get_mut(root_id).unwrap().add_child(myself.button_id);
|
||||
|
||||
myself
|
||||
}
|
||||
}
|
||||
|
||||
impl Driver for Counter {
|
||||
fn update(&mut self, rdom: &Arc<RwLock<RealDom>>) {
|
||||
// update the counter
|
||||
let mut rdom = rdom.write().unwrap();
|
||||
let mut node = rdom.get_mut(self.button_id).unwrap();
|
||||
if let NodeTypeMut::Element(mut el) = node.node_type_mut() {
|
||||
el.set_attribute(
|
||||
("background-color", "style"),
|
||||
format!("rgb({}, {}, {})", self.count * 10, 0, 0,),
|
||||
);
|
||||
}
|
||||
let mut text = rdom.get_mut(self.counter_id).unwrap();
|
||||
let type_mut = text.node_type_mut();
|
||||
if let NodeTypeMut::Text(mut text) = type_mut {
|
||||
*text = self.count.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_event(
|
||||
&mut self,
|
||||
_: &Arc<RwLock<RealDom>>,
|
||||
_: NodeId,
|
||||
_: &str,
|
||||
_: Rc<EventData>,
|
||||
_: bool,
|
||||
) {
|
||||
// when a click or wheel event is fired, increment the counter
|
||||
self.count += 1;
|
||||
}
|
||||
|
||||
fn poll_async(&mut self) -> std::pin::Pin<Box<dyn futures::Future<Output = ()> + '_>> {
|
||||
Box::pin(async move { tokio::time::sleep(std::time::Duration::from_millis(1000)).await })
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
render(Config::new(), |rdom, _, _| {
|
||||
let mut rdom = rdom.write().unwrap();
|
||||
let root = rdom.root_id();
|
||||
Counter::create(rdom.get_mut(root).unwrap())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
BIN
packages/rink/examples/example.png
Normal file
BIN
packages/rink/examples/example.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 105 KiB |
173
packages/rink/examples/grid.rs
Normal file
173
packages/rink/examples/grid.rs
Normal file
|
@ -0,0 +1,173 @@
|
|||
use dioxus_html::EventData;
|
||||
use dioxus_native_core::{
|
||||
node::TextNode,
|
||||
prelude::*,
|
||||
real_dom::{NodeImmutable, NodeTypeMut},
|
||||
NodeId,
|
||||
};
|
||||
use rink::{render, Config, Driver};
|
||||
use rustc_hash::FxHashSet;
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
const SIZE: usize = 20;
|
||||
|
||||
#[derive(Default, Clone, Copy)]
|
||||
struct Node {
|
||||
container_id: Option<NodeId>,
|
||||
text_id: Option<NodeId>,
|
||||
count: usize,
|
||||
}
|
||||
|
||||
struct Test {
|
||||
node_states: [[Node; SIZE]; SIZE],
|
||||
dirty: FxHashSet<(usize, usize)>,
|
||||
}
|
||||
|
||||
impl Default for Test {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
node_states: [[Node {
|
||||
container_id: None,
|
||||
text_id: None,
|
||||
count: 0,
|
||||
}; SIZE]; SIZE],
|
||||
dirty: FxHashSet::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Test {
|
||||
fn create(mut root: NodeMut) -> Self {
|
||||
let mut myself = Self::default();
|
||||
|
||||
// Set the root node to be a flexbox with a column direction.
|
||||
if let NodeTypeMut::Element(mut el) = root.node_type_mut() {
|
||||
el.set_attribute("display".to_string(), "flex".to_string());
|
||||
el.set_attribute(("flex-direction", "style"), "column".to_string());
|
||||
el.set_attribute(("width", "style"), "100%".to_string());
|
||||
el.set_attribute(("height", "style"), "100%".to_string());
|
||||
}
|
||||
|
||||
let root_id = root.id();
|
||||
let rdom = root.real_dom_mut();
|
||||
|
||||
// create the grid
|
||||
for (x, row) in myself.node_states.iter_mut().enumerate() {
|
||||
let row_node = rdom
|
||||
.create_node(NodeType::Element(ElementNode {
|
||||
tag: "div".to_string(),
|
||||
attributes: [
|
||||
("display".to_string().into(), "flex".to_string().into()),
|
||||
(("flex-direction", "style").into(), "row".to_string().into()),
|
||||
(("width", "style").into(), "100%".to_string().into()),
|
||||
(("height", "style").into(), "100%".to_string().into()),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}))
|
||||
.id();
|
||||
for (y, node) in row.iter_mut().enumerate() {
|
||||
let count = node.count;
|
||||
let id = rdom
|
||||
.create_node(NodeType::Text(TextNode::new(count.to_string())))
|
||||
.id();
|
||||
let mut button = rdom.create_node(NodeType::Element(ElementNode {
|
||||
tag: "div".to_string(),
|
||||
attributes: [
|
||||
("display".to_string().into(), "flex".to_string().into()),
|
||||
(
|
||||
("background-color", "style").into(),
|
||||
format!("rgb({}, {}, {})", count * 10, 0, (x + y),).into(),
|
||||
),
|
||||
(("width", "style").into(), "100%".to_string().into()),
|
||||
(("height", "style").into(), "100%".to_string().into()),
|
||||
(("flex-direction", "style").into(), "row".to_string().into()),
|
||||
(
|
||||
("justify-content", "style").into(),
|
||||
"center".to_string().into(),
|
||||
),
|
||||
(("align-items", "style").into(), "center".to_string().into()),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}));
|
||||
button.add_event_listener("click");
|
||||
button.add_event_listener("wheel");
|
||||
button.add_child(id);
|
||||
let button_id = button.id();
|
||||
rdom.get_mut(row_node).unwrap().add_child(button_id);
|
||||
node.container_id = Some(button_id);
|
||||
node.text_id = Some(id);
|
||||
}
|
||||
rdom.get_mut(root_id).unwrap().add_child(row_node);
|
||||
}
|
||||
myself
|
||||
}
|
||||
}
|
||||
|
||||
impl Driver for Test {
|
||||
fn update(&mut self, rdom: &Arc<RwLock<RealDom>>) {
|
||||
let mut rdom = rdom.write().unwrap();
|
||||
for (x, y) in self.dirty.drain() {
|
||||
let node = self.node_states[x][y];
|
||||
let node_id = node.container_id.unwrap();
|
||||
let mut container = rdom.get_mut(node_id).unwrap();
|
||||
if let NodeTypeMut::Element(mut el) = container.node_type_mut() {
|
||||
el.set_attribute(
|
||||
("background-color", "style"),
|
||||
format!("rgb({}, {}, {})", node.count * 10, 0, (x + y),),
|
||||
);
|
||||
}
|
||||
let text_id = node.text_id.unwrap();
|
||||
let mut text = rdom.get_mut(text_id).unwrap();
|
||||
let type_mut = text.node_type_mut();
|
||||
if let NodeTypeMut::Text(mut text) = type_mut {
|
||||
*text = node.count.to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_event(
|
||||
&mut self,
|
||||
rdom: &Arc<RwLock<RealDom>>,
|
||||
id: NodeId,
|
||||
_: &str,
|
||||
_: Rc<EventData>,
|
||||
_: bool,
|
||||
) {
|
||||
let rdom = rdom.read().unwrap();
|
||||
let node = rdom.get(id).unwrap();
|
||||
if let Some(parent) = node.parent() {
|
||||
let child_number = parent
|
||||
.child_ids()
|
||||
.iter()
|
||||
.position(|id| *id == node.id())
|
||||
.unwrap();
|
||||
if let Some(parents_parent) = parent.parent() {
|
||||
let parents_child_number = parents_parent
|
||||
.child_ids()
|
||||
.iter()
|
||||
.position(|id| *id == parent.id())
|
||||
.unwrap();
|
||||
self.node_states[parents_child_number][child_number].count += 1;
|
||||
self.dirty.insert((parents_child_number, child_number));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_async(&mut self) -> std::pin::Pin<Box<dyn futures::Future<Output = ()> + '_>> {
|
||||
Box::pin(async move { tokio::time::sleep(std::time::Duration::from_millis(1000)).await })
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
render(Config::new(), |rdom, _, _| {
|
||||
let mut rdom = rdom.write().unwrap();
|
||||
let root = rdom.root_id();
|
||||
Test::create(rdom.get_mut(root).unwrap())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
|
@ -1,18 +1,23 @@
|
|||
use crate::{node::PreventDefault, TuiDom};
|
||||
use crate::prevent_default::PreventDefault;
|
||||
|
||||
use dioxus_native_core::{
|
||||
tree::TreeView,
|
||||
utils::{ElementProduced, PersistantElementIter},
|
||||
RealNodeId,
|
||||
node_ref::{AttributeMaskBuilder, NodeMaskBuilder},
|
||||
prelude::*,
|
||||
real_dom::NodeImmutable,
|
||||
utils::{IteratorMovement, PersistantElementIter},
|
||||
};
|
||||
use dioxus_native_core_macro::sorted_str_slice;
|
||||
use dioxus_native_core_macro::partial_derive_state;
|
||||
use once_cell::sync::Lazy;
|
||||
use rustc_hash::FxHashSet;
|
||||
use shipyard::Component;
|
||||
use shipyard::{Get, ViewMut};
|
||||
|
||||
use std::{cmp::Ordering, num::NonZeroU16};
|
||||
|
||||
use dioxus_native_core::{
|
||||
node_ref::{AttributeMask, NodeMask, NodeView},
|
||||
state::NodeDepState,
|
||||
};
|
||||
use dioxus_native_core::node_ref::NodeView;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Focused(pub bool);
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
|
||||
pub(crate) enum FocusLevel {
|
||||
|
@ -54,20 +59,31 @@ impl Ord for FocusLevel {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Default)]
|
||||
#[derive(Clone, PartialEq, Debug, Default, Component)]
|
||||
pub(crate) struct Focus {
|
||||
pub level: FocusLevel,
|
||||
}
|
||||
|
||||
impl NodeDepState for Focus {
|
||||
type DepState = ();
|
||||
type Ctx = ();
|
||||
const NODE_MASK: NodeMask =
|
||||
NodeMask::new_with_attrs(AttributeMask::Static(FOCUS_ATTRIBUTES)).with_listeners();
|
||||
#[partial_derive_state]
|
||||
impl State for Focus {
|
||||
const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new()
|
||||
.with_attrs(AttributeMaskBuilder::Some(FOCUS_ATTRIBUTES))
|
||||
.with_listeners();
|
||||
|
||||
fn reduce(&mut self, node: NodeView<'_>, _sibling: (), _: &Self::Ctx) -> bool {
|
||||
type ParentDependencies = ();
|
||||
type ChildDependencies = ();
|
||||
type NodeDependencies = ();
|
||||
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
node_view: NodeView,
|
||||
_: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
|
||||
_: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_: &SendAnyMap,
|
||||
) -> bool {
|
||||
let new = Focus {
|
||||
level: if let Some(a) = node
|
||||
level: if let Some(a) = node_view
|
||||
.attributes()
|
||||
.and_then(|mut a| a.find(|a| a.attribute.name == "tabindex"))
|
||||
{
|
||||
|
@ -86,12 +102,10 @@ impl NodeDepState for Focus {
|
|||
} else {
|
||||
FocusLevel::Unfocusable
|
||||
}
|
||||
} else if node
|
||||
} else if node_view
|
||||
.listeners()
|
||||
.and_then(|mut listeners| {
|
||||
listeners
|
||||
.any(|l| FOCUS_EVENTS.binary_search(&l).is_ok())
|
||||
.then_some(())
|
||||
listeners.any(|l| FOCUS_EVENTS.contains(&l)).then_some(())
|
||||
})
|
||||
.is_some()
|
||||
{
|
||||
|
@ -107,24 +121,48 @@ impl NodeDepState for Focus {
|
|||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn create<'a>(
|
||||
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,
|
||||
) -> Self {
|
||||
let mut myself = Self::default();
|
||||
myself.update(node_view, node, parent, children, context);
|
||||
myself
|
||||
}
|
||||
}
|
||||
|
||||
const FOCUS_EVENTS: &[&str] = &sorted_str_slice!(["keydown", "keypress", "keyup"]);
|
||||
const FOCUS_ATTRIBUTES: &[&str] = &sorted_str_slice!(["tabindex"]);
|
||||
static FOCUS_EVENTS: Lazy<FxHashSet<&str>> =
|
||||
Lazy::new(|| ["keydown", "keypress", "keyup"].into_iter().collect());
|
||||
const FOCUS_ATTRIBUTES: &[&str] = &["tabindex"];
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct FocusState {
|
||||
pub(crate) focus_iter: PersistantElementIter,
|
||||
pub(crate) last_focused_id: Option<RealNodeId>,
|
||||
pub(crate) last_focused_id: Option<NodeId>,
|
||||
pub(crate) focus_level: FocusLevel,
|
||||
pub(crate) dirty: bool,
|
||||
}
|
||||
|
||||
impl FocusState {
|
||||
pub fn create(rdom: &mut RealDom) -> Self {
|
||||
let focus_iter = PersistantElementIter::create(rdom);
|
||||
Self {
|
||||
focus_iter,
|
||||
last_focused_id: Default::default(),
|
||||
focus_level: Default::default(),
|
||||
dirty: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the focus has changed.
|
||||
pub fn progress(&mut self, rdom: &mut TuiDom, forward: bool) -> bool {
|
||||
pub fn progress(&mut self, rdom: &mut RealDom, forward: bool) -> bool {
|
||||
if let Some(last) = self.last_focused_id {
|
||||
if rdom[last].state.prevent_default == PreventDefault::KeyDown {
|
||||
if rdom.get(last).unwrap().get::<PreventDefault>().map(|p| *p)
|
||||
== Some(PreventDefault::KeyDown)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -140,13 +178,13 @@ impl FocusState {
|
|||
self.focus_iter.prev(rdom)
|
||||
};
|
||||
let new_id = new.id();
|
||||
if let ElementProduced::Looped(_) = new {
|
||||
if let IteratorMovement::Looped = new.movement() {
|
||||
let mut closest_level = None;
|
||||
|
||||
if forward {
|
||||
// find the closest focusable element after the current level
|
||||
rdom.traverse_depth_first(|n| {
|
||||
let node_level = n.state.focus.level;
|
||||
let node_level = n.get::<Focus>().unwrap().level;
|
||||
if node_level != *focus_level
|
||||
&& node_level.focusable()
|
||||
&& node_level > *focus_level
|
||||
|
@ -163,7 +201,7 @@ impl FocusState {
|
|||
} else {
|
||||
// find the closest focusable element before the current level
|
||||
rdom.traverse_depth_first(|n| {
|
||||
let node_level = n.state.focus.level;
|
||||
let node_level = n.get::<Focus>().unwrap().level;
|
||||
if node_level != *focus_level
|
||||
&& node_level.focusable()
|
||||
&& node_level < *focus_level
|
||||
|
@ -200,7 +238,7 @@ impl FocusState {
|
|||
loop_marker_id = Some(new_id);
|
||||
}
|
||||
|
||||
let current_level = rdom[new_id].state.focus.level;
|
||||
let current_level = rdom.get(new_id).unwrap().get::<Focus>().unwrap().level;
|
||||
let after_previous_focused = if forward {
|
||||
current_level >= *focus_level
|
||||
} else {
|
||||
|
@ -214,12 +252,15 @@ impl FocusState {
|
|||
}
|
||||
|
||||
if let Some(id) = next_focus {
|
||||
if !rdom[id].state.focus.level.focusable() {
|
||||
let mut node = rdom.get_mut(id).unwrap();
|
||||
if !node.get::<Focus>().unwrap().level.focusable() {
|
||||
panic!()
|
||||
}
|
||||
rdom[id].state.focused = true;
|
||||
node.insert(Focused(true));
|
||||
if let Some(old) = self.last_focused_id.replace(id) {
|
||||
rdom[old].state.focused = false;
|
||||
let mut focused_borrow: ViewMut<Focused> = rdom.raw_world().borrow().unwrap();
|
||||
let focused = (&mut focused_borrow).get(old).unwrap();
|
||||
focused.0 = false;
|
||||
}
|
||||
// reset the position to the currently focused element
|
||||
while self.focus_iter.next(rdom).id() != id {}
|
||||
|
@ -230,52 +271,14 @@ impl FocusState {
|
|||
false
|
||||
}
|
||||
|
||||
pub(crate) fn prune(&mut self, mutations: &dioxus_core::Mutations, rdom: &TuiDom) {
|
||||
fn remove_children(
|
||||
to_prune: &mut [&mut Option<RealNodeId>],
|
||||
rdom: &TuiDom,
|
||||
removed: RealNodeId,
|
||||
) {
|
||||
for opt in to_prune.iter_mut() {
|
||||
if let Some(id) = opt {
|
||||
if *id == removed {
|
||||
**opt = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(children) = &rdom.children_ids(removed) {
|
||||
for child in *children {
|
||||
remove_children(to_prune, rdom, *child);
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.focus_iter.prune(mutations, rdom) {
|
||||
self.dirty = true;
|
||||
}
|
||||
for m in &mutations.edits {
|
||||
match m {
|
||||
dioxus_core::Mutation::ReplaceWith { id, .. } => remove_children(
|
||||
&mut [&mut self.last_focused_id],
|
||||
rdom,
|
||||
rdom.element_to_node_id(*id),
|
||||
),
|
||||
dioxus_core::Mutation::Remove { id } => remove_children(
|
||||
&mut [&mut self.last_focused_id],
|
||||
rdom,
|
||||
rdom.element_to_node_id(*id),
|
||||
),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_focus(&mut self, rdom: &mut TuiDom, id: RealNodeId) {
|
||||
pub(crate) fn set_focus(&mut self, rdom: &mut RealDom, id: NodeId) {
|
||||
if let Some(old) = self.last_focused_id.replace(id) {
|
||||
rdom[old].state.focused = false;
|
||||
let mut node = rdom.get_mut(old).unwrap();
|
||||
node.insert(Focused(false));
|
||||
}
|
||||
let state = &mut rdom[id].state;
|
||||
state.focused = true;
|
||||
self.focus_level = state.focus.level;
|
||||
let mut node = rdom.get_mut(id).unwrap();
|
||||
node.insert(Focused(true));
|
||||
self.focus_level = node.get::<Focus>().unwrap().level;
|
||||
// reset the position to the currently focused element
|
||||
while self.focus_iter.next(rdom).id() != id {}
|
||||
self.dirty = true;
|
|
@ -1,9 +1,8 @@
|
|||
use crossterm::event::{
|
||||
Event as TermEvent, KeyCode as TermKeyCode, KeyModifiers, MouseButton, MouseEventKind,
|
||||
};
|
||||
use dioxus_core::*;
|
||||
use dioxus_native_core::tree::TreeView;
|
||||
use dioxus_native_core::NodeId;
|
||||
use dioxus_native_core::prelude::*;
|
||||
use dioxus_native_core::real_dom::NodeImmutable;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use dioxus_html::geometry::euclid::{Point2D, Rect, Size2D};
|
||||
|
@ -13,9 +12,8 @@ use dioxus_html::geometry::{
|
|||
use dioxus_html::input_data::keyboard_types::{Code, Key, Location, Modifiers};
|
||||
use dioxus_html::input_data::MouseButtonSet as DioxusMouseButtons;
|
||||
use dioxus_html::input_data::{MouseButton as DioxusMouseButton, MouseButtonSet};
|
||||
use dioxus_html::{event_bubbles, FocusData, KeyboardData, MouseData, WheelData};
|
||||
use dioxus_html::{event_bubbles, EventData, FocusData, KeyboardData, MouseData, WheelData};
|
||||
use std::{
|
||||
any::Any,
|
||||
cell::{RefCell, RefMut},
|
||||
rc::Rc,
|
||||
time::{Duration, Instant},
|
||||
|
@ -23,91 +21,37 @@ use std::{
|
|||
use taffy::geometry::{Point, Size};
|
||||
use taffy::{prelude::Layout, Taffy};
|
||||
|
||||
use crate::focus::{Focus, Focused};
|
||||
use crate::layout::TaffyLayout;
|
||||
use crate::{layout_to_screen_space, FocusState};
|
||||
use crate::{TuiDom, TuiNode};
|
||||
|
||||
pub(crate) struct Event {
|
||||
pub id: ElementId,
|
||||
pub id: NodeId,
|
||||
pub name: &'static str,
|
||||
pub data: Rc<dyn Any>,
|
||||
pub data: Rc<EventData>,
|
||||
pub bubbles: bool,
|
||||
}
|
||||
|
||||
// a wrapper around the input state for easier access
|
||||
// todo: fix loop
|
||||
// pub struct InputState(Rc<Rc<RefCell<InnerInputState>>>);
|
||||
// impl InputState {
|
||||
// pub fn get(cx: &ScopeState) -> InputState {
|
||||
// let inner = cx
|
||||
// .consume_context::<Rc<RefCell<InnerInputState>>>()
|
||||
// .expect("Rink InputState can only be used in Rink apps!");
|
||||
// (**inner).borrow_mut().subscribe(cx.schedule_update());
|
||||
// InputState(inner)
|
||||
// }
|
||||
|
||||
// pub fn mouse(&self) -> Option<MouseData> {
|
||||
// let data = (**self.0).borrow();
|
||||
// mouse.as_ref().map(|m| m.clone())
|
||||
// }
|
||||
|
||||
// pub fn wheel(&self) -> Option<WheelData> {
|
||||
// let data = (**self.0).borrow();
|
||||
// wheel.as_ref().map(|w| w.clone())
|
||||
// }
|
||||
|
||||
// pub fn screen(&self) -> Option<(u16, u16)> {
|
||||
// let data = (**self.0).borrow();
|
||||
// screen.as_ref().map(|m| m.clone())
|
||||
// }
|
||||
|
||||
// pub fn last_key_pressed(&self) -> Option<KeyboardData> {
|
||||
// let data = (**self.0).borrow();
|
||||
// last_key_pressed
|
||||
// .as_ref()
|
||||
// .map(|k| &k.0.clone())
|
||||
// }
|
||||
// }
|
||||
|
||||
type EventCore = (&'static str, EventData);
|
||||
|
||||
#[derive(Debug)]
|
||||
enum EventData {
|
||||
Mouse(MouseData),
|
||||
Wheel(WheelData),
|
||||
Screen((u16, u16)),
|
||||
Keyboard(KeyboardData),
|
||||
}
|
||||
impl EventData {
|
||||
fn into_any(self) -> Rc<dyn Any + Send + Sync> {
|
||||
match self {
|
||||
Self::Mouse(m) => Rc::new(m),
|
||||
Self::Wheel(w) => Rc::new(w),
|
||||
Self::Screen(s) => Rc::new(s),
|
||||
Self::Keyboard(k) => Rc::new(k),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_REPEAT_TIME: Duration = Duration::from_millis(100);
|
||||
|
||||
pub struct InnerInputState {
|
||||
mouse: Option<MouseData>,
|
||||
wheel: Option<WheelData>,
|
||||
last_key_pressed: Option<(KeyboardData, Instant)>,
|
||||
screen: Option<(u16, u16)>,
|
||||
pub(crate) focus_state: FocusState,
|
||||
// subscribers: Vec<Rc<dyn Fn() + 'static>>,
|
||||
}
|
||||
|
||||
impl InnerInputState {
|
||||
fn new() -> Self {
|
||||
fn create(rdom: &mut RealDom) -> Self {
|
||||
Self {
|
||||
mouse: None,
|
||||
wheel: None,
|
||||
last_key_pressed: None,
|
||||
screen: None,
|
||||
// subscribers: Vec::new(),
|
||||
focus_state: FocusState::default(),
|
||||
focus_state: FocusState::create(rdom),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -148,7 +92,6 @@ impl InnerInputState {
|
|||
*m = new_mouse_data;
|
||||
}
|
||||
EventData::Wheel(ref w) => self.wheel = Some(w.clone()),
|
||||
EventData::Screen(ref s) => self.screen = Some(*s),
|
||||
EventData::Keyboard(ref mut k) => {
|
||||
let is_repeating = self
|
||||
.last_key_pressed
|
||||
|
@ -165,6 +108,7 @@ impl InnerInputState {
|
|||
|
||||
self.last_key_pressed = Some((k.clone(), Instant::now()));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -173,7 +117,7 @@ impl InnerInputState {
|
|||
evts: &mut Vec<EventCore>,
|
||||
resolved_events: &mut Vec<Event>,
|
||||
layout: &Taffy,
|
||||
dom: &mut TuiDom,
|
||||
dom: &mut RealDom,
|
||||
) {
|
||||
let previous_mouse = self.mouse.clone();
|
||||
|
||||
|
@ -200,32 +144,26 @@ impl InnerInputState {
|
|||
if old_focus != self.focus_state.last_focused_id {
|
||||
// elements with listeners will always have a element id
|
||||
if let Some(id) = self.focus_state.last_focused_id {
|
||||
let element = dom.tree.get(id).unwrap();
|
||||
if let Some(id) = element.node_data.element_id {
|
||||
resolved_events.push(Event {
|
||||
name: "focus",
|
||||
id,
|
||||
data: Rc::new(FocusData {}),
|
||||
bubbles: event_bubbles("focus"),
|
||||
});
|
||||
resolved_events.push(Event {
|
||||
name: "focusin",
|
||||
id,
|
||||
data: Rc::new(FocusData {}),
|
||||
bubbles: event_bubbles("focusin"),
|
||||
});
|
||||
}
|
||||
resolved_events.push(Event {
|
||||
name: "focus",
|
||||
id,
|
||||
data: Rc::new(EventData::Focus(FocusData {})),
|
||||
bubbles: event_bubbles("focus"),
|
||||
});
|
||||
resolved_events.push(Event {
|
||||
name: "focusin",
|
||||
id,
|
||||
data: Rc::new(EventData::Focus(FocusData {})),
|
||||
bubbles: event_bubbles("focusin"),
|
||||
});
|
||||
}
|
||||
if let Some(id) = old_focus {
|
||||
let element = dom.tree.get(id).unwrap();
|
||||
if let Some(id) = element.node_data.element_id {
|
||||
resolved_events.push(Event {
|
||||
name: "focusout",
|
||||
id,
|
||||
data: Rc::new(FocusData {}),
|
||||
bubbles: event_bubbles("focusout"),
|
||||
});
|
||||
}
|
||||
resolved_events.push(Event {
|
||||
name: "focusout",
|
||||
id,
|
||||
data: Rc::new(EventData::Focus(FocusData {})),
|
||||
bubbles: event_bubbles("focusout"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -239,7 +177,7 @@ impl InnerInputState {
|
|||
previous_mouse: Option<MouseData>,
|
||||
resolved_events: &mut Vec<Event>,
|
||||
layout: &Taffy,
|
||||
dom: &mut TuiDom,
|
||||
dom: &mut RealDom,
|
||||
) {
|
||||
fn layout_contains_point(layout: &Layout, point: ScreenPoint) -> bool {
|
||||
let Point { x, y } = layout.location;
|
||||
|
@ -259,29 +197,27 @@ impl InnerInputState {
|
|||
|
||||
fn try_create_event(
|
||||
name: &'static str,
|
||||
data: Rc<dyn Any>,
|
||||
data: Rc<EventData>,
|
||||
will_bubble: &mut FxHashSet<NodeId>,
|
||||
resolved_events: &mut Vec<Event>,
|
||||
node: &TuiNode,
|
||||
dom: &TuiDom,
|
||||
node: NodeRef,
|
||||
dom: &RealDom,
|
||||
) {
|
||||
// only trigger event if the event was not triggered already by a child
|
||||
let id = node.node_data.node_id;
|
||||
let id = node.id();
|
||||
if will_bubble.insert(id) {
|
||||
let mut parent = dom.parent(id);
|
||||
let mut parent = Some(node);
|
||||
while let Some(current_parent) = parent {
|
||||
let parent_id = current_parent.node_data.node_id;
|
||||
let parent_id = current_parent.id();
|
||||
will_bubble.insert(parent_id);
|
||||
parent = dom.parent(parent_id);
|
||||
}
|
||||
if let Some(id) = node.mounted_id() {
|
||||
resolved_events.push(Event {
|
||||
name,
|
||||
id,
|
||||
data,
|
||||
bubbles: event_bubbles(name),
|
||||
})
|
||||
parent = current_parent.parent_id().and_then(|id| dom.get(id));
|
||||
}
|
||||
resolved_events.push(Event {
|
||||
name,
|
||||
id,
|
||||
data,
|
||||
bubbles: event_bubbles(name),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -345,7 +281,10 @@ impl InnerInputState {
|
|||
if currently_contains && previously_contained {
|
||||
try_create_event(
|
||||
"mousemove",
|
||||
Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
|
||||
Rc::new(EventData::Mouse(prepare_mouse_data(
|
||||
mouse_data,
|
||||
&node_layout,
|
||||
))),
|
||||
&mut will_bubble,
|
||||
resolved_events,
|
||||
node,
|
||||
|
@ -369,7 +308,7 @@ impl InnerInputState {
|
|||
if currently_contains && !previously_contained {
|
||||
try_create_event(
|
||||
"mouseenter",
|
||||
Rc::new(mouse_data.clone()),
|
||||
Rc::new(dioxus_html::EventData::Mouse(mouse_data.clone())),
|
||||
&mut will_bubble,
|
||||
resolved_events,
|
||||
node,
|
||||
|
@ -392,7 +331,10 @@ impl InnerInputState {
|
|||
if currently_contains && !previously_contained {
|
||||
try_create_event(
|
||||
"mouseover",
|
||||
Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
|
||||
Rc::new(EventData::Mouse(prepare_mouse_data(
|
||||
mouse_data,
|
||||
&node_layout,
|
||||
))),
|
||||
&mut will_bubble,
|
||||
resolved_events,
|
||||
node,
|
||||
|
@ -412,7 +354,10 @@ impl InnerInputState {
|
|||
if currently_contains {
|
||||
try_create_event(
|
||||
"mousedown",
|
||||
Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
|
||||
Rc::new(EventData::Mouse(prepare_mouse_data(
|
||||
mouse_data,
|
||||
&node_layout,
|
||||
))),
|
||||
&mut will_bubble,
|
||||
resolved_events,
|
||||
node,
|
||||
|
@ -433,7 +378,10 @@ impl InnerInputState {
|
|||
if currently_contains {
|
||||
try_create_event(
|
||||
"mouseup",
|
||||
Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
|
||||
Rc::new(EventData::Mouse(prepare_mouse_data(
|
||||
mouse_data,
|
||||
&node_layout,
|
||||
))),
|
||||
&mut will_bubble,
|
||||
resolved_events,
|
||||
node,
|
||||
|
@ -455,7 +403,10 @@ impl InnerInputState {
|
|||
if currently_contains {
|
||||
try_create_event(
|
||||
"click",
|
||||
Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
|
||||
Rc::new(EventData::Mouse(prepare_mouse_data(
|
||||
mouse_data,
|
||||
&node_layout,
|
||||
))),
|
||||
&mut will_bubble,
|
||||
resolved_events,
|
||||
node,
|
||||
|
@ -478,7 +429,10 @@ impl InnerInputState {
|
|||
if currently_contains {
|
||||
try_create_event(
|
||||
"contextmenu",
|
||||
Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
|
||||
Rc::new(EventData::Mouse(prepare_mouse_data(
|
||||
mouse_data,
|
||||
&node_layout,
|
||||
))),
|
||||
&mut will_bubble,
|
||||
resolved_events,
|
||||
node,
|
||||
|
@ -502,7 +456,7 @@ impl InnerInputState {
|
|||
if currently_contains {
|
||||
try_create_event(
|
||||
"wheel",
|
||||
Rc::new(w.clone()),
|
||||
Rc::new(EventData::Wheel(w.clone())),
|
||||
&mut will_bubble,
|
||||
resolved_events,
|
||||
node,
|
||||
|
@ -527,7 +481,10 @@ impl InnerInputState {
|
|||
if !currently_contains && previously_contained {
|
||||
try_create_event(
|
||||
"mouseleave",
|
||||
Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
|
||||
Rc::new(EventData::Mouse(prepare_mouse_data(
|
||||
mouse_data,
|
||||
&node_layout,
|
||||
))),
|
||||
&mut will_bubble,
|
||||
resolved_events,
|
||||
node,
|
||||
|
@ -550,7 +507,10 @@ impl InnerInputState {
|
|||
if !currently_contains && previously_contained {
|
||||
try_create_event(
|
||||
"mouseout",
|
||||
Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
|
||||
Rc::new(EventData::Mouse(prepare_mouse_data(
|
||||
mouse_data,
|
||||
&node_layout,
|
||||
))),
|
||||
&mut will_bubble,
|
||||
resolved_events,
|
||||
node,
|
||||
|
@ -564,11 +524,13 @@ impl InnerInputState {
|
|||
if was_released {
|
||||
let mut focus_id = None;
|
||||
dom.traverse_depth_first(|node| {
|
||||
let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
|
||||
let node_layout = layout
|
||||
.layout(node.get::<TaffyLayout>().unwrap().node.unwrap())
|
||||
.unwrap();
|
||||
let currently_contains = layout_contains_point(node_layout, new_pos);
|
||||
|
||||
if currently_contains && node.state.focus.level.focusable() {
|
||||
focus_id = Some(node.node_data.node_id);
|
||||
if currently_contains && node.get::<Focus>().unwrap().level.focusable() {
|
||||
focus_id = Some(node.id());
|
||||
}
|
||||
});
|
||||
if let Some(id) = focus_id {
|
||||
|
@ -583,13 +545,18 @@ impl InnerInputState {
|
|||
// }
|
||||
}
|
||||
|
||||
fn get_abs_layout(node: &TuiNode, dom: &TuiDom, taffy: &Taffy) -> Layout {
|
||||
let mut node_layout = *taffy.layout(node.state.layout.node.unwrap()).unwrap();
|
||||
fn get_abs_layout(node: NodeRef, dom: &RealDom, taffy: &Taffy) -> Layout {
|
||||
let mut node_layout = *taffy
|
||||
.layout(node.get::<TaffyLayout>().unwrap().node.unwrap())
|
||||
.unwrap();
|
||||
let mut current = node;
|
||||
|
||||
while let Some(parent) = dom.parent(current.node_data.node_id) {
|
||||
while let Some(parent) = current.parent_id() {
|
||||
let parent = dom.get(parent).unwrap();
|
||||
current = parent;
|
||||
let parent_layout = taffy.layout(parent.state.layout.node.unwrap()).unwrap();
|
||||
let parent_layout = taffy
|
||||
.layout(parent.get::<TaffyLayout>().unwrap().node.unwrap())
|
||||
.unwrap();
|
||||
node_layout.location.x += parent_layout.location.x;
|
||||
node_layout.location.y += parent_layout.location.y;
|
||||
}
|
||||
|
@ -604,11 +571,7 @@ pub struct RinkInputHandler {
|
|||
impl RinkInputHandler {
|
||||
/// global context that handles events
|
||||
/// limitations: GUI key modifier is never detected, key up events are not detected, and only two mouse buttons may be pressed at once
|
||||
pub fn new() -> (
|
||||
Self,
|
||||
Rc<RefCell<InnerInputState>>,
|
||||
impl FnMut(crossterm::event::Event),
|
||||
) {
|
||||
pub fn create(rdom: &mut RealDom) -> (Self, impl FnMut(crossterm::event::Event)) {
|
||||
let queued_events = Rc::new(RefCell::new(Vec::new()));
|
||||
let queued_events2 = Rc::downgrade(&queued_events);
|
||||
|
||||
|
@ -620,23 +583,18 @@ impl RinkInputHandler {
|
|||
}
|
||||
};
|
||||
|
||||
let state = Rc::new(RefCell::new(InnerInputState::new()));
|
||||
let state = Rc::new(RefCell::new(InnerInputState::create(rdom)));
|
||||
|
||||
(
|
||||
Self {
|
||||
state: state.clone(),
|
||||
state,
|
||||
queued_events,
|
||||
},
|
||||
state,
|
||||
regester_event,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn prune(&self, mutations: &dioxus_core::Mutations, rdom: &TuiDom) {
|
||||
self.state.borrow_mut().focus_state.prune(mutations, rdom);
|
||||
}
|
||||
|
||||
pub(crate) fn get_events(&self, layout: &Taffy, dom: &mut TuiDom) -> Vec<Event> {
|
||||
pub(crate) fn get_events(&self, layout: &Taffy, dom: &mut RealDom) -> Vec<Event> {
|
||||
let mut resolved_events = Vec::new();
|
||||
|
||||
(*self.state).borrow_mut().update(
|
||||
|
@ -667,28 +625,27 @@ impl RinkInputHandler {
|
|||
]
|
||||
.contains(&e.0)
|
||||
})
|
||||
.map(|evt| (evt.0, evt.1.into_any()));
|
||||
.map(|evt| (evt.0, evt.1));
|
||||
|
||||
let mut hm: FxHashMap<&'static str, Vec<Rc<dyn Any + Send + Sync>>> = FxHashMap::default();
|
||||
let mut hm: FxHashMap<&'static str, Vec<Rc<EventData>>> = FxHashMap::default();
|
||||
for (event, data) in events {
|
||||
if let Some(v) = hm.get_mut(event) {
|
||||
v.push(data);
|
||||
v.push(Rc::new(data));
|
||||
} else {
|
||||
hm.insert(event, vec![data]);
|
||||
hm.insert(event, vec![Rc::new(data)]);
|
||||
}
|
||||
}
|
||||
for (event, datas) in hm {
|
||||
for node in dom.get_listening_sorted(event) {
|
||||
for data in &datas {
|
||||
if node.state.focused {
|
||||
if let Some(id) = node.mounted_id() {
|
||||
resolved_events.push(Event {
|
||||
name: event,
|
||||
id,
|
||||
data: data.clone(),
|
||||
bubbles: event_bubbles(event),
|
||||
});
|
||||
}
|
||||
let focused = node.get::<Focused>();
|
||||
if focused.is_some() && focused.unwrap().0 {
|
||||
resolved_events.push(Event {
|
||||
name: event,
|
||||
id: node.id(),
|
||||
data: data.clone(),
|
||||
bubbles: event_bubbles(event),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -770,7 +727,7 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> {
|
|||
MouseEventKind::ScrollUp => ("wheel", get_wheel_data(true)),
|
||||
}
|
||||
}
|
||||
TermEvent::Resize(x, y) => ("resize", EventData::Screen((x, y))),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some((name, data))
|
|
@ -1,12 +1,13 @@
|
|||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use dioxus_native_core::exports::shipyard::Component;
|
||||
use dioxus_native_core::layout_attributes::{
|
||||
apply_layout_attributes_cfg, BorderWidths, LayoutConfigeration,
|
||||
};
|
||||
use dioxus_native_core::node::OwnedAttributeView;
|
||||
use dioxus_native_core::node_ref::{AttributeMask, NodeMask, NodeView};
|
||||
use dioxus_native_core::state::ChildDepState;
|
||||
use dioxus_native_core_macro::sorted_str_slice;
|
||||
use dioxus_native_core::node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView};
|
||||
use dioxus_native_core::prelude::*;
|
||||
use dioxus_native_core_macro::partial_derive_state;
|
||||
use taffy::prelude::*;
|
||||
|
||||
use crate::{screen_to_layout_space, unit_to_layout_space};
|
||||
|
@ -16,6 +17,7 @@ pub(crate) enum PossiblyUninitalized<T> {
|
|||
Uninitalized,
|
||||
Initialized(T),
|
||||
}
|
||||
|
||||
impl<T> PossiblyUninitalized<T> {
|
||||
pub fn unwrap(self) -> T {
|
||||
match self {
|
||||
|
@ -36,35 +38,35 @@ impl<T> Default for PossiblyUninitalized<T> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Default, Debug)]
|
||||
#[derive(Clone, PartialEq, Default, Debug, Component)]
|
||||
pub(crate) struct TaffyLayout {
|
||||
pub style: Style,
|
||||
pub node: PossiblyUninitalized<Node>,
|
||||
}
|
||||
|
||||
impl ChildDepState for TaffyLayout {
|
||||
type Ctx = Arc<Mutex<Taffy>>;
|
||||
type DepState = (Self,);
|
||||
// use tag to force this to be called when a node is built
|
||||
const NODE_MASK: NodeMask =
|
||||
NodeMask::new_with_attrs(AttributeMask::Static(SORTED_LAYOUT_ATTRS))
|
||||
.with_text()
|
||||
.with_tag();
|
||||
#[partial_derive_state]
|
||||
impl State for TaffyLayout {
|
||||
type ChildDependencies = (Self,);
|
||||
type ParentDependencies = ();
|
||||
type NodeDependencies = ();
|
||||
|
||||
/// Setup the layout
|
||||
fn reduce<'a>(
|
||||
const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new()
|
||||
.with_attrs(AttributeMaskBuilder::Some(SORTED_LAYOUT_ATTRS))
|
||||
.with_text();
|
||||
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
node: NodeView,
|
||||
children: impl Iterator<Item = (&'a Self,)>,
|
||||
ctx: &Self::Ctx,
|
||||
) -> bool
|
||||
where
|
||||
Self::DepState: 'a,
|
||||
{
|
||||
node_view: NodeView,
|
||||
_: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
|
||||
_: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
ctx: &SendAnyMap,
|
||||
) -> bool {
|
||||
let mut changed = false;
|
||||
let mut taffy = ctx.lock().expect("poisoned taffy");
|
||||
let taffy: &Arc<Mutex<Taffy>> = ctx.get().unwrap();
|
||||
let mut taffy = taffy.lock().expect("poisoned taffy");
|
||||
let mut style = Style::default();
|
||||
if let Some(text) = node.text() {
|
||||
if let Some(text) = node_view.text() {
|
||||
let char_len = text.chars().count();
|
||||
|
||||
style = Style {
|
||||
|
@ -87,14 +89,11 @@ impl ChildDepState for TaffyLayout {
|
|||
}
|
||||
} else {
|
||||
// gather up all the styles from the attribute list
|
||||
if let Some(attributes) = node.attributes() {
|
||||
if let Some(attributes) = node_view.attributes() {
|
||||
for OwnedAttributeView {
|
||||
attribute, value, ..
|
||||
} in attributes
|
||||
{
|
||||
assert!(SORTED_LAYOUT_ATTRS
|
||||
.binary_search(&attribute.name.as_ref())
|
||||
.is_ok());
|
||||
if let Some(text) = value.as_text() {
|
||||
apply_layout_attributes_cfg(
|
||||
&attribute.name,
|
||||
|
@ -191,10 +190,22 @@ impl ChildDepState for TaffyLayout {
|
|||
}
|
||||
changed
|
||||
}
|
||||
|
||||
fn create<'a>(
|
||||
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,
|
||||
) -> Self {
|
||||
let mut myself = Self::default();
|
||||
myself.update(node_view, node, parent, children, context);
|
||||
myself
|
||||
}
|
||||
}
|
||||
|
||||
// these are the attributes in layout_attiributes in native-core
|
||||
const SORTED_LAYOUT_ATTRS: &[&str] = &sorted_str_slice!([
|
||||
const SORTED_LAYOUT_ATTRS: &[&str] = &[
|
||||
"align-content",
|
||||
"align-items",
|
||||
"align-self",
|
||||
|
@ -346,5 +357,5 @@ const SORTED_LAYOUT_ATTRS: &[&str] = &sorted_str_slice!([
|
|||
"word-break",
|
||||
"word-spacing",
|
||||
"word-wrap",
|
||||
"z-index"
|
||||
]);
|
||||
"z-index",
|
||||
];
|
|
@ -1,3 +1,4 @@
|
|||
use crate::focus::Focus;
|
||||
use anyhow::Result;
|
||||
use crossterm::{
|
||||
cursor::{MoveTo, RestorePosition, SavePosition, Show},
|
||||
|
@ -5,21 +6,21 @@ use crossterm::{
|
|||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use dioxus_core::*;
|
||||
use dioxus_native_core::{real_dom::RealDom, FxDashSet, NodeId, NodeMask, SendAnyMap};
|
||||
use dioxus_html::EventData;
|
||||
use dioxus_native_core::prelude::*;
|
||||
use dioxus_native_core::{real_dom::RealDom, FxDashSet, NodeId, SendAnyMap};
|
||||
use focus::FocusState;
|
||||
use futures::{
|
||||
channel::mpsc::{UnboundedReceiver, UnboundedSender},
|
||||
pin_mut, StreamExt,
|
||||
};
|
||||
use futures::{channel::mpsc::UnboundedSender, pin_mut, Future, StreamExt};
|
||||
use futures_channel::mpsc::unbounded;
|
||||
use query::Query;
|
||||
use std::rc::Rc;
|
||||
use layout::TaffyLayout;
|
||||
use prevent_default::PreventDefault;
|
||||
use std::{io, time::Duration};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
pin::Pin,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use std::{io, time::Duration};
|
||||
use std::{rc::Rc, sync::RwLock};
|
||||
use style_attributes::StyleModifier;
|
||||
use taffy::Taffy;
|
||||
pub use taffy::{geometry::Point, prelude::*};
|
||||
use tokio::select;
|
||||
|
@ -29,18 +30,17 @@ mod config;
|
|||
mod focus;
|
||||
mod hooks;
|
||||
mod layout;
|
||||
mod node;
|
||||
pub mod prelude;
|
||||
mod prevent_default;
|
||||
pub mod query;
|
||||
mod render;
|
||||
mod style;
|
||||
mod style_attributes;
|
||||
mod widget;
|
||||
mod widgets;
|
||||
|
||||
pub use config::*;
|
||||
pub use hooks::*;
|
||||
pub(crate) use node::*;
|
||||
pub use query::Query;
|
||||
|
||||
// the layout space has a multiplier of 10 to minimize rounding errors
|
||||
pub(crate) fn screen_to_layout_space(screen: u16) -> f32 {
|
||||
|
@ -59,7 +59,12 @@ pub(crate) fn layout_to_screen_space(layout: f32) -> f32 {
|
|||
pub struct TuiContext {
|
||||
tx: UnboundedSender<InputEvent>,
|
||||
}
|
||||
|
||||
impl TuiContext {
|
||||
pub fn new(tx: UnboundedSender<InputEvent>) -> Self {
|
||||
Self { tx }
|
||||
}
|
||||
|
||||
pub fn quit(&self) {
|
||||
self.tx.unbounded_send(InputEvent::Close).unwrap();
|
||||
}
|
||||
|
@ -71,21 +76,25 @@ impl TuiContext {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn launch(app: Component<()>) {
|
||||
launch_cfg(app, Config::default())
|
||||
}
|
||||
pub fn render<R: Driver>(
|
||||
cfg: Config,
|
||||
create_renderer: impl FnOnce(
|
||||
&Arc<RwLock<RealDom>>,
|
||||
&Arc<Mutex<Taffy>>,
|
||||
UnboundedSender<InputEvent>,
|
||||
) -> R,
|
||||
) -> Result<()> {
|
||||
let mut rdom = RealDom::new([
|
||||
TaffyLayout::to_type_erased(),
|
||||
Focus::to_type_erased(),
|
||||
StyleModifier::to_type_erased(),
|
||||
PreventDefault::to_type_erased(),
|
||||
]);
|
||||
|
||||
pub fn launch_cfg(app: Component<()>, cfg: Config) {
|
||||
launch_cfg_with_props(app, (), cfg);
|
||||
}
|
||||
|
||||
pub fn launch_cfg_with_props<Props: 'static>(app: Component<Props>, props: Props, cfg: Config) {
|
||||
let mut dom = VirtualDom::new_with_props(app, props);
|
||||
|
||||
let (handler, state, register_event) = RinkInputHandler::new();
|
||||
let (handler, mut register_event) = RinkInputHandler::create(&mut rdom);
|
||||
|
||||
// Setup input handling
|
||||
let (event_tx, event_rx) = unbounded();
|
||||
let (event_tx, mut event_reciever) = unbounded();
|
||||
let event_tx_clone = event_tx.clone();
|
||||
if !cfg.headless {
|
||||
std::thread::spawn(move || {
|
||||
|
@ -101,59 +110,22 @@ pub fn launch_cfg_with_props<Props: 'static>(app: Component<Props>, props: Props
|
|||
});
|
||||
}
|
||||
|
||||
let cx = dom.base_scope();
|
||||
let rdom = Rc::new(RefCell::new(RealDom::new()));
|
||||
let rdom = Arc::new(RwLock::new(rdom));
|
||||
let taffy = Arc::new(Mutex::new(Taffy::new()));
|
||||
cx.provide_context(state);
|
||||
cx.provide_context(TuiContext { tx: event_tx_clone });
|
||||
cx.provide_context(Query {
|
||||
rdom: rdom.clone(),
|
||||
stretch: taffy.clone(),
|
||||
});
|
||||
let mut renderer = create_renderer(&rdom, &taffy, event_tx_clone);
|
||||
|
||||
{
|
||||
let mut rdom = rdom.borrow_mut();
|
||||
let mutations = dom.rebuild();
|
||||
let (to_update, _) = rdom.apply_mutations(mutations);
|
||||
renderer.update(&rdom);
|
||||
let mut any_map = SendAnyMap::new();
|
||||
any_map.insert(taffy.clone());
|
||||
let _to_rerender = rdom.update_state(to_update, any_map);
|
||||
let mut rdom = rdom.write().unwrap();
|
||||
let _ = rdom.update_state(any_map);
|
||||
}
|
||||
|
||||
render_vdom(
|
||||
&mut dom,
|
||||
event_rx,
|
||||
handler,
|
||||
cfg,
|
||||
rdom,
|
||||
taffy,
|
||||
register_event,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn render_vdom(
|
||||
vdom: &mut VirtualDom,
|
||||
mut event_reciever: UnboundedReceiver<InputEvent>,
|
||||
handler: RinkInputHandler,
|
||||
cfg: Config,
|
||||
rdom: Rc<RefCell<TuiDom>>,
|
||||
taffy: Arc<Mutex<Taffy>>,
|
||||
mut register_event: impl FnMut(crossterm::event::Event),
|
||||
) -> Result<()> {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()?
|
||||
.block_on(async {
|
||||
#[cfg(all(feature = "hot-reload", debug_assertions))]
|
||||
let mut hot_reload_rx = {
|
||||
let (hot_reload_tx, hot_reload_rx) =
|
||||
tokio::sync::mpsc::unbounded_channel::<dioxus_hot_reload::HotReloadMsg>();
|
||||
dioxus_hot_reload::connect(move |msg| {
|
||||
let _ = hot_reload_tx.send(msg);
|
||||
});
|
||||
hot_reload_rx
|
||||
};
|
||||
let mut terminal = (!cfg.headless).then(|| {
|
||||
enable_raw_mode().unwrap();
|
||||
let mut stdout = std::io::stdout();
|
||||
|
@ -172,7 +144,7 @@ fn render_vdom(
|
|||
}
|
||||
|
||||
let mut to_rerender = FxDashSet::default();
|
||||
to_rerender.insert(NodeId(0));
|
||||
to_rerender.insert(rdom.read().unwrap().root_id());
|
||||
let mut updated = true;
|
||||
|
||||
loop {
|
||||
|
@ -187,19 +159,27 @@ fn render_vdom(
|
|||
|
||||
if !to_rerender.is_empty() || updated {
|
||||
updated = false;
|
||||
fn resize(dims: Rect, taffy: &mut Taffy, rdom: &TuiDom) {
|
||||
fn resize(dims: Rect, taffy: &mut Taffy, rdom: &RealDom) {
|
||||
let width = screen_to_layout_space(dims.width);
|
||||
let height = screen_to_layout_space(dims.height);
|
||||
let root_node = rdom[NodeId(0)].state.layout.node.unwrap();
|
||||
let root_node = rdom
|
||||
.get(rdom.root_id())
|
||||
.unwrap()
|
||||
.get::<TaffyLayout>()
|
||||
.unwrap()
|
||||
.node
|
||||
.unwrap();
|
||||
|
||||
// the root node fills the entire area
|
||||
|
||||
let mut style = *taffy.style(root_node).unwrap();
|
||||
style.size = Size {
|
||||
let new_size = Size {
|
||||
width: Dimension::Points(width),
|
||||
height: Dimension::Points(height),
|
||||
};
|
||||
taffy.set_style(root_node, style).unwrap();
|
||||
if style.size != new_size {
|
||||
style.size = new_size;
|
||||
taffy.set_style(root_node, style).unwrap();
|
||||
}
|
||||
|
||||
let size = Size {
|
||||
width: AvailableSpace::Definite(width),
|
||||
|
@ -210,16 +190,16 @@ fn render_vdom(
|
|||
if let Some(terminal) = &mut terminal {
|
||||
execute!(terminal.backend_mut(), SavePosition).unwrap();
|
||||
terminal.draw(|frame| {
|
||||
let rdom = rdom.borrow();
|
||||
let rdom = rdom.write().unwrap();
|
||||
let mut taffy = taffy.lock().expect("taffy lock poisoned");
|
||||
// size is guaranteed to not change when rendering
|
||||
resize(frame.size(), &mut taffy, &rdom);
|
||||
let root = &rdom[NodeId(0)];
|
||||
render::render_vnode(frame, &taffy, &rdom, root, cfg, Point::ZERO);
|
||||
let root = rdom.get(rdom.root_id()).unwrap();
|
||||
render::render_vnode(frame, &taffy, root, cfg, Point::ZERO);
|
||||
})?;
|
||||
execute!(terminal.backend_mut(), RestorePosition, Show).unwrap();
|
||||
} else {
|
||||
let rdom = rdom.borrow();
|
||||
let rdom = rdom.read().unwrap();
|
||||
resize(
|
||||
Rect {
|
||||
x: 0,
|
||||
|
@ -233,14 +213,8 @@ fn render_vdom(
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "hot-reload", debug_assertions))]
|
||||
let mut hot_reload_msg = None;
|
||||
{
|
||||
let wait = vdom.wait_for_work();
|
||||
#[cfg(all(feature = "hot-reload", debug_assertions))]
|
||||
let hot_reload_wait = hot_reload_rx.recv();
|
||||
#[cfg(not(all(feature = "hot-reload", debug_assertions)))]
|
||||
let hot_reload_wait: std::future::Pending<Option<()>> = std::future::pending();
|
||||
let wait = renderer.poll_async();
|
||||
|
||||
pin_mut!(wait);
|
||||
|
||||
|
@ -253,8 +227,8 @@ fn render_vdom(
|
|||
InputEvent::UserInput(event) => match event {
|
||||
TermEvent::Key(key) => {
|
||||
if matches!(key.code, KeyCode::Char('C' | 'c'))
|
||||
&& key.modifiers.contains(KeyModifiers::CONTROL)
|
||||
&& cfg.ctrl_c_quit
|
||||
&& key.modifiers.contains(KeyModifiers::CONTROL)
|
||||
&& cfg.ctrl_c_quit
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
@ -269,52 +243,34 @@ fn render_vdom(
|
|||
register_event(evt);
|
||||
}
|
||||
},
|
||||
Some(msg) = hot_reload_wait => {
|
||||
#[cfg(all(feature = "hot-reload", debug_assertions))]
|
||||
{
|
||||
hot_reload_msg = Some(msg);
|
||||
}
|
||||
#[cfg(not(all(feature = "hot-reload", debug_assertions)))]
|
||||
let () = msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we have a new template, replace the old one
|
||||
#[cfg(all(feature = "hot-reload", debug_assertions))]
|
||||
if let Some(msg) = hot_reload_msg {
|
||||
match msg {
|
||||
dioxus_hot_reload::HotReloadMsg::UpdateTemplate(template) => {
|
||||
vdom.replace_template(template);
|
||||
}
|
||||
dioxus_hot_reload::HotReloadMsg::Shutdown => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let evts = {
|
||||
let mut rdom = rdom.borrow_mut();
|
||||
handler.get_events(&taffy.lock().expect("taffy lock poisoned"), &mut rdom)
|
||||
};
|
||||
{
|
||||
let evts = {
|
||||
handler.get_events(
|
||||
&taffy.lock().expect("taffy lock poisoned"),
|
||||
&mut rdom.write().unwrap(),
|
||||
)
|
||||
};
|
||||
updated |= handler.state().focus_state.clean();
|
||||
|
||||
for e in evts {
|
||||
renderer.handle_event(&rdom, e.id, e.name, e.data, e.bubbles);
|
||||
}
|
||||
}
|
||||
for e in evts {
|
||||
vdom.handle_event(e.name, e.data, e.id, e.bubbles)
|
||||
}
|
||||
let mut rdom = rdom.borrow_mut();
|
||||
let mutations = vdom.render_immediate();
|
||||
handler.prune(&mutations, &rdom);
|
||||
// updates the dom's nodes
|
||||
let (to_update, dirty) = rdom.apply_mutations(mutations);
|
||||
renderer.update(&rdom);
|
||||
// update the style and layout
|
||||
let mut rdom = rdom.write().unwrap();
|
||||
let mut any_map = SendAnyMap::new();
|
||||
any_map.insert(taffy.clone());
|
||||
to_rerender = rdom.update_state(to_update, any_map);
|
||||
let (new_to_rerender, dirty) = rdom.update_state(any_map);
|
||||
to_rerender = new_to_rerender;
|
||||
let text_mask = NodeMaskBuilder::new().with_text().build();
|
||||
for (id, mask) in dirty {
|
||||
if mask.overlaps(&NodeMask::new().with_text()) {
|
||||
if mask.overlaps(&text_mask) {
|
||||
to_rerender.insert(id);
|
||||
}
|
||||
}
|
||||
|
@ -336,7 +292,20 @@ fn render_vdom(
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum InputEvent {
|
||||
pub enum InputEvent {
|
||||
UserInput(TermEvent),
|
||||
Close,
|
||||
}
|
||||
|
||||
pub trait Driver {
|
||||
fn update(&mut self, rdom: &Arc<RwLock<RealDom>>);
|
||||
fn handle_event(
|
||||
&mut self,
|
||||
rdom: &Arc<RwLock<RealDom>>,
|
||||
id: NodeId,
|
||||
event: &str,
|
||||
value: Rc<EventData>,
|
||||
bubbles: bool,
|
||||
);
|
||||
fn poll_async(&mut self) -> Pin<Box<dyn Future<Output = ()> + '_>>;
|
||||
}
|
2
packages/rink/src/prelude/mod.rs
Normal file
2
packages/rink/src/prelude/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
#[cfg(feature = "dioxus-bindings")]
|
||||
pub use crate::widgets::*;
|
86
packages/rink/src/prevent_default.rs
Normal file
86
packages/rink/src/prevent_default.rs
Normal file
|
@ -0,0 +1,86 @@
|
|||
use dioxus_native_core::prelude::*;
|
||||
use dioxus_native_core_macro::partial_derive_state;
|
||||
use shipyard::Component;
|
||||
|
||||
#[derive(PartialEq, Debug, Clone, Copy, Component, Default)]
|
||||
pub(crate) enum PreventDefault {
|
||||
Focus,
|
||||
KeyPress,
|
||||
KeyRelease,
|
||||
KeyDown,
|
||||
KeyUp,
|
||||
MouseDown,
|
||||
Click,
|
||||
MouseEnter,
|
||||
MouseLeave,
|
||||
MouseOut,
|
||||
#[default]
|
||||
Unknown,
|
||||
MouseOver,
|
||||
ContextMenu,
|
||||
Wheel,
|
||||
MouseUp,
|
||||
}
|
||||
|
||||
#[partial_derive_state]
|
||||
impl State for PreventDefault {
|
||||
type ParentDependencies = ();
|
||||
type ChildDependencies = ();
|
||||
type NodeDependencies = ();
|
||||
|
||||
const NODE_MASK: dioxus_native_core::node_ref::NodeMaskBuilder<'static> =
|
||||
dioxus_native_core::node_ref::NodeMaskBuilder::new()
|
||||
.with_attrs(dioxus_native_core::node_ref::AttributeMaskBuilder::Some(&[
|
||||
"dioxus-prevent-default",
|
||||
]))
|
||||
.with_listeners();
|
||||
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
node_view: NodeView,
|
||||
_: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
|
||||
_: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_: &SendAnyMap,
|
||||
) -> bool {
|
||||
let new = match node_view.attributes().and_then(|mut attrs| {
|
||||
attrs
|
||||
.find(|a| a.attribute.name == "dioxus-prevent-default")
|
||||
.and_then(|a| a.value.as_text())
|
||||
}) {
|
||||
Some("onfocus") => PreventDefault::Focus,
|
||||
Some("onkeypress") => PreventDefault::KeyPress,
|
||||
Some("onkeyrelease") => PreventDefault::KeyRelease,
|
||||
Some("onkeydown") => PreventDefault::KeyDown,
|
||||
Some("onkeyup") => PreventDefault::KeyUp,
|
||||
Some("onclick") => PreventDefault::Click,
|
||||
Some("onmousedown") => PreventDefault::MouseDown,
|
||||
Some("onmouseup") => PreventDefault::MouseUp,
|
||||
Some("onmouseenter") => PreventDefault::MouseEnter,
|
||||
Some("onmouseover") => PreventDefault::MouseOver,
|
||||
Some("onmouseleave") => PreventDefault::MouseLeave,
|
||||
Some("onmouseout") => PreventDefault::MouseOut,
|
||||
Some("onwheel") => PreventDefault::Wheel,
|
||||
Some("oncontextmenu") => PreventDefault::ContextMenu,
|
||||
_ => return false,
|
||||
};
|
||||
if new == *self {
|
||||
false
|
||||
} else {
|
||||
*self = new;
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fn create<'a>(
|
||||
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,
|
||||
) -> Self {
|
||||
let mut myself = Self::default();
|
||||
myself.update(node_view, node, parent, children, context);
|
||||
myself
|
||||
}
|
||||
}
|
|
@ -1,17 +1,13 @@
|
|||
use std::{
|
||||
cell::{Ref, RefCell},
|
||||
rc::Rc,
|
||||
sync::{Arc, Mutex, MutexGuard},
|
||||
};
|
||||
use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard};
|
||||
|
||||
use dioxus_core::ElementId;
|
||||
use dioxus_native_core::prelude::*;
|
||||
use taffy::{
|
||||
geometry::Point,
|
||||
prelude::{Layout, Size},
|
||||
Taffy,
|
||||
};
|
||||
|
||||
use crate::{layout_to_screen_space, TuiDom};
|
||||
use crate::{layout::TaffyLayout, layout_to_screen_space};
|
||||
|
||||
/// Allows querying the layout of nodes after rendering. It will only provide a correct value after a node is rendered.
|
||||
/// Provided as a root context for all tui applictions.
|
||||
|
@ -46,28 +42,38 @@ use crate::{layout_to_screen_space, TuiDom};
|
|||
/// ```
|
||||
#[derive(Clone)]
|
||||
pub struct Query {
|
||||
pub(crate) rdom: Rc<RefCell<TuiDom>>,
|
||||
pub(crate) rdom: Arc<RwLock<RealDom>>,
|
||||
pub(crate) stretch: Arc<Mutex<Taffy>>,
|
||||
}
|
||||
|
||||
impl Query {
|
||||
pub fn get(&self, id: ElementId) -> ElementRef {
|
||||
pub fn new(rdom: Arc<RwLock<RealDom>>, stretch: Arc<Mutex<Taffy>>) -> Self {
|
||||
Self { rdom, stretch }
|
||||
}
|
||||
|
||||
pub fn get(&self, id: NodeId) -> ElementRef {
|
||||
let rdom = self.rdom.read();
|
||||
let stretch = self.stretch.lock();
|
||||
ElementRef::new(
|
||||
self.rdom.borrow(),
|
||||
self.stretch.lock().expect("taffy lock poisoned"),
|
||||
rdom.expect("rdom lock poisoned"),
|
||||
stretch.expect("taffy lock poisoned"),
|
||||
id,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ElementRef<'a> {
|
||||
inner: Ref<'a, TuiDom>,
|
||||
inner: RwLockReadGuard<'a, RealDom>,
|
||||
stretch: MutexGuard<'a, Taffy>,
|
||||
id: ElementId,
|
||||
id: NodeId,
|
||||
}
|
||||
|
||||
impl<'a> ElementRef<'a> {
|
||||
fn new(inner: Ref<'a, TuiDom>, stretch: MutexGuard<'a, Taffy>, id: ElementId) -> Self {
|
||||
fn new(
|
||||
inner: RwLockReadGuard<'a, RealDom>,
|
||||
stretch: MutexGuard<'a, Taffy>,
|
||||
id: NodeId,
|
||||
) -> Self {
|
||||
Self { inner, stretch, id }
|
||||
}
|
||||
|
||||
|
@ -85,7 +91,15 @@ impl<'a> ElementRef<'a> {
|
|||
pub fn layout(&self) -> Option<Layout> {
|
||||
let layout = self
|
||||
.stretch
|
||||
.layout(self.inner[self.id].state.layout.node.ok()?)
|
||||
.layout(
|
||||
self.inner
|
||||
.get(self.id)
|
||||
.unwrap()
|
||||
.get::<TaffyLayout>()
|
||||
.unwrap()
|
||||
.node
|
||||
.ok()?,
|
||||
)
|
||||
.ok();
|
||||
layout.map(|layout| Layout {
|
||||
order: layout.order,
|
|
@ -1,4 +1,4 @@
|
|||
use dioxus_native_core::tree::TreeView;
|
||||
use dioxus_native_core::prelude::*;
|
||||
use std::io::Stdout;
|
||||
use taffy::{
|
||||
geometry::Point,
|
||||
|
@ -8,11 +8,13 @@ use taffy::{
|
|||
use tui::{backend::CrosstermBackend, layout::Rect, style::Color};
|
||||
|
||||
use crate::{
|
||||
focus::Focused,
|
||||
layout::TaffyLayout,
|
||||
layout_to_screen_space,
|
||||
style::{RinkColor, RinkStyle},
|
||||
style_attributes::{BorderEdge, BorderStyle},
|
||||
style_attributes::{BorderEdge, BorderStyle, StyleModifier},
|
||||
widget::{RinkBuffer, RinkCell, RinkWidget, WidgetWithContext},
|
||||
Config, TuiDom, TuiNode,
|
||||
Config,
|
||||
};
|
||||
|
||||
const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5];
|
||||
|
@ -20,20 +22,19 @@ const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5];
|
|||
pub(crate) fn render_vnode(
|
||||
frame: &mut tui::Frame<CrosstermBackend<Stdout>>,
|
||||
layout: &Taffy,
|
||||
rdom: &TuiDom,
|
||||
node: &TuiNode,
|
||||
node: NodeRef,
|
||||
cfg: Config,
|
||||
parent_location: Point<f32>,
|
||||
) {
|
||||
use dioxus_native_core::node::NodeType;
|
||||
|
||||
if let NodeType::Placeholder = &node.node_data.node_type {
|
||||
if let NodeType::Placeholder = &*node.node_type() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Layout {
|
||||
mut location, size, ..
|
||||
} = layout.layout(node.state.layout.node.unwrap()).unwrap();
|
||||
} = layout
|
||||
.layout(node.get::<TaffyLayout>().unwrap().node.unwrap())
|
||||
.unwrap();
|
||||
location.x += parent_location.x;
|
||||
location.y += parent_location.y;
|
||||
|
||||
|
@ -44,8 +45,8 @@ pub(crate) fn render_vnode(
|
|||
let width = layout_to_screen_space(fx + width).round() as u16 - x;
|
||||
let height = layout_to_screen_space(fy + height).round() as u16 - y;
|
||||
|
||||
match &node.node_data.node_type {
|
||||
NodeType::Text { text } => {
|
||||
match &*node.node_type() {
|
||||
NodeType::Text(text) => {
|
||||
#[derive(Default)]
|
||||
struct Label<'a> {
|
||||
text: &'a str,
|
||||
|
@ -64,8 +65,8 @@ pub(crate) fn render_vnode(
|
|||
}
|
||||
|
||||
let label = Label {
|
||||
text,
|
||||
style: node.state.style.core,
|
||||
text: &text.text,
|
||||
style: node.get::<StyleModifier>().unwrap().core,
|
||||
};
|
||||
let area = Rect::new(x, y, width, height);
|
||||
|
||||
|
@ -82,15 +83,15 @@ pub(crate) fn render_vnode(
|
|||
frame.render_widget(WidgetWithContext::new(node, cfg), area);
|
||||
}
|
||||
|
||||
for c in rdom.children_ids(node.node_data.node_id).unwrap() {
|
||||
render_vnode(frame, layout, rdom, &rdom[*c], cfg, location);
|
||||
for c in node.children() {
|
||||
render_vnode(frame, layout, c, cfg, location);
|
||||
}
|
||||
}
|
||||
NodeType::Placeholder => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
impl RinkWidget for &TuiNode {
|
||||
impl RinkWidget for NodeRef<'_> {
|
||||
fn render(self, area: Rect, mut buf: RinkBuffer<'_>) {
|
||||
use tui::symbols::line::*;
|
||||
|
||||
|
@ -266,18 +267,22 @@ impl RinkWidget for &TuiNode {
|
|||
for x in area.left()..area.right() {
|
||||
for y in area.top()..area.bottom() {
|
||||
let mut new_cell = RinkCell::default();
|
||||
if let Some(c) = self.state.style.core.bg {
|
||||
if let Some(c) = self.get::<StyleModifier>().unwrap().core.bg {
|
||||
new_cell.bg = c;
|
||||
}
|
||||
if self.state.focused {
|
||||
new_cell.bg.alpha = 100;
|
||||
new_cell.bg.color = new_cell.bg.blend(Color::White);
|
||||
if let Some(focused) = self.get::<Focused>() {
|
||||
if focused.0 {
|
||||
new_cell.bg.alpha = 100;
|
||||
new_cell.bg.color = new_cell.bg.blend(Color::White);
|
||||
}
|
||||
}
|
||||
buf.set(x, y, new_cell);
|
||||
}
|
||||
}
|
||||
|
||||
let borders = &self.state.style.modifier.borders;
|
||||
let style = self.get::<StyleModifier>().unwrap();
|
||||
|
||||
let borders = &style.modifier.borders;
|
||||
|
||||
let last_edge = &borders.left;
|
||||
let current_edge = &borders.top;
|
||||
|
@ -294,7 +299,7 @@ impl RinkWidget for &TuiNode {
|
|||
(last_r * RADIUS_MULTIPLIER[0]) as u16,
|
||||
(last_r * RADIUS_MULTIPLIER[1]) as u16,
|
||||
];
|
||||
let color = current_edge.color.or(self.state.style.core.fg);
|
||||
let color = current_edge.color.or(style.core.fg);
|
||||
let mut new_cell = RinkCell::default();
|
||||
if let Some(c) = color {
|
||||
new_cell.fg = c;
|
||||
|
@ -329,7 +334,7 @@ impl RinkWidget for &TuiNode {
|
|||
(last_r * RADIUS_MULTIPLIER[0]) as u16,
|
||||
(last_r * RADIUS_MULTIPLIER[1]) as u16,
|
||||
];
|
||||
let color = current_edge.color.or(self.state.style.core.fg);
|
||||
let color = current_edge.color.or(style.core.fg);
|
||||
let mut new_cell = RinkCell::default();
|
||||
if let Some(c) = color {
|
||||
new_cell.fg = c;
|
||||
|
@ -364,7 +369,7 @@ impl RinkWidget for &TuiNode {
|
|||
(last_r * RADIUS_MULTIPLIER[0]) as u16,
|
||||
(last_r * RADIUS_MULTIPLIER[1]) as u16,
|
||||
];
|
||||
let color = current_edge.color.or(self.state.style.core.fg);
|
||||
let color = current_edge.color.or(style.core.fg);
|
||||
let mut new_cell = RinkCell::default();
|
||||
if let Some(c) = color {
|
||||
new_cell.fg = c;
|
||||
|
@ -399,7 +404,7 @@ impl RinkWidget for &TuiNode {
|
|||
(last_r * RADIUS_MULTIPLIER[0]) as u16,
|
||||
(last_r * RADIUS_MULTIPLIER[1]) as u16,
|
||||
];
|
||||
let color = current_edge.color.or(self.state.style.core.fg);
|
||||
let color = current_edge.color.or(style.core.fg);
|
||||
let mut new_cell = RinkCell::default();
|
||||
if let Some(c) = color {
|
||||
new_cell.fg = c;
|
|
@ -32,36 +32,48 @@
|
|||
use dioxus_native_core::{
|
||||
layout_attributes::parse_value,
|
||||
node::OwnedAttributeView,
|
||||
node_ref::{AttributeMask, NodeMask, NodeView},
|
||||
state::ParentDepState,
|
||||
node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView},
|
||||
prelude::*,
|
||||
};
|
||||
use dioxus_native_core_macro::sorted_str_slice;
|
||||
use dioxus_native_core_macro::partial_derive_state;
|
||||
use shipyard::Component;
|
||||
use taffy::prelude::*;
|
||||
|
||||
use crate::style::{RinkColor, RinkStyle};
|
||||
|
||||
#[derive(Default, Clone, PartialEq, Debug)]
|
||||
#[derive(Default, Clone, PartialEq, Debug, Component)]
|
||||
pub struct StyleModifier {
|
||||
pub core: RinkStyle,
|
||||
pub modifier: TuiModifier,
|
||||
}
|
||||
|
||||
impl ParentDepState for StyleModifier {
|
||||
type Ctx = ();
|
||||
type DepState = (Self,);
|
||||
// todo: seperate each attribute into it's own class
|
||||
const NODE_MASK: NodeMask =
|
||||
NodeMask::new_with_attrs(AttributeMask::Static(SORTED_STYLE_ATTRS)).with_element();
|
||||
#[partial_derive_state]
|
||||
impl State for StyleModifier {
|
||||
type ParentDependencies = (Self,);
|
||||
type ChildDependencies = ();
|
||||
type NodeDependencies = ();
|
||||
|
||||
fn reduce(&mut self, node: NodeView, parent: Option<(&Self,)>, _: &Self::Ctx) -> bool {
|
||||
// todo: seperate each attribute into it's own class
|
||||
const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new()
|
||||
.with_attrs(AttributeMaskBuilder::Some(SORTED_STYLE_ATTRS))
|
||||
.with_element();
|
||||
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
node_view: NodeView,
|
||||
_: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
|
||||
parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
_: &SendAnyMap,
|
||||
) -> bool {
|
||||
let mut new = StyleModifier::default();
|
||||
if parent.is_some() {
|
||||
new.core.fg = None;
|
||||
}
|
||||
|
||||
// handle text modifier elements
|
||||
if node.namespace().is_none() {
|
||||
if let Some(tag) = node.tag() {
|
||||
if node_view.namespace().is_none() {
|
||||
if let Some(tag) = node_view.tag() {
|
||||
match tag {
|
||||
"b" => apply_style_attributes("font-weight", "bold", &mut new),
|
||||
"strong" => apply_style_attributes("font-weight", "bold", &mut new),
|
||||
|
@ -79,7 +91,7 @@ impl ParentDepState for StyleModifier {
|
|||
}
|
||||
|
||||
// gather up all the styles from the attribute list
|
||||
if let Some(attrs) = node.attributes() {
|
||||
if let Some(attrs) = node_view.attributes() {
|
||||
for OwnedAttributeView {
|
||||
attribute, value, ..
|
||||
} in attrs
|
||||
|
@ -103,6 +115,18 @@ impl ParentDepState for StyleModifier {
|
|||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn create<'a>(
|
||||
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,
|
||||
) -> Self {
|
||||
let mut myself = Self::default();
|
||||
myself.update(node_view, node, parent, children, context);
|
||||
myself
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, PartialEq, Debug)]
|
||||
|
@ -598,7 +622,7 @@ fn apply_transition(_name: &str, _value: &str, _style: &mut StyleModifier) {
|
|||
todo!()
|
||||
}
|
||||
|
||||
const SORTED_STYLE_ATTRS: &[&str] = &sorted_str_slice!([
|
||||
const SORTED_STYLE_ATTRS: &[&str] = &[
|
||||
"animation",
|
||||
"animation-delay",
|
||||
"animation-direction",
|
||||
|
@ -799,5 +823,5 @@ const SORTED_STYLE_ATTRS: &[&str] = &sorted_str_slice!([
|
|||
"text-justify",
|
||||
"text-overflow",
|
||||
"text-shadow",
|
||||
"text-transform"
|
||||
]);
|
||||
"text-transform",
|
||||
];
|
73
packages/rink/test.html
Normal file
73
packages/rink/test.html
Normal file
|
@ -0,0 +1,73 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Test</title>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: black;
|
||||
/* justify-content: center;
|
||||
align-items: center; */
|
||||
/* margin: auto; */
|
||||
}
|
||||
|
||||
.smaller {
|
||||
height: 70%;
|
||||
width: 70%;
|
||||
background-color: green;
|
||||
/* justify-content: center; */
|
||||
/* align-items: center; */
|
||||
}
|
||||
|
||||
.superinner {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
/* display: flex; */
|
||||
/* */
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
/* */
|
||||
background-color: red;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
/* margin: 20px; */
|
||||
/* margin: 20px; */
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="smaller">
|
||||
<div class="superinner">
|
||||
<h1>Hello World</h1>
|
||||
<p>This is a test</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="container">
|
||||
<div class="smaller">
|
||||
hello world
|
||||
<div style="color: green; margin: 40px;">
|
||||
goodbye
|
||||
<div style="color:red;">
|
||||
asdasdasd
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -36,7 +36,6 @@ thiserror = "1.0.30"
|
|||
futures-util = "0.3.21"
|
||||
serde = { version = "1", optional = true }
|
||||
serde_urlencoded = { version = "0.7.1", optional = true }
|
||||
simple_logger = "4.0.0"
|
||||
|
||||
[features]
|
||||
default = ["query"]
|
||||
|
@ -46,7 +45,6 @@ wasm_test = []
|
|||
|
||||
[dev-dependencies]
|
||||
console_error_panic_hook = "0.1.7"
|
||||
log = "0.4.14"
|
||||
wasm-logger = "0.2.0"
|
||||
wasm-bindgen-test = "0.3"
|
||||
gloo-utils = "0.1.2"
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
[package]
|
||||
name = "rsx-rosetta"
|
||||
version = "0.0.0"
|
||||
edition = "2018"
|
||||
version = "0.3.0"
|
||||
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
|
||||
|
||||
[dependencies]
|
||||
dioxus-autofmt = { path = "../autofmt" }
|
||||
dioxus-autofmt = { path = "../autofmt", version = "0.3.0" }
|
||||
dioxus-rsx = { path = "../rsx" , version = "^0.0.3" }
|
||||
html_parser = "0.6.3"
|
||||
proc-macro2 = "1.0.49"
|
||||
|
|
|
@ -29,7 +29,7 @@ pub struct FileMapBuildResult<Ctx: HotReloadingContext> {
|
|||
|
||||
pub struct FileMap<Ctx: HotReloadingContext> {
|
||||
pub map: HashMap<PathBuf, (String, Option<Template<'static>>)>,
|
||||
in_workspace: HashMap<PathBuf, bool>,
|
||||
in_workspace: HashMap<PathBuf, Option<PathBuf>>,
|
||||
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
|
||||
// we need to check if the file is in a workspace or not and strip the prefix accordingly
|
||||
let prefix = if in_workspace {
|
||||
crate_dir.parent().ok_or(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Could not load workspace",
|
||||
))?
|
||||
let prefix = if let Some(workspace) = &in_workspace {
|
||||
workspace
|
||||
} else {
|
||||
crate_dir
|
||||
};
|
||||
|
@ -173,9 +170,9 @@ impl<Ctx: HotReloadingContext> FileMap<Ctx> {
|
|||
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) {
|
||||
Ok(*in_workspace)
|
||||
Ok(in_workspace.clone())
|
||||
} else {
|
||||
let mut cmd = Cmd::new();
|
||||
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))?;
|
||||
|
||||
let in_workspace = metadata.workspace_root != crate_dir;
|
||||
let workspace_path = in_workspace.then(|| metadata.workspace_root.into());
|
||||
self.in_workspace
|
||||
.insert(crate_dir.to_path_buf(), in_workspace);
|
||||
Ok(in_workspace)
|
||||
.insert(crate_dir.to_path_buf(), workspace_path.clone());
|
||||
Ok(workspace_path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue