Merge remote-tracking branch 'origin/master' into jk/rsx-refactor

This commit is contained in:
Evan Almloff committed 2022-05-27 17:56:50 -05:00
commit f9713b2fe9
83 files changed
+6905 -2326

No files matched your search

+1 -1
View File
@@ -1,4 +1,4 @@
# These are supported funding model platforms
github: jkelleyrtp # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
github: DioxusLabs # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
open_collective: dioxus-labs # Replace with a single Open Collective username
+1 -2
View File
@@ -24,8 +24,7 @@ jobs:
run: cd docs &&
cd guide && mdbook build -d ../nightly/guide && cd .. &&
cd reference && mdbook build -d ../nightly/reference && cd .. &&
cd router && mdbook build -d ../nightly/router && cd .. &&
cd cli && mdbook build -d ../nightly/cli && cd ..
cd router && mdbook build -d ../nightly/router && cd ..
- name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@v4.2.3
+4 -4
View File
@@ -38,7 +38,7 @@ jobs:
override: true
- uses: Swatinem/rust-cache@v1
- run: sudo apt-get update
- run: sudo apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev
- run: sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libayatana-appindicator3-dev
- uses: actions-rs/cargo@v1
with:
command: check
@@ -56,7 +56,7 @@ jobs:
override: true
- uses: Swatinem/rust-cache@v1
- run: sudo apt-get update
- run: sudo apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev
- run: sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libayatana-appindicator3-dev
- uses: davidB/rust-cargo-make@v1
- uses: browser-actions/setup-firefox@latest
- uses: jetli/wasm-pack-action@v0.3.0
@@ -96,7 +96,7 @@ jobs:
override: true
- uses: Swatinem/rust-cache@v1
- run: sudo apt-get update
- run: sudo apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev
- run: sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libayatana-appindicator3-dev
- run: rustup component add clippy
- uses: actions-rs/cargo@v1
with:
@@ -117,7 +117,7 @@ jobs:
# run: |
# apt-get update &&\
# apt-get install build-essential &&\
# apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev -y &&\
# apt install libwebkit2gtk-4.0-dev libgtk-3-dev libayatana-appindicator3-dev -y &&\
# cargo +nightly tarpaulin --verbose --all-features --workspace --timeout 120 --out Xml
# - name: Upload to codecov.io
# uses: codecov/codecov-action@v2
+3 -1
View File
@@ -9,4 +9,6 @@ Cargo.lock
!.vscode/launch.json
!.vscode/extensions.json
tarpaulin-report.html
.idea
# Jetbrain
.idea/
+24 -14
View File
@@ -1,6 +1,6 @@
[package]
name = "dioxus"
version = "0.2.3"
version = "0.2.4"
authors = ["Jonathan Kelley"]
edition = "2021"
description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
@@ -12,23 +12,30 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
rust-version = "1.56.0"
[dependencies]
dioxus-core = { path = "./packages/core", version = "^0.2.0" }
dioxus-html = { path = "./packages/html", version = "^0.2.0", optional = true }
dioxus-core-macro = { path = "./packages/core-macro", version = "^0.2.0", optional = true }
dioxus-hooks = { path = "./packages/hooks", version = "^0.2.0", optional = true }
fermi = { path = "./packages/fermi", version = "^0.2.0", optional = true }
dioxus-core = { path = "./packages/core", version = "^0.2.1" }
dioxus-html = { path = "./packages/html", version = "^0.2.1", optional = true }
dioxus-core-macro = { path = "./packages/core-macro", version = "^0.2.1", optional = true }
dioxus-hooks = { path = "./packages/hooks", version = "^0.2.1", optional = true }
fermi = { path = "./packages/fermi", version = "^0.2.1", optional = true }
dioxus-web = { path = "./packages/web", version = "^0.2.0", optional = true }
dioxus-desktop = { path = "./packages/desktop", version = "^0.2.2", optional = true }
dioxus-ssr = { path = "./packages/ssr", version = "^0.2.0", optional = true }
dioxus-web = { path = "./packages/web", version = "^0.2.1", optional = true }
dioxus-desktop = { path = "./packages/desktop", version = "^0.2.3", optional = true }
dioxus-ssr = { path = "./packages/ssr", version = "^0.2.1", optional = true }
dioxus-router = { path = "./packages/router", version = "^0.2.1", optional = true }
dioxus-interpreter-js = { path = "./packages/interpreter", version = "^0.2.0", optional = true }
dioxus-tui = { path = "./packages/tui", version = "^0.2.0", optional = true }
dioxus-router = { path = "./packages/router", version = "^0.2.3", optional = true }
dioxus-interpreter-js = { path = "./packages/interpreter", version = "^0.2.1", optional = true }
dioxus-tui = { path = "./packages/tui", version = "^0.2.2", optional = true }
dioxus-liveview = { path = "./packages/liveview", optional = true }
dioxus-rsx = { path = "./packages/rsx", optional = true }
dioxus-rsx-interperter = { path = "./packages/rsx_interperter", optional = true }
dioxus-liveview = { path = "./packages/liveview", version = "^0.1.0", optional = true }
dioxus-native-core = { path = "./packages/native-core", version = "^0.2.0", optional = true }
dioxus-native-core-macro = { path = "./packages/native-core-macro", version = "^0.2.0", optional = true }
# dioxus-mobile = { path = "./packages/mobile", version = "^0.2.0", optional = true }
# dioxus-rsx = { path = "./packages/rsx", optional = true }
# macro = ["dioxus-core-macro", "dioxus-rsx"]
[features]
default = ["macro", "hooks", "html"]
@@ -39,11 +46,12 @@ html = ["dioxus-html"]
ssr = ["dioxus-ssr"]
web = ["dioxus-web", "dioxus-router/web"]
desktop = ["dioxus-desktop"]
ayatana = ["dioxus-desktop/ayatana"]
router = ["dioxus-router"]
tui = ["dioxus-tui"]
liveview = ["dioxus-liveview"]
hot_reload = ["dioxus-core-macro/hot_reload", "dioxus-rsx-interperter"]
native-core = ["dioxus-native-core", "dioxus-native-core-macro"]
[workspace]
members = [
@@ -61,6 +69,8 @@ members = [
"packages/liveview",
"packages/rsx",
"packages/rsx_interperter",
"packages/native-core",
"packages/native-core-macro",
]
[dev-dependencies]
-1
View File
@@ -1 +0,0 @@
book
-6
View File
@@ -1,6 +0,0 @@
[book]
authors = ["YuKun Liu"]
language = "en"
multilingual = false
src = "src"
title = "Dioxus Cli"
-11
View File
@@ -1,11 +0,0 @@
# Summary
- [Introduction](./introduction.md)
- [Installation](./installation.md)
- [Create a Project](./creating.md)
- [Configure Project](./configure.md)
- [Commands](./cmd/README.md)
- [Build](./cmd/build.md)
- [Serve](./cmd/serve.md)
- [Clean](./cmd/clean.md)
- [Translate](./cmd/translate.md)
-26
View File
@@ -1,26 +0,0 @@
# Commands
In this chapter we will introduce all `dioxus-cli` commands.
> you can also use `dioxus --help` to get cli help info.
```
dioxus
Build, bundle, & ship your Dioxus app
USAGE:
dioxus [OPTIONS] <SUBCOMMAND>
OPTIONS:
-h, --help Print help information
-v Enable verbose logging
SUBCOMMANDS:
build Build the Rust WASM app and all of its assets
clean Clean output artifacts
config Dioxus config file controls
create Init a new project for Dioxus
help Print this message or the help of the given subcommand(s)
serve Build, watch & serve the Rust WASM app and all of its assets
translate Translate some source file into Dioxus code
```
-47
View File
@@ -1,47 +0,0 @@
# Build
The `dioxus build` command can help you `pack & build` a dioxus project.
```
dioxus-build
Build the Rust WASM app and all of its assets
USAGE:
dioxus build [OPTIONS]
OPTIONS:
--example <EXAMPLE> [default: ""]
--platform <PLATFORM> [default: "default_platform"]
--release [default: false]
```
You can use this command to build project to `out_dir` :
```
dioxus build --release
```
## Target platform
Use option `platform` choose build target platform:
```
# for desktop project
dioxus build --platform desktop
```
`platform` only supports `desktop` & `web`.
```
# for web project
dioxus build --platform web
```
## Build Example
You can use `--example {name}` to build a example code.
```
# build `example/test` code
dioxus build --exmaple test
```
-18
View File
@@ -1,18 +0,0 @@
# Clean
`dioxus clean` will call `target clean` and remove `out_dir` directory.
```
dioxus-clean
Clean output artifacts
USAGE:
dioxus clean
```
you can use this command to clean all build cache and the `out_dir` content.
```
dioxus clean
```
-49
View File
@@ -1,49 +0,0 @@
# Serve
The `dioxus serve` can start a dev server (include hot-reload tool) to run the project.
```
dioxus-serve
Build, watch & serve the Rust WASM app and all of its assets
USAGE:
dioxus serve [OPTIONS]
OPTIONS:
--example <EXAMPLE> [default: ""]
--platform <PLATFORM> [default: "default_platform"]
--release [default: false]
```
You can use this command to build project and start a `dev server` :
```
dioxus serve
```
## Target platform
Use option `platform` choose build target platform:
```
# for desktop project
dioxus serve --platform desktop
```
`platform` only supports `desktop` & `web`.
`dev-server` only for `web` project.
```
# for web project
dioxus serve --platform web
```
## Serve Example
You can use `--example {name}` to start a example code.
```
# build `example/test` code
dioxus serve --exmaple test
```
-57
View File
@@ -1,57 +0,0 @@
# Translate
`dioxus translate` can translate some source file into Dioxus code.
```
dioxus-translate
Translate some source file into Dioxus code
USAGE:
dioxus translate [OPTIONS] [OUTPUT]
ARGS:
<OUTPUT> Output file, stdout if not present
OPTIONS:
-c, --component Activate debug mode
-f, --file <FILE> Input file
```
## Translate HTML to stdout
```
dioxus transtale --file ./index.html
```
## Output in a file
```
dioxus translate --component --file ./index.html component.rsx
```
set `component` flag will wrap `dioxus rsx` code in a component function.
## Example
```html
<div>
<h1> Hello World </h1>
<a href="https://dioxuslabs.com/">Link</a>
</div>
```
Translate HTML to Dioxus component code.
```rust
fn component(cx: Scope) -> Element {
cx.render(rsx! {
div {
h1 { "Hello World" },
a {
href: "https://dioxuslabs.com/",
"Link"
}
}
})
}
```
-147
View File
@@ -1,147 +0,0 @@
# Configure Project
This chapter will introduce `Dioxus.toml` and anatomy the config file.
## Structure
We use `toml` to define some info for `dioxus` project.
### Application
1. ***name*** - project name & title
2. ***default_platform*** - which platform target for this project.
```
# current support: web, desktop
# default: web
default_platform = "web"
```
change this to `desktop`, the `dioxus build & serve` will default build desktop app.
3. ***out_dir*** - which directory to put the output file; use `dioxus build & service`, the output will put at this directory, and the `assets` will be also copy to here.
```
out_dir = "dist"
```
4. ***asset_dir*** - which direcotry to put your `static, assets` file, cli will automatic copy all file to `out_dir`, so you can put some resource file in there, like `CSS, JS, Image` file.
```
asset_dir = "public"
```
### Web.App
Web platform application config:
1. ***title*** - this value will display on the web page title. like `<title></title>` tag.
```
# HTML title tag content
title = "dioxus app | ⛺"
```
### Web.Watcher
Web platform `dev-server` watcher config:
1. ***reload_html*** - a boolean value; when watcher trigger, regenerate `index.html` file.
```
# when watcher trigger, regenerate the `index.html`
reload_html = true
```
2. ***watch_path*** - which files & directories will be watcher monitoring.
```
watch_path = ["src", "public"]
```
### Web.Resource
Include some `CSS Javascript` resources into html file.
1. ***style*** - include some style(CSS) file into html.
```
style = [
# include from public_dir.
"./assets/style.css",
# or some asset from online cdn.
"https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.css"
]
```
2. ***script*** - include some script(JS) file into html.
```
style = [
# include from public_dir.
"./assets/index.js",
# or some asset from online cdn.
"https://cdn.jsdelivr.net/npm/bootstrap/dist/js/bootstrap.js"
]
```
### Web.Resource.Dev
Only include resources at `Dev` mode.
1. ***style*** - include some style(CSS) file into html.
```
style = [
# include from public_dir.
"./assets/style.css",
# or some asset from online cdn.
"https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.css"
]
```
2. ***script*** - include some script(JS) file into html.
```
style = [
# include from public_dir.
"./assets/index.js",
# or some asset from online cdn.
"https://cdn.jsdelivr.net/npm/bootstrap/dist/js/bootstrap.js"
]
```
## Config example
```toml
[application]
# App (Project) Name
name = "{{project-name}}"
# Dioxus App Default Platform
# desktop, web, mobile, ssr
default_platform = "web"
# `build` & `serve` dist path
out_dir = "dist"
# resource (public) file folder
asset_dir = "public"
[web.app]
# HTML title tag content
title = "dioxus | ⛺"
[web.watcher]
# when watcher trigger, regenerate the `index.html`
reload_html = true
# which files or dirs will be watcher monitoring
watch_path = ["src", "public"]
# include `assets` in web platform
[web.resource]
# CSS style file
style = []
# Javascript code file
script = []
[web.resource.dev]
# serve: [dev-server] only
# CSS style file
style = []
# Javascript code file
script = []
```
-38
View File
@@ -1,38 +0,0 @@
# Create a Project
Once you have the Dioxus CLI tool installed, you can use it to create dioxus project.
## Initializing a default project
The `dioxus create` command will create a new directory containing a project template.
```
dioxus create hello-dioxus
```
It will clone a default template from github template: [DioxusLabs/dioxus-template](https://github.com/DioxusLabs/dioxus-template)
> This default template is use for `web` platform application.
then you can change the current directory in to the project:
```
cd hello-dioxus
```
> Make sure `wasm32 target` is installed before running the Web project.
now we can create a `dev server` to display the project:
```
dioxus serve
```
by default, the dioxus dev server will running at: [`http://127.0.0.1:8080/`](http://127.0.0.1:8080/)
## Initalizing from other repository
you can assign which repository you want to create:
```
dioxus init hello-dioxus --template=gh:dioxuslabs/dioxus-template
```
-24
View File
@@ -1,24 +0,0 @@
# Installation
There are multiple ways to install the Dioxus CLI tool. Choose any one of the methods below that best suit your needs.
## Install from latest master version
We suggest you use `github master` version to install it now.
We have not yet released the latest version to `crates.io`
```
cargo install --git https://github.com/Dioxuslabs/cli
```
This will automatically download `Dioxus-CLI` source from github master branch,
and install it in Cargo's global binary directory (`~/.cargo/bin/` by default).
## Install from `crates.io` version
```
cargo install dioxus-cli
```
Make sure to add the Cargo bin directory to your `PATH`.
-21
View File
@@ -1,21 +0,0 @@
# Introduction
📦✨ **Dioxus-Cli** is a tool to help get dioxus projects off the ground.
![dioxus-logo](https://dioxuslabs.com/guide/images/dioxuslogo_full.png)
It includes `dev server`, `hot reload` and some `quick command` to help you use dioxus.
## Features
- [x] `html` to `rsx` conversion tool
- [x] hot reload for `web` platform
- [x] create dioxus project from `git` repo
- [x] build & pack dioxus project
- [ ] autoformat dioxus `rsx` code
## Contributors
Contributors to this guide:
- [mrxiaozhuox](https://github.com/mrxiaozhuox)
+1 -1
View File
@@ -102,7 +102,7 @@ For keen Rustaceans: notice how we don't actually call `collect` on the name lis
## Keeping list items in order with `key`
The examples above demonstrate the power of iterators in `rsx!` but all share the same issue: if your array items move (e.g. due to sorting), get inserted, or get deleted, Dioxus has no way of knowing what happened. This can cause Elements to be unnecessarily removed, changed and rebuilt when all that was needed was to change their position – this is inneficient.
The examples above demonstrate the power of iterators in `rsx!` but all share the same issue: if your array items move (e.g. due to sorting), get inserted, or get deleted, Dioxus has no way of knowing what happened. This can cause Elements to be unnecessarily removed, changed and rebuilt when all that was needed was to change their position – this is inefficient.
To solve this problem, each item in the list must be **uniquely identifiable**. You can achieve this by giving it a unique, fixed "key". In Dioxus, a key is a string that identifies an item among others in the list.
+484
View File
@@ -1 +1,485 @@
# Custom Renderer
Dioxus is an incredibly portable framework for UI development. The lessons, knowledge, hooks, and components you acquire over time can always be used for future projects. However, sometimes those projects cannot leverage a supported renderer or you need to implement your own better renderer.
Great news: the design of the renderer is entirely up to you! We provide suggestions and inspiration with the 1st party renderers, but only really require processing `DomEdits` and sending `UserEvents`.
## The specifics:
Implementing the renderer is fairly straightforward. The renderer needs to:
1. Handle the stream of edits generated by updates to the virtual DOM
2. Register listeners and pass events into the virtual DOM's event system
Essentially, your renderer needs to implement the `RealDom` trait and generate `EventTrigger` objects to update the VirtualDOM. From there, you'll have everything needed to render the VirtualDOM to the screen.
Internally, Dioxus handles the tree relationship, diffing, memory management, and the event system, leaving as little as possible required for renderers to implement themselves.
For reference, check out the javascript interperter or tui renderer as a starting point for your custom renderer.
## DomEdits
The "DomEdit" type is a serialized enum that represents an atomic operation occurring on the RealDom. The variants roughly follow this set:
```rust
enum DomEdit {
PushRoot,
AppendChildren,
ReplaceWith,
InsertAfter,
InsertBefore,
Remove,
CreateTextNode,
CreateElement,
CreateElementNs,
CreatePlaceholder,
NewEventListener,
RemoveEventListener,
SetText,
SetAttribute,
RemoveAttribute,
PopRoot,
}
```
The Dioxus diffing mechanism operates as a [stack machine](https://en.wikipedia.org/wiki/Stack_machine) where the "push_root" method pushes a new "real" DOM node onto the stack and "append_child" and "replace_with" both remove nodes from the stack.
### An example
For the sake of understanding, lets consider this example - a very simple UI declaration:
```rust
rsx!( h1 {"hello world"} )
```
To get things started, Dioxus must first navigate to the container of this h1 tag. To "navigate" here, the internal diffing algorithm generates the DomEdit `PushRoot` where the ID of the root is the container.
When the renderer receives this instruction, it pushes the actual Node onto its own stack. The real renderer's stack will look like this:
```rust
instructions: [
PushRoot(Container)
]
stack: [
ContainerNode,
]
```
Next, Dioxus will encounter the h1 node. The diff algorithm decides that this node needs to be created, so Dioxus will generate the DomEdit `CreateElement`. When the renderer receives this instruction, it will create an unmounted node and push into its own stack:
```rust
instructions: [
PushRoot(Container),
CreateElement(h1),
]
stack: [
ContainerNode,
h1,
]
```
Next, Dioxus sees the text node, and generates the `CreateTextNode` DomEdit:
```rust
instructions: [
PushRoot(Container),
CreateElement(h1),
CreateTextNode("hello world")
]
stack: [
ContainerNode,
h1,
"hello world"
]
```
Remember, the text node is not attached to anything (it is unmounted) so Dioxus needs to generate an Edit that connects the text node to the h1 element. It depends on the situation, but in this case we use `AppendChildren`. This pops the text node off the stack, leaving the h1 element as the next element in line.
```rust
instructions: [
PushRoot(Container),
CreateElement(h1),
CreateTextNode("hello world"),
AppendChildren(1)
]
stack: [
ContainerNode,
h1
]
```
We call `AppendChildren` again, popping off the h1 node and attaching it to the parent:
```rust
instructions: [
PushRoot(Container),
CreateElement(h1),
CreateTextNode("hello world"),
AppendChildren(1),
AppendChildren(1)
]
stack: [
ContainerNode,
]
```
Finally, the container is popped since we don't need it anymore.
```rust
instructions: [
PushRoot(Container),
CreateElement(h1),
CreateTextNode("hello world"),
AppendChildren(1),
AppendChildren(1),
PopRoot
]
stack: []
```
Over time, our stack looked like this:
```rust
[]
[Container]
[Container, h1]
[Container, h1, "hello world"]
[Container, h1]
[Container]
[]
```
Notice how our stack is empty once UI has been mounted. Conveniently, this approach completely separates the VirtualDOM and the Real DOM. Additionally, these edits are serializable, meaning we can even manage UIs across a network connection. This little stack machine and serialized edits makes Dioxus independent of platform specifics.
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.
It's important to note that there _is_ one layer of connectedness between Dioxus and the renderer. Dioxus saves and loads elements (the PushRoot edit) with an ID. Inside the VirtualDOM, this is just tracked as a u64.
Whenever a `CreateElement` edit is generated during diffing, Dioxus increments its node counter and assigns that new element its current NodeCount. The RealDom is responsible for remembering this ID and pushing the correct node when PushRoot(ID) is generated. Dioxus reclaims IDs of elements when removed. To stay in sync with Dioxus you can use a sparce Vec (Vec<Option<T>>) with possibly unoccupied items. You can use the ids as indexes into the Vec for elements, and grow the Vec when a id does not exist.
This little demo serves to show exactly how a Renderer would need to process an edit stream to build UIs. A set of serialized DomEditss for various demos is available for you to test your custom renderer against.
## 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.
The code for the WebSys implementation is straightforward, so we'll add it here to demonstrate how simple an event loop is:
```rust
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());
websys_dom.stack.push(root_node);
// Rebuild or hydrate the virtualdom
let mutations = self.internal_dom.rebuild();
websys_dom.apply_mutations(mutations);
// Wait for updates from the real dom and progress the virtual dom
loop {
let user_input_future = websys_dom.wait_for_event();
let internal_event_future = self.internal_dom.wait_for_work();
match select(user_input_future, internal_event_future).await {
Either::Left((_, _)) => {
let mutations = self.internal_dom.work_with_deadline(|| false);
websys_dom.apply_mutations(mutations);
},
Either::Right((event, _)) => websys_dom.handle_event(event),
}
// render
}
}
```
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.
```rust
fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
match event.type_().as_str() {
"keydown" => {
let event: web_sys::KeyboardEvent = event.clone().dyn_into().unwrap();
UserEvent::KeyboardEvent(UserEvent {
scope_id: None,
priority: EventPriority::Medium,
name: "keydown",
// This should be whatever element is focused
element: Some(ElementId(0)),
data: Arc::new(KeyboardData{
char_code: event.char_code(),
key: event.key(),
key_code: event.key_code(),
alt_key: event.alt_key(),
ctrl_key: event.ctrl_key(),
meta_key: event.meta_key(),
shift_key: event.shift_key(),
locale: "".to_string(),
location: event.location(),
repeat: event.repeat(),
which: event.which(),
})
})
}
_ => todo!()
}
}
```
## Custom raw elements
If you need to go as far as relying on custom elements for your renderer - you totally can. This still enables you to use Dioxus' reactive nature, component system, shared state, and other features, but will ultimately generate different nodes. All attributes and listeners for the HTML and SVG namespace are shuttled through helper structs that essentially compile away (pose no runtime overhead). You can drop in your own elements any time you want, with little hassle. However, you must be absolutely sure your renderer can handle the new type, or it will crash and burn.
These custom elements are defined as unit structs with trait implementations.
For example, the `div` element is (approximately!) defined as such:
```rust
struct div;
impl div {
/// Some glorious documentation about the class property.
const TAG_NAME: &'static str = "div";
const NAME_SPACE: Option<&'static str> = None;
// define the class attribute
pub fn class<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
cx.attr("class", val, None, false)
}
// more attributes
}
```
You've probably noticed that many elements in the `rsx!` macros support on-hover documentation. The approach we take to custom elements means that the unit struct is created immediately where the element is used in the macro. When the macro is expanded, the doc comments still apply to the unit struct, giving tons of in-editor feedback, even inside a proc macro.
# Native Core
Renderers take a lot of work. If you are creating a renderer in rust, native core provides some utilites to implement a renderer. It provides an abstraction over DomEdits and handles layout for you.
## RealDom
The `RealDom` is a higher level abstraction over updating the Dom. It updates with `DomEdits` and provides a way to lazily update the state of nodes based on what attributes change.
### Example
Let's build a toy renderer with borders, size, and text color.
Before we start lets take a look at an exaple element we can render:
```rust
cx.render(rsx!{
div{
color: "red",
p{
border: "1px solid black",
"hello world"
}
}
})
```
In this tree the color depends on the parent's color. The size depends on the childrens size, the current text, and a text size. The border depends on only the current node.
```mermaid
flowchart TB
subgraph context
text_width(text width)
end
subgraph div
state1(state)-->color1(color)
state1(state)-->border1(border)
border1-.->text_width
linkStyle 2 stroke:#5555ff,stroke-width:4px;
state1(state)-->layout_width1(layout width)
end
subgraph p
state2(state)-->color2(color)
color2-.->color1(color)
linkStyle 5 stroke:#0000ff,stroke-width:4px;
state2(state)-->border2(border)
border2-.->text_width
linkStyle 7 stroke:#5555ff,stroke-width:4px;
state2(state)-->layout_width2(layout width)
layout_width1-.->layout_width2
linkStyle 9 stroke:#aaaaff,stroke-width:4px;
end
subgraph hello world
state3(state)-->color3(color)
color3-.->color2(color)
linkStyle 11 stroke:#0000ff,stroke-width:4px;
state3(state)-->border3(border)
border3-.->text_width
linkStyle 13 stroke:#5555ff,stroke-width:4px;
state3(state)-->layout_width3(layout width)
layout_width2-.->layout_width3
linkStyle 15 stroke:#aaaaff,stroke-width:4px;
end
```
To help in building a Dom, native core provides four traits: State, ChildDepState, ParentDepState, and NodeDepState and a RealDom struct.
```rust
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(f32, f32);
// 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 = f32;
// 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::DepState>,
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 f32 * ctx;
height = ctx;
} else {
// otherwise, the size is the maximum size of the children
width = *children
.reduce(|accum, item| if accum >= item.0 { accum } else { item.0 })
.unwrap_or(0.0));
height = *children
.reduce(|accum, item| if accum >= item.1 { accum } else { item.1 })
.unwrap_or(&0.0);
}
// if the node contains a width or height attribute it overrides the other size
for a in node.attibutes(){
match a.name{
"width" => width = a.value.parse().unwrap(),
"height" => height = a.value.parse().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::DepState>,
_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().next() {
// 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 = ();
// Border does not depended on any other member in the current node
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: &Self::DepState, _ctx: &Self::Ctx) -> bool {
// check if the node contians a border attribute
let new = Self(node.attributes().next().map(|a| a.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,
}
```
Now that we have our state, we can put it to use in our dom. Re can update the dom with update_state to update the structure of the dom (adding, removing, and chaning properties of nodes) and then apply_mutations to update the ToyState for each of the nodes that changed.
```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();
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.3);
// update the ToyState for nodes in the real_dom tree
let _to_rerender = rdom.update_state(&dom, to_update, ctx).unwrap();
// 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.3);
let _to_rerender = rdom.update_state(vdom, to_update, ctx).unwrap();
// render...
}
})
}
```
## Layout
For most platforms the layout of the Elements will stay the same. The layout_attributes module provides a way to apply html attributes to a stretch layout style.
## Conclusion
That should be it! You should have nearly all the knowledge required on how to implement your own renderer. We're super interested in seeing Dioxus apps brought to custom desktop renderers, mobile renderer, video game UI, and even augmented reality! If you're interesting in contributing to any of the these projects, don't be afraid to reach out or join the community.
+3 -5
View File
@@ -53,7 +53,6 @@ $ cargo run
Press "ctrl-c" to close the app. To switch from "ctrl-c" to just "q" to quit you can launch the app with a configuration to disable the default quit and use the root TuiContext to quit on your own.
```rust
// main
use dioxus::events::{KeyCode, KeyboardEvent};
use dioxus::prelude::*;
use dioxus::tui::TuiContext;
@@ -61,11 +60,10 @@ use dioxus::tui::TuiContext;
fn main() {
dioxus::tui::launch_cfg(
app,
dioxus::tui::Config {
ctrl_c_quit: false,
dioxus::tui::Config::new()
.without_ctrl_c_quit()
// Some older terminals only support 16 colors or ANSI colors if your terminal is one of these change this to BaseColors or ANSI
rendering_mode: dioxus::tui::RenderingMode::Rgb,
},
.with_rendering_mode(dioxus::tui::RenderingMode::Rgb),
);
}
+30
View File
@@ -0,0 +1,30 @@
//! This example shows to wrap a webcomponent / custom element with a component.
//!
//! Oftentimes, a third party library will provide a webcomponent that you want
//! to use in your application. This example shows how to create that custom element
//! directly with the raw_element method on NodeFactory.
use dioxus::prelude::*;
fn main() {
let mut dom = VirtualDom::new(app);
let _ = dom.rebuild();
let output = dioxus::ssr::render_vdom(&dom);
println!("{}", output);
}
fn app(cx: Scope) -> Element {
let nf = NodeFactory::new(&cx);
let mut attrs = dioxus::core::exports::bumpalo::collections::Vec::new_in(nf.bump());
attrs.push(nf.attr("client-id", format_args!("abc123"), None, false));
attrs.push(nf.attr("name", format_args!("bob"), None, false));
attrs.push(nf.attr("age", format_args!("47"), None, false));
Some(nf.raw_element("my-element", None, &[], attrs.into_bump_slice(), &[], None))
}
+26
View File
@@ -14,6 +14,7 @@ fn app(cx: Scope) -> Element {
cx.render(rsx! {
div { "hello {name}!" }
Child {}
ChildWithRef{}
})
}
@@ -27,3 +28,28 @@ fn Child(cx: Scope) -> Element {
}
})
}
static NAMES: AtomRef<Vec<String>> = |_| vec!["world".to_string()];
fn ChildWithRef(cx: Scope) -> Element {
let names = use_atom_ref(&cx, NAMES);
cx.render(rsx! {
div {
ul {
names.read().iter().map(|f| rsx!{
li { "hello: {f}" }
})
}
button {
onclick: move |_| {
let names = names.clone();
cx.spawn(async move {
names.write().push("asd".to_string());
})
},
"Add name"
}
}
})
}
+56
View File
@@ -0,0 +1,56 @@
use dioxus::prelude::*;
use dioxus_core::UiEvent;
use dioxus_html::on::MouseData;
fn main() {
dioxus::desktop::launch(app);
}
fn app(cx: Scope) -> Element {
let page_coordinates = use_state(&cx, || "".to_string());
let screen_coordinates = use_state(&cx, || "".to_string());
let element_coordinates = use_state(&cx, || "".to_string());
let buttons = use_state(&cx, || "".to_string());
let modifiers = use_state(&cx, || "".to_string());
let container_style = r#"
display: flex;
flex-direction: column;
align-items: center;
"#;
let rect_style = r#"
background: deepskyblue;
height: 50vh;
width: 50vw;
"#;
let update_mouse_position = move |event: UiEvent<MouseData>| {
let mouse_data = event.data;
page_coordinates.set(format!("{:?}", mouse_data.page_coordinates()));
screen_coordinates.set(format!("{:?}", mouse_data.screen_coordinates()));
element_coordinates.set(format!("{:?}", mouse_data.element_coordinates()));
// Note: client coordinates are also available, but they would be the same as the page coordinates in this example, because there is no scrolling.
buttons.set(format!("{:?}", mouse_data.held_buttons()));
modifiers.set(format!("{:?}", mouse_data.modifiers()));
};
cx.render(rsx! (
div {
style: "{container_style}",
"Hover over to display coordinates:",
div {
style: "{rect_style}",
onmousemove: update_mouse_position,
prevent_default: "mousedown",
}
div {"Page coordinates: {page_coordinates}"},
div {"Screen coordinates: {screen_coordinates}"},
div {"Element coordinates: {element_coordinates}"},
div {"Buttons: {buttons}"},
div {"Modifiers: {modifiers}"},
}
))
}
+29 -2
View File
@@ -1,6 +1,7 @@
use std::{convert::TryInto, sync::Arc};
use dioxus::{events::MouseData, prelude::*};
use dioxus_core::UiEvent;
fn main() {
dioxus::tui::launch(app);
@@ -12,7 +13,7 @@ fn app(cx: Scope) -> Element {
}
fn get_brightness(m: Arc<MouseData>) -> i32 {
let b: i32 = m.buttons.count_ones().try_into().unwrap();
let b: i32 = m.held_buttons().len().try_into().unwrap();
127 * b
}
@@ -26,6 +27,24 @@ fn app(cx: Scope) -> Element {
let q3_color_str = to_str(q3_color);
let q4_color_str = to_str(q4_color);
let page_coordinates = use_state(&cx, || "".to_string());
let element_coordinates = use_state(&cx, || "".to_string());
let buttons = use_state(&cx, || "".to_string());
let modifiers = use_state(&cx, || "".to_string());
let update_data = move |event: UiEvent<MouseData>| {
let mouse_data = event.data;
page_coordinates.set(format!("{:?}", mouse_data.page_coordinates()));
element_coordinates.set(format!("{:?}", mouse_data.element_coordinates()));
// Note: client coordinates are also available, but they would be the same as the page coordinates in this example, because there is no scrolling.
// There are also screen coordinates, but they are currently the same as client coordinates due to technical limitations
buttons.set(format!("{:?}", mouse_data.held_buttons()));
modifiers.set(format!("{:?}", mouse_data.modifiers()));
};
cx.render(rsx! {
div {
width: "100%",
@@ -48,6 +67,7 @@ fn app(cx: Scope) -> Element {
onmouseup: move |m| q1_color.set([get_brightness(m.data), 0, 0]),
onwheel: move |w| q1_color.set([q1_color[0] + (10.0*w.delta_y) as i32, 0, 0]),
onmouseleave: move |_| q1_color.set([200; 3]),
onmousemove: update_data,
"click me"
}
div {
@@ -61,6 +81,7 @@ fn app(cx: Scope) -> Element {
onmouseup: move |m| q2_color.set([get_brightness(m.data); 3]),
onwheel: move |w| q2_color.set([q2_color[0] + (10.0*w.delta_y) as i32;3]),
onmouseleave: move |_| q2_color.set([200; 3]),
onmousemove: update_data,
"click me"
}
}
@@ -80,6 +101,7 @@ fn app(cx: Scope) -> Element {
onmouseup: move |m| q3_color.set([0, get_brightness(m.data), 0]),
onwheel: move |w| q3_color.set([0, q3_color[1] + (10.0*w.delta_y) as i32, 0]),
onmouseleave: move |_| q3_color.set([200; 3]),
onmousemove: update_data,
"click me"
}
div {
@@ -93,9 +115,14 @@ fn app(cx: Scope) -> Element {
onmouseup: move |m| q4_color.set([0, 0, get_brightness(m.data)]),
onwheel: move |w| q4_color.set([0, 0, q4_color[2] + (10.0*w.delta_y) as i32]),
onmouseleave: move |_| q4_color.set([200; 3]),
onmousemove: update_data,
"click me"
}
}
},
div {"Page coordinates: {page_coordinates}"},
div {"Element coordinates: {element_coordinates}"},
div {"Buttons: {buttons}"},
div {"Modifiers: {modifiers}"},
}
})
}
+9 -7
View File
@@ -1,5 +1,7 @@
use dioxus::events::WheelEvent;
use dioxus::prelude::*;
use dioxus_html::geometry::ScreenPoint;
use dioxus_html::input_data::MouseButtonSet;
use dioxus_html::on::{KeyboardEvent, MouseEvent};
use dioxus_html::KeyCode;
@@ -9,9 +11,9 @@ fn main() {
fn app(cx: Scope) -> Element {
let key = use_state(&cx, || "".to_string());
let mouse = use_state(&cx, || (0, 0));
let mouse = use_state(&cx, || ScreenPoint::zero());
let count = use_state(&cx, || 0);
let buttons = use_state(&cx, || 0);
let buttons = use_state(&cx, || MouseButtonSet::empty());
let mouse_clicked = use_state(&cx, || false);
cx.render(rsx! {
@@ -36,21 +38,21 @@ fn app(cx: Scope) -> Element {
count.set(count + evt.data.delta_y as i64);
},
ondrag: move |evt: MouseEvent| {
mouse.set((evt.data.screen_x, evt.data.screen_y));
mouse.set(evt.data.screen_coordinates());
},
onmousedown: move |evt: MouseEvent| {
mouse.set((evt.data.screen_x, evt.data.screen_y));
buttons.set(evt.data.buttons);
mouse.set(evt.data.screen_coordinates());
buttons.set(evt.data.held_buttons());
mouse_clicked.set(true);
},
onmouseup: move |evt: MouseEvent| {
buttons.set(evt.data.buttons);
buttons.set(evt.data.held_buttons());
mouse_clicked.set(false);
},
"count: {count:?}",
"key: {key}",
"mouse buttons: {buttons:b}",
"mouse buttons: {buttons:?}",
"mouse pos: {mouse:?}",
"mouse button pressed: {mouse_clicked}"
}
+23
View File
@@ -0,0 +1,23 @@
use dioxus::desktop::use_window;
use dioxus::prelude::*;
fn main() {
dioxus::desktop::launch(app);
}
fn app(cx: Scope) -> Element {
let window = use_window(&cx);
let level = use_state(&cx, || 1.0);
cx.render(rsx! {
input {
r#type: "number",
value: "{level}",
oninput: |e| {
let num = e.value.parse::<f64>().unwrap_or(1.0);
level.set(num);
window.set_zoom_level(num);
}
}
})
}
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "dioxus-core-macro"
version = "0.2.0"
version = "0.2.1"
authors = ["Jonathan Kelley"]
edition = "2021"
description = "Core macro for Dioxus Virtual DOM"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "dioxus-core"
version = "0.2.0"
version = "0.2.1"
authors = ["Jonathan Kelley"]
edition = "2018"
description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
+239
View File
@@ -0,0 +1,239 @@
use std::fmt::Formatter;
/// Possible values for an attribute
// trying to keep values at 3 bytes
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", serde(untagged))]
#[derive(Clone, Debug, PartialEq)]
#[allow(missing_docs)]
pub enum AttributeValue<'a> {
Text(&'a str),
Float32(f32),
Float64(f64),
Int32(i32),
Int64(i64),
Uint32(u32),
Uint64(u64),
Bool(bool),
Vec3Float(f32, f32, f32),
Vec3Int(i32, i32, i32),
Vec3Uint(u32, u32, u32),
Vec4Float(f32, f32, f32, f32),
Vec4Int(i32, i32, i32, i32),
Vec4Uint(u32, u32, u32, u32),
Bytes(&'a [u8]),
Any(ArbitraryAttributeValue<'a>),
}
// todo
#[allow(missing_docs)]
impl<'a> AttributeValue<'a> {
pub fn is_truthy(&self) -> bool {
match self {
AttributeValue::Text(t) => *t == "true",
AttributeValue::Bool(t) => *t,
_ => false,
}
}
pub fn is_falsy(&self) -> bool {
match self {
AttributeValue::Text(t) => *t == "false",
AttributeValue::Bool(t) => !(*t),
_ => false,
}
}
}
impl<'a> std::fmt::Display for AttributeValue<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
AttributeValue::Text(a) => write!(f, "{}", a),
AttributeValue::Float32(a) => write!(f, "{}", a),
AttributeValue::Float64(a) => write!(f, "{}", a),
AttributeValue::Int32(a) => write!(f, "{}", a),
AttributeValue::Int64(a) => write!(f, "{}", a),
AttributeValue::Uint32(a) => write!(f, "{}", a),
AttributeValue::Uint64(a) => write!(f, "{}", a),
AttributeValue::Bool(a) => write!(f, "{}", a),
AttributeValue::Vec3Float(_, _, _) => todo!(),
AttributeValue::Vec3Int(_, _, _) => todo!(),
AttributeValue::Vec3Uint(_, _, _) => todo!(),
AttributeValue::Vec4Float(_, _, _, _) => todo!(),
AttributeValue::Vec4Int(_, _, _, _) => todo!(),
AttributeValue::Vec4Uint(_, _, _, _) => todo!(),
AttributeValue::Bytes(_) => todo!(),
AttributeValue::Any(_) => todo!(),
}
}
}
#[derive(Clone, Copy)]
pub struct ArbitraryAttributeValue<'a> {
pub value: &'a dyn std::any::Any,
pub cmp: fn(&'a dyn std::any::Any, &'a dyn std::any::Any) -> bool,
}
impl PartialEq for ArbitraryAttributeValue<'_> {
fn eq(&self, other: &Self) -> bool {
(self.cmp)(self.value, other.value)
}
}
impl std::fmt::Debug for ArbitraryAttributeValue<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ArbitraryAttributeValue").finish()
}
}
#[cfg(feature = "serialize")]
impl<'a> serde::Serialize for ArbitraryAttributeValue<'a> {
fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
panic!("ArbitraryAttributeValue should not be serialized")
}
}
#[cfg(feature = "serialize")]
impl<'de, 'a> serde::Deserialize<'de> for &'a ArbitraryAttributeValue<'a> {
fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
panic!("ArbitraryAttributeValue is not deserializable!")
}
}
#[cfg(feature = "serialize")]
impl<'de, 'a> serde::Deserialize<'de> for ArbitraryAttributeValue<'a> {
fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
panic!("ArbitraryAttributeValue is not deserializable!")
}
}
// todo
#[allow(missing_docs)]
impl<'a> AttributeValue<'a> {
pub fn as_text(&self) -> Option<&'a str> {
match self {
AttributeValue::Text(s) => Some(s),
_ => None,
}
}
pub fn as_float32(&self) -> Option<f32> {
match self {
AttributeValue::Float32(f) => Some(*f),
_ => None,
}
}
pub fn as_float64(&self) -> Option<f64> {
match self {
AttributeValue::Float64(f) => Some(*f),
_ => None,
}
}
pub fn as_int32(&self) -> Option<i32> {
match self {
AttributeValue::Int32(i) => Some(*i),
_ => None,
}
}
pub fn as_int64(&self) -> Option<i64> {
match self {
AttributeValue::Int64(i) => Some(*i),
_ => None,
}
}
pub fn as_uint32(&self) -> Option<u32> {
match self {
AttributeValue::Uint32(i) => Some(*i),
_ => None,
}
}
pub fn as_uint64(&self) -> Option<u64> {
match self {
AttributeValue::Uint64(i) => Some(*i),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
AttributeValue::Bool(b) => Some(*b),
_ => None,
}
}
pub fn as_vec3_float(&self) -> Option<(f32, f32, f32)> {
match self {
AttributeValue::Vec3Float(x, y, z) => Some((*x, *y, *z)),
_ => None,
}
}
pub fn as_vec3_int(&self) -> Option<(i32, i32, i32)> {
match self {
AttributeValue::Vec3Int(x, y, z) => Some((*x, *y, *z)),
_ => None,
}
}
pub fn as_vec3_uint(&self) -> Option<(u32, u32, u32)> {
match self {
AttributeValue::Vec3Uint(x, y, z) => Some((*x, *y, *z)),
_ => None,
}
}
pub fn as_vec4_float(&self) -> Option<(f32, f32, f32, f32)> {
match self {
AttributeValue::Vec4Float(x, y, z, w) => Some((*x, *y, *z, *w)),
_ => None,
}
}
pub fn as_vec4_int(&self) -> Option<(i32, i32, i32, i32)> {
match self {
AttributeValue::Vec4Int(x, y, z, w) => Some((*x, *y, *z, *w)),
_ => None,
}
}
pub fn as_vec4_uint(&self) -> Option<(u32, u32, u32, u32)> {
match self {
AttributeValue::Vec4Uint(x, y, z, w) => Some((*x, *y, *z, *w)),
_ => None,
}
}
pub fn as_bytes(&self) -> Option<&[u8]> {
match self {
AttributeValue::Bytes(b) => Some(b),
_ => None,
}
}
pub fn as_any(&self) -> Option<&'a ArbitraryAttributeValue> {
match self {
AttributeValue::Any(a) => Some(a),
_ => None,
}
}
}
// #[test]
// fn test_attribute_value_size() {
// assert_eq!(std::mem::size_of::<AttributeValue<'_>>(), 24);
// }
+1 -1
View File
@@ -49,7 +49,7 @@ impl BubbleState {
/// }
/// )).unwrap();
/// ```
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct UserEvent {
/// The originator of the event trigger if available
pub scope_id: Option<ScopeId>,
+6 -4
View File
@@ -2,6 +2,7 @@
#![doc = include_str!("../README.md")]
#![deny(missing_docs)]
pub(crate) mod arbitrary_value;
pub(crate) mod diff;
pub(crate) mod events;
pub(crate) mod lazynodes;
@@ -13,6 +14,7 @@ pub(crate) mod util;
pub(crate) mod virtual_dom;
pub(crate) mod innerlude {
pub use crate::arbitrary_value::*;
pub use crate::events::*;
pub use crate::lazynodes::*;
pub use crate::mutations::*;
@@ -71,10 +73,10 @@ pub(crate) mod innerlude {
}
pub use crate::innerlude::{
AnyEvent, Attribute, Component, DioxusElement, DomEdit, Element, ElementId, ElementIdIterator,
EventHandler, EventPriority, IntoVNode, LazyNodes, Listener, Mutations, NodeFactory,
Properties, SchedulerMsg, Scope, ScopeId, ScopeState, TaskId, UiEvent, UserEvent, VComponent,
VElement, VFragment, VNode, VPlaceholder, VText, VirtualDom,
AnyEvent, Attribute, AttributeValue, Component, DioxusElement, DomEdit, Element, ElementId,
ElementIdIterator, EventHandler, EventPriority, IntoVNode, LazyNodes, Listener, Mutations,
NodeFactory, Properties, SchedulerMsg, Scope, ScopeId, ScopeState, TaskId, UiEvent, UserEvent,
VComponent, VElement, VFragment, VNode, VPlaceholder, VText, VirtualDom,
};
/// The purpose of this module is to alleviate imports of many common types
+4 -3
View File
@@ -167,8 +167,9 @@ pub enum DomEdit<'bump> {
field: &'static str,
/// The value of the attribute.
value: &'bump str,
value: AttributeValue<'bump>,
// value: &'bump str,
/// The (optional) namespace of the attribute.
/// For instance, "style" is in the "style" namespace.
ns: Option<&'bump str>,
@@ -286,7 +287,7 @@ impl<'a> Mutations<'a> {
self.edits.push(SetText { text, root });
}
pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute, root: u64) {
pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute<'a>, root: u64) {
let Attribute {
name,
value,
@@ -296,7 +297,7 @@ impl<'a> Mutations<'a> {
self.edits.push(SetAttribute {
field: name,
value,
value: value.clone(),
ns: *namespace,
root,
});
+20 -2
View File
@@ -4,7 +4,7 @@
//! cheap and *very* fast to construct - building a full tree should be quick.
use crate::{
innerlude::{ComponentPtr, Element, Properties, Scope, ScopeId, ScopeState},
innerlude::{AttributeValue, ComponentPtr, Element, Properties, Scope, ScopeId, ScopeState},
lazynodes::LazyNodes,
AnyEvent, Component,
};
@@ -339,7 +339,7 @@ pub struct Attribute<'a> {
pub name: &'static str,
/// The value of the attribute.
pub value: &'a str,
pub value: AttributeValue<'a>,
/// An indication if this attribute can be ignored during diffing
///
@@ -610,6 +610,24 @@ impl<'a> NodeFactory<'a> {
is_volatile: bool,
) -> Attribute<'a> {
let (value, is_static) = self.raw_text(val);
Attribute {
name,
value: AttributeValue::Text(value),
is_static,
namespace,
is_volatile,
}
}
/// Create a new [`Attribute`] using non-arguments
pub fn custom_attr(
&self,
name: &'static str,
value: AttributeValue<'a>,
namespace: Option<&'static str>,
is_volatile: bool,
is_static: bool,
) -> Attribute<'a> {
Attribute {
name,
value,
+6 -10
View File
@@ -1,6 +1,6 @@
[package]
name = "dioxus-desktop"
version = "0.2.2"
version = "0.2.3"
authors = ["Jonathan Kelley"]
edition = "2018"
description = "Dioxus VirtualDOM renderer for a remote webview instance"
@@ -12,15 +12,15 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dioxus-core = { path = "../core", version = "^0.2.0", features = ["serialize"] }
dioxus-html = { path = "../html", features = ["serialize"], version = "^0.2.0" }
dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.0" }
dioxus-core = { path = "../core", version = "^0.2.1", features = ["serialize"] }
dioxus-html = { path = "../html", features = ["serialize"], version = "^0.2.1" }
dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.1" }
serde = "1.0.136"
serde_json = "1.0.79"
thiserror = "1.0.30"
log = "0.4.14"
wry = { version = "0.13.1" }
wry = { version = "0.16.0" }
futures-channel = "0.3.21"
tokio = { version = "1.16.1", features = [
"sync",
@@ -28,7 +28,7 @@ tokio = { version = "1.16.1", features = [
"rt",
"time",
], optional = true, default-features = false }
webbrowser = "0.6.0"
webbrowser = "0.7.1"
mime_guess = "2.0.3"
dunce = "1.0.2"
@@ -38,13 +38,9 @@ core-foundation = "0.9.3"
[features]
default = ["tokio_runtime"]
tokio_runtime = ["tokio"]
devtool = ["wry/devtool"]
fullscreen = ["wry/fullscreen"]
transparent = ["wry/transparent"]
tray = ["wry/tray"]
ayatana = ["wry/ayatana"]
[dev-dependencies]
+28 -4
View File
@@ -115,6 +115,16 @@ impl DesktopContext {
let _ = self.proxy.send_event(SetDecorations(decoration));
}
/// set window zoom level
pub fn set_zoom_level(&self, scale_factor: f64) {
let _ = self.proxy.send_event(SetZoomLevel(scale_factor));
}
/// launch print modal
pub fn print(&self) {
let _ = self.proxy.send_event(Print);
}
/// opens DevTool window
pub fn devtool(&self) {
let _ = self.proxy.send_event(DevTool);
@@ -148,6 +158,9 @@ pub enum UserWindowEvent {
SetTitle(String),
SetDecorations(bool),
SetZoomLevel(f64),
Print,
DevTool,
Eval(String),
@@ -191,11 +204,22 @@ pub(super) fn handler(
SetTitle(content) => window.set_title(&content),
SetDecorations(state) => window.set_decorations(state),
DevTool => webview.devtool(),
SetZoomLevel(scale_factor) => webview.zoom(scale_factor),
Eval(code) => webview
.evaluate_script(code.as_str())
.expect("eval shouldn't panic"),
Print => {
if let Err(e) = webview.print() {
// we can't panic this error.
log::warn!("Open print modal failed: {e}");
}
}
DevTool => webview.open_devtools(),
Eval(code) => {
if let Err(e) = webview.evaluate_script(code.as_str()) {
// we can't panic this error.
log::warn!("Eval script error: {e}");
}
}
}
}
+1 -1
View File
@@ -200,7 +200,7 @@ pub fn launch_with_props<P: 'static + Send>(
)
} else {
// in debug, we are okay with the reload menu showing and dev tool
webview = webview.with_dev_tool(true);
webview = webview.with_devtools(true);
}
desktop.webviews.insert(window_id, webview.build().unwrap());
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "fermi"
version = "0.2.0"
version = "0.2.1"
authors = ["Jonathan Kelley"]
edition = "2018"
description = "Global state management for Dioxus"
@@ -13,7 +13,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dioxus-core = { path = "../core", version = "^0.2.0" }
dioxus-core = { path = "../core", version = "^0.2.1" }
im-rc = { version = "15.0.0", features = ["serde"] }
log = "0.4.14"
+38 -14
View File
@@ -16,15 +16,35 @@ use std::{
pub fn use_atom_ref<T: 'static>(cx: &ScopeState, atom: AtomRef<T>) -> &UseAtomRef<T> {
let root = use_atom_root(cx);
cx.use_hook(|_| {
&cx.use_hook(|_| {
root.initialize(atom);
UseAtomRef {
ptr: atom.unique_id(),
root: root.clone(),
scope_id: cx.scope_id(),
value: root.register(atom, cx.scope_id()),
}
(
UseAtomRef {
ptr: atom.unique_id(),
root: root.clone(),
scope_id: cx.scope_id(),
value: root.register(atom, cx.scope_id()),
},
AtomRefSubscription {
ptr: atom.unique_id(),
root: root.clone(),
scope_id: cx.scope_id(),
},
)
})
.0
}
pub struct AtomRefSubscription {
ptr: AtomId,
root: Rc<AtomRoot>,
scope_id: ScopeId,
}
impl Drop for AtomRefSubscription {
fn drop(&mut self) {
self.root.unsubscribe(self.ptr, self.scope_id)
}
}
pub struct UseAtomRef<T> {
@@ -34,6 +54,17 @@ pub struct UseAtomRef<T> {
scope_id: ScopeId,
}
impl<T> Clone for UseAtomRef<T> {
fn clone(&self) -> Self {
Self {
ptr: self.ptr,
value: self.value.clone(),
root: self.root.clone(),
scope_id: self.scope_id,
}
}
}
impl<T: 'static> UseAtomRef<T> {
pub fn read(&self) -> Ref<T> {
self.value.borrow()
@@ -45,7 +76,6 @@ impl<T: 'static> UseAtomRef<T> {
}
pub fn write_silent(&self) -> RefMut<T> {
self.root.force_update(self.ptr);
self.value.borrow_mut()
}
@@ -54,9 +84,3 @@ impl<T: 'static> UseAtomRef<T> {
self.root.set(self.ptr, new);
}
}
impl<T> Drop for UseAtomRef<T> {
fn drop(&mut self) {
self.root.unsubscribe(self.ptr, self.scope_id)
}
}
+3 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "dioxus-hooks"
version = "0.2.0"
version = "0.2.1"
authors = ["Jonathan Kelley"]
edition = "2018"
description = "Dioxus VirtualDOM renderer for a remote webview instance"
@@ -12,11 +12,11 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dioxus-core = { path = "../../packages/core", version = "^0.2.0" }
dioxus-core = { path = "../../packages/core", version = "^0.2.1" }
futures-channel = "0.3.21"
log = { version = "0.4" }
[dev-dependencies]
futures-util = { version = "0.3", default-features = false }
dioxus-core = { path = "../../packages/core", version = "^0.2.0" }
dioxus-core = { path = "../../packages/core", version = "^0.2.1" }
+3
View File
@@ -16,5 +16,8 @@ pub use usecoroutine::*;
mod usefuture;
pub use usefuture::*;
mod useeffect;
pub use useeffect::*;
// mod usesuspense;
// pub use usesuspense::*;
+1 -1
View File
@@ -24,7 +24,7 @@ use std::{
/// val.write().insert(1, "hello".to_string());
/// ```
///
/// You can avoid this defualt behavior with `write_silent`
/// You can avoid this default behavior with `write_silent`
///
/// ```
/// // with `write_silent`, the component will not be re-rendered
+26 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "dioxus-html"
version = "0.2.0"
version = "0.2.1"
authors = ["Jonathan Kelley"]
edition = "2018"
description = "HTML Element pack for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
@@ -11,10 +11,34 @@ documentation = "https://docs.rs/dioxus"
keywords = ["dom", "ui", "gui", "react", "wasm"]
[dependencies]
dioxus-core = { path = "../core", version = "^0.2.0" }
dioxus-core = { path = "../core", version = "^0.2.1" }
serde = { version = "1", features = ["derive"], optional = true }
serde_repr = { version = "0.1", optional = true }
wasm-bindgen = { version = "0.2.79", optional = true }
euclid = "0.22.7"
enumset = "1.0.11"
keyboard-types = "0.6.2"
[dependencies.web-sys]
optional = true
version = "0.3.56"
features = [
"TouchEvent",
"MouseEvent",
"InputEvent",
"ClipboardEvent",
"KeyboardEvent",
"TouchEvent",
"WheelEvent",
"AnimationEvent",
"TransitionEvent",
"PointerEvent",
"FocusEvent",
"CompositionEvent",
"ClipboardEvent",
]
[features]
default = []
serialize = ["serde", "serde_repr"]
wasm-bind = ["web-sys", "wasm-bindgen"]
+190 -15
View File
@@ -3,6 +3,13 @@ use dioxus_core::exports::bumpalo;
use dioxus_core::*;
pub mod on {
//! Input events and associated data
use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint};
use crate::input_data::{
decode_mouse_button_set, encode_mouse_button_set, MouseButton, MouseButtonSet,
};
use keyboard_types::Modifiers;
use std::collections::HashMap;
use super::*;
@@ -398,21 +405,21 @@ pub mod on {
pub type ClipboardEvent = UiEvent<ClipboardData>;
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct ClipboardData {
// DOMDataTransfer clipboardData
}
pub type CompositionEvent = UiEvent<CompositionData>;
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct CompositionData {
pub data: String,
}
pub type KeyboardEvent = UiEvent<KeyboardData>;
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct KeyboardData {
pub char_code: u32,
@@ -481,12 +488,12 @@ pub mod on {
pub type FocusEvent = UiEvent<FocusData>;
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct FocusData {/* DOMEventInner: Send + SyncTarget relatedTarget */}
pub type FormEvent = UiEvent<FormData>;
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct FormData {
pub value: String,
pub values: HashMap<String, String>,
@@ -495,26 +502,194 @@ pub mod on {
pub type MouseEvent = UiEvent<MouseData>;
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
#[derive(Debug, Clone)]
/// Data associated with a mouse event
///
/// Do not use the deprecated fields; they may change or become private in the future.
pub struct MouseData {
/// True if the alt key was down when the mouse event was fired.
#[deprecated(since = "0.3.0", note = "use modifiers() instead")]
pub alt_key: bool,
/// The button number that was pressed (if applicable) when the mouse event was fired.
#[deprecated(since = "0.3.0", note = "use trigger_button() instead")]
pub button: i16,
/// Indicates which buttons are pressed on the mouse (or other input device) when a mouse event is triggered.
///
/// Each button that can be pressed is represented by a given number (see below). If more than one button is pressed, the button values are added together to produce a new number. For example, if the secondary (2) and auxiliary (4) buttons are pressed simultaneously, the value is 6 (i.e., 2 + 4).
///
/// - 1: Primary button (usually the left button)
/// - 2: Secondary button (usually the right button)
/// - 4: Auxiliary button (usually the mouse wheel button or middle button)
/// - 8: 4th button (typically the "Browser Back" button)
/// - 16 : 5th button (typically the "Browser Forward" button)
#[deprecated(since = "0.3.0", note = "use held_buttons() instead")]
pub buttons: u16,
/// The horizontal coordinate within the application's viewport at which the event occurred (as opposed to the coordinate within the page).
///
/// For example, clicking on the left edge of the viewport will always result in a mouse event with a clientX value of 0, regardless of whether the page is scrolled horizontally.
#[deprecated(since = "0.3.0", note = "use client_coordinates() instead")]
pub client_x: i32,
/// The vertical coordinate within the application's viewport at which the event occurred (as opposed to the coordinate within the page).
///
/// For example, clicking on the top edge of the viewport will always result in a mouse event with a clientY value of 0, regardless of whether the page is scrolled vertically.
#[deprecated(since = "0.3.0", note = "use client_coordinates() instead")]
pub client_y: i32,
/// True if the control key was down when the mouse event was fired.
#[deprecated(since = "0.3.0", note = "use modifiers() instead")]
pub ctrl_key: bool,
/// True if the meta key was down when the mouse event was fired.
#[deprecated(since = "0.3.0", note = "use modifiers() instead")]
pub meta_key: bool,
/// The offset in the X coordinate of the mouse pointer between that event and the padding edge of the target node.
#[deprecated(since = "0.3.0", note = "use element_coordinates() instead")]
pub offset_x: i32,
/// The offset in the Y coordinate of the mouse pointer between that event and the padding edge of the target node.
#[deprecated(since = "0.3.0", note = "use element_coordinates() instead")]
pub offset_y: i32,
/// The X (horizontal) coordinate (in pixels) of the mouse, relative to the left edge of the entire document. This includes any portion of the document not currently visible.
///
/// Being based on the edge of the document as it is, this property takes into account any horizontal scrolling of the page. For example, if the page is scrolled such that 200 pixels of the left side of the document are scrolled out of view, and the mouse is clicked 100 pixels inward from the left edge of the view, the value returned by pageX will be 300.
#[deprecated(since = "0.3.0", note = "use page_coordinates() instead")]
pub page_x: i32,
/// The Y (vertical) coordinate in pixels of the event relative to the whole document.
///
/// See `page_x`.
#[deprecated(since = "0.3.0", note = "use page_coordinates() instead")]
pub page_y: i32,
/// The X coordinate of the mouse pointer in global (screen) coordinates.
#[deprecated(since = "0.3.0", note = "use screen_coordinates() instead")]
pub screen_x: i32,
/// The Y coordinate of the mouse pointer in global (screen) coordinates.
#[deprecated(since = "0.3.0", note = "use screen_coordinates() instead")]
pub screen_y: i32,
/// True if the shift key was down when the mouse event was fired.
#[deprecated(since = "0.3.0", note = "use modifiers() instead")]
pub shift_key: bool,
// fn get_modifier_state(&self, key_code: &str) -> bool;
}
impl MouseData {
/// Construct MouseData with the specified properties
///
/// Note: the current implementation truncates coordinates. In the future, when we change the internal representation, it may also support a fractional part.
pub fn new(
coordinates: Coordinates,
trigger_button: Option<MouseButton>,
held_buttons: MouseButtonSet,
modifiers: Modifiers,
) -> Self {
let alt_key = modifiers.contains(Modifiers::ALT);
let ctrl_key = modifiers.contains(Modifiers::CONTROL);
let meta_key = modifiers.contains(Modifiers::META);
let shift_key = modifiers.contains(Modifiers::SHIFT);
let [client_x, client_y]: [i32; 2] = coordinates.client().cast().into();
let [offset_x, offset_y]: [i32; 2] = coordinates.element().cast().into();
let [page_x, page_y]: [i32; 2] = coordinates.page().cast().into();
let [screen_x, screen_y]: [i32; 2] = coordinates.screen().cast().into();
#[allow(deprecated)]
Self {
alt_key,
ctrl_key,
meta_key,
shift_key,
button: trigger_button.map_or(0, |b| b.into_web_code()),
buttons: encode_mouse_button_set(held_buttons),
client_x,
client_y,
offset_x,
offset_y,
page_x,
page_y,
screen_x,
screen_y,
}
}
/// The event's coordinates relative to the application's viewport (as opposed to the coordinate within the page).
///
/// For example, clicking in the top left corner of the viewport will always result in a mouse event with client coordinates (0., 0.), regardless of whether the page is scrolled horizontally.
pub fn client_coordinates(&self) -> ClientPoint {
#[allow(deprecated)]
ClientPoint::new(self.client_x.into(), self.client_y.into())
}
/// The event's coordinates relative to the padding edge of the target element
///
/// For example, clicking in the top left corner of an element will result in element coordinates (0., 0.)
pub fn element_coordinates(&self) -> ElementPoint {
#[allow(deprecated)]
ElementPoint::new(self.offset_x.into(), self.offset_y.into())
}
/// The event's coordinates relative to the entire document. This includes any portion of the document not currently visible.
///
/// For example, if the page is scrolled 200 pixels to the right and 300 pixels down, clicking in the top left corner of the viewport would result in page coordinates (200., 300.)
pub fn page_coordinates(&self) -> PagePoint {
#[allow(deprecated)]
PagePoint::new(self.page_x.into(), self.page_y.into())
}
/// The event's coordinates relative to the entire screen. This takes into account the window's offset.
pub fn screen_coordinates(&self) -> ScreenPoint {
#[allow(deprecated)]
ScreenPoint::new(self.screen_x.into(), self.screen_y.into())
}
pub fn coordinates(&self) -> Coordinates {
Coordinates::new(
self.screen_coordinates(),
self.client_coordinates(),
self.element_coordinates(),
self.page_coordinates(),
)
}
/// The set of modifier keys which were pressed when the event occurred
pub fn modifiers(&self) -> Modifiers {
let mut modifiers = Modifiers::empty();
#[allow(deprecated)]
{
if self.alt_key {
modifiers.insert(Modifiers::ALT);
}
if self.ctrl_key {
modifiers.insert(Modifiers::CONTROL);
}
if self.meta_key {
modifiers.insert(Modifiers::META);
}
if self.shift_key {
modifiers.insert(Modifiers::SHIFT);
}
}
modifiers
}
/// The set of mouse buttons which were held when the event occurred.
pub fn held_buttons(&self) -> MouseButtonSet {
#[allow(deprecated)]
decode_mouse_button_set(self.buttons)
}
/// The mouse button that triggered the event
///
// todo the following is kind of bad; should we just return None when the trigger_button is unreliable (and frankly irrelevant)? i guess we would need the event_type here
/// This is only guaranteed to indicate which button was pressed during events caused by pressing or releasing a button. As such, it is not reliable for events such as mouseenter, mouseleave, mouseover, mouseout, or mousemove. For example, a value of MouseButton::Primary may also indicate that no button was pressed.
pub fn trigger_button(&self) -> Option<MouseButton> {
#[allow(deprecated)]
Some(MouseButton::from_web_code(self.button))
}
}
pub type PointerEvent = UiEvent<PointerData>;
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct PointerData {
// Mouse only
pub alt_key: bool,
@@ -544,12 +719,12 @@ pub mod on {
pub type SelectionEvent = UiEvent<SelectionData>;
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct SelectionData {}
pub type TouchEvent = UiEvent<TouchData>;
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct TouchData {
pub alt_key: bool,
pub ctrl_key: bool,
@@ -563,7 +738,7 @@ pub mod on {
pub type WheelEvent = UiEvent<WheelData>;
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct WheelData {
pub delta_mode: u32,
pub delta_x: f64,
@@ -573,19 +748,19 @@ pub mod on {
pub type MediaEvent = UiEvent<MediaData>;
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct MediaData {}
pub type ImageEvent = UiEvent<ImageData>;
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct ImageData {
pub load_error: bool,
}
pub type AnimationEvent = UiEvent<AnimationData>;
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct AnimationData {
pub animation_name: String,
pub pseudo_element: String,
@@ -594,7 +769,7 @@ pub mod on {
pub type TransitionEvent = UiEvent<TransitionData>;
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct TransitionData {
pub property_name: String,
pub pseudo_element: String,
@@ -603,7 +778,7 @@ pub mod on {
pub type ToggleEvent = UiEvent<ToggleData>;
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct ToggleData {}
}
+73
View File
@@ -0,0 +1,73 @@
//! Geometry primitives for representing e.g. mouse events
/// A re-export of euclid, which we use for geometry primitives
pub use euclid;
use euclid::*;
/// Coordinate space relative to the screen
pub struct ScreenSpace;
/// A point in ScreenSpace
pub type ScreenPoint = Point2D<f64, ScreenSpace>;
/// Coordinate space relative to the viewport
pub struct ClientSpace;
/// A point in ClientSpace
pub type ClientPoint = Point2D<f64, ClientSpace>;
/// Coordinate space relative to an element
pub struct ElementSpace;
/// A point in ElementSpace
pub type ElementPoint = Point2D<f64, ElementSpace>;
/// Coordinate space relative to the page
pub struct PageSpace;
/// A point in PageSpace
pub type PagePoint = Point2D<f64, PageSpace>;
/// Coordinates of a point in the app's interface
pub struct Coordinates {
screen: ScreenPoint,
client: ClientPoint,
element: ElementPoint,
page: PagePoint,
}
impl Coordinates {
/// Construct new coordinates with the specified screen-, client-, element- and page-relative points
pub fn new(
screen: ScreenPoint,
client: ClientPoint,
element: ElementPoint,
page: PagePoint,
) -> Self {
Self {
screen,
client,
element,
page,
}
}
/// Coordinates relative to the entire screen. This takes into account the window's offset.
pub fn screen(&self) -> ScreenPoint {
self.screen
}
/// Coordinates relative to the application's viewport (as opposed to the coordinate within the page).
///
/// For example, clicking in the top left corner of the viewport will always result in a mouse event with client coordinates (0., 0.), regardless of whether the page is scrolled horizontally.
pub fn client(&self) -> ClientPoint {
self.client
}
/// Coordinates relative to the padding edge of the target element
///
/// For example, clicking in the top left corner of an element will result in element coordinates (0., 0.)
pub fn element(&self) -> ElementPoint {
self.element
}
/// Coordinates relative to the entire document. This includes any portion of the document not currently visible.
///
/// For example, if the page is scrolled 200 pixels to the right and 300 pixels down, clicking in the top left corner of the viewport would result in page coordinates (200., 300.)
pub fn page(&self) -> PagePoint {
self.page
}
}
+120
View File
@@ -0,0 +1,120 @@
//! Data structures representing user input, such as modifier keys and mouse buttons
use enumset::{EnumSet, EnumSetType};
/// A re-export of keyboard_types
pub use keyboard_types;
/// A mouse button type (such as Primary/Secondary)
// note: EnumSetType also derives Copy and Clone for some reason
#[derive(EnumSetType, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
pub enum MouseButton {
/// Primary button (typically the left button)
Primary,
/// Secondary button (typically the right button)
Secondary,
/// Auxiliary button (typically the middle button)
Auxiliary,
/// Fourth button (typically the "Browser Back" button)
Fourth,
/// Fifth button (typically the "Browser Forward" button)
Fifth,
/// A button with an unknown code
Unknown,
}
impl MouseButton {
/// Constructs a MouseButton for the specified button code
///
/// E.g. 0 => Primary; 1 => Auxiliary
///
/// Unknown codes get mapped to MouseButton::Unknown.
pub fn from_web_code(code: i16) -> Self {
match code {
0 => MouseButton::Primary,
// not a typo; auxiliary and secondary are swapped unlike in the `buttons` field.
// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
1 => MouseButton::Auxiliary,
2 => MouseButton::Secondary,
3 => MouseButton::Fourth,
4 => MouseButton::Fifth,
_ => MouseButton::Unknown,
}
}
/// Converts MouseButton into the corresponding button code
///
/// MouseButton::Unknown will get mapped to -1
pub fn into_web_code(self) -> i16 {
match self {
MouseButton::Primary => 0,
// not a typo; auxiliary and secondary are swapped unlike in the `buttons` field.
// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
MouseButton::Auxiliary => 1,
MouseButton::Secondary => 2,
MouseButton::Fourth => 3,
MouseButton::Fifth => 4,
MouseButton::Unknown => -1,
}
}
}
/// A set of mouse buttons
pub type MouseButtonSet = EnumSet<MouseButton>;
pub fn decode_mouse_button_set(code: u16) -> MouseButtonSet {
let mut set = EnumSet::empty();
// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons
#[allow(deprecated)]
{
if code & 0b1 != 0 {
set |= MouseButton::Primary;
}
if code & 0b10 != 0 {
set |= MouseButton::Secondary;
}
if code & 0b100 != 0 {
set |= MouseButton::Auxiliary;
}
if code & 0b1000 != 0 {
set |= MouseButton::Fourth;
}
if code & 0b10000 != 0 {
set |= MouseButton::Fifth;
}
if code & (!0b11111) != 0 {
set |= MouseButton::Unknown;
}
}
set
}
pub fn encode_mouse_button_set(set: MouseButtonSet) -> u16 {
let mut code = 0;
// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons
{
if set.contains(MouseButton::Primary) {
code |= 0b1;
}
if set.contains(MouseButton::Secondary) {
code |= 0b10;
}
if set.contains(MouseButton::Auxiliary) {
code |= 0b100;
}
if set.contains(MouseButton::Fourth) {
code |= 0b1000;
}
if set.contains(MouseButton::Fifth) {
code |= 0b10000;
}
if set.contains(MouseButton::Unknown) {
code |= 0b100000;
}
}
code
}
+4
View File
@@ -15,7 +15,11 @@
mod elements;
mod events;
pub mod geometry;
mod global_attributes;
pub mod input_data;
#[cfg(feature = "wasm-bind")]
mod web_sys_bind;
pub use elements::*;
pub use events::*;
+176
View File
@@ -0,0 +1,176 @@
use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint};
use crate::input_data::{decode_mouse_button_set, MouseButton};
use crate::on::{
AnimationData, CompositionData, KeyboardData, MouseData, PointerData, TouchData,
TransitionData, WheelData,
};
use crate::KeyCode;
use keyboard_types::Modifiers;
use wasm_bindgen::JsCast;
use web_sys::{
AnimationEvent, CompositionEvent, Event, KeyboardEvent, MouseEvent, PointerEvent, TouchEvent,
TransitionEvent, WheelEvent,
};
macro_rules! uncheck_convert {
($t:ty, $d:ty) => {
impl From<Event> for $d {
#[inline]
fn from(e: Event) -> Self {
let e: $t = e.unchecked_into();
Self::from(&e)
}
}
impl From<&Event> for $d {
#[inline]
fn from(e: &Event) -> Self {
let e: &$t = e.unchecked_ref();
Self::from(e)
}
}
};
($($t:ty => $d:ty),+ $(,)?) => {
$(uncheck_convert!($t, $d);)+
};
}
uncheck_convert![
CompositionEvent => CompositionData,
KeyboardEvent => KeyboardData,
MouseEvent => MouseData,
TouchEvent => TouchData,
PointerEvent => PointerData,
WheelEvent => WheelData,
AnimationEvent => AnimationData,
TransitionEvent => TransitionData,
];
impl From<&CompositionEvent> for CompositionData {
fn from(e: &CompositionEvent) -> Self {
Self {
data: e.data().unwrap_or_default(),
}
}
}
impl From<&KeyboardEvent> for KeyboardData {
fn from(e: &KeyboardEvent) -> Self {
Self {
alt_key: e.alt_key(),
char_code: e.char_code(),
key: e.key(),
key_code: KeyCode::from_raw_code(e.key_code() as u8),
ctrl_key: e.ctrl_key(),
locale: "not implemented".to_string(),
location: e.location() as usize,
meta_key: e.meta_key(),
repeat: e.repeat(),
shift_key: e.shift_key(),
which: e.which() as usize,
}
}
}
impl From<&MouseEvent> for MouseData {
fn from(e: &MouseEvent) -> Self {
let mut modifiers = Modifiers::empty();
if e.alt_key() {
modifiers.insert(Modifiers::ALT);
}
if e.ctrl_key() {
modifiers.insert(Modifiers::CONTROL);
}
if e.meta_key() {
modifiers.insert(Modifiers::META);
}
if e.shift_key() {
modifiers.insert(Modifiers::SHIFT);
}
MouseData::new(
Coordinates::new(
ScreenPoint::new(e.screen_x().into(), e.screen_y().into()),
ClientPoint::new(e.client_x().into(), e.client_y().into()),
ElementPoint::new(e.offset_x().into(), e.offset_y().into()),
PagePoint::new(e.page_x().into(), e.page_y().into()),
),
Some(MouseButton::from_web_code(e.button().into())),
decode_mouse_button_set(e.buttons()),
modifiers,
)
}
}
impl From<&TouchEvent> for TouchData {
fn from(e: &TouchEvent) -> Self {
Self {
alt_key: e.alt_key(),
ctrl_key: e.ctrl_key(),
meta_key: e.meta_key(),
shift_key: e.shift_key(),
}
}
}
impl From<&PointerEvent> for PointerData {
fn from(e: &PointerEvent) -> Self {
Self {
alt_key: e.alt_key(),
button: e.button(),
buttons: e.buttons(),
client_x: e.client_x(),
client_y: e.client_y(),
ctrl_key: e.ctrl_key(),
meta_key: e.meta_key(),
page_x: e.page_x(),
page_y: e.page_y(),
screen_x: e.screen_x(),
screen_y: e.screen_y(),
shift_key: e.shift_key(),
pointer_id: e.pointer_id(),
width: e.width(),
height: e.height(),
pressure: e.pressure(),
tangential_pressure: e.tangential_pressure(),
tilt_x: e.tilt_x(),
tilt_y: e.tilt_y(),
twist: e.twist(),
pointer_type: e.pointer_type(),
is_primary: e.is_primary(),
// get_modifier_state: evt.get_modifier_state(),
}
}
}
impl From<&WheelEvent> for WheelData {
fn from(e: &WheelEvent) -> Self {
Self {
delta_x: e.delta_x(),
delta_y: e.delta_y(),
delta_z: e.delta_z(),
delta_mode: e.delta_mode(),
}
}
}
impl From<&AnimationEvent> for AnimationData {
fn from(e: &AnimationEvent) -> Self {
Self {
elapsed_time: e.elapsed_time(),
animation_name: e.animation_name(),
pseudo_element: e.pseudo_element(),
}
}
}
impl From<&TransitionEvent> for TransitionData {
fn from(e: &TransitionEvent) -> Self {
Self {
elapsed_time: e.elapsed_time(),
property_name: e.property_name(),
pseudo_element: e.pseudo_element(),
}
}
}
+1
View File
@@ -0,0 +1 @@
mod events;
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "dioxus-interpreter-js"
version = "0.2.0"
version = "0.2.1"
edition = "2018"
authors = ["Jonathan Kelley"]
description = "JS Intepreter for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
+4
View File
@@ -409,6 +409,8 @@ export function serialize_event(event) {
clientY,
ctrlKey,
metaKey,
offsetX,
offsetY,
pageX,
pageY,
screenX,
@@ -423,6 +425,8 @@ export function serialize_event(event) {
client_y: clientY,
ctrl_key: ctrlKey,
meta_key: metaKey,
offset_x: offsetX,
offset_y: offsetY,
page_x: pageX,
page_y: pageY,
screen_x: screenX,
+6 -3
View File
@@ -6,6 +6,9 @@ repository = "https://github.com/DioxusLabs/dioxus/"
homepage = "https://dioxuslabs.com"
documentation = "https://dioxuslabs.com"
keywords = ["dom", "ui", "gui", "react", "wasm"]
description = "Build server-side apps with Dioxus"
license = "MIT/Apache-2.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -23,8 +26,8 @@ serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0.79"
tokio-util = { version = "0.7.0", features = ["full"] }
dioxus-html = { path = "../html", features = ["serialize"] }
dioxus-core = { path = "../core", features = ["serialize"] }
dioxus-html = { path = "../html", features = ["serialize"], version = "^0.2.1" }
dioxus-core = { path = "../core", features = ["serialize"], version = "^0.2.1" }
# warp
@@ -42,4 +45,4 @@ axum = { version = "0.5.1", features = ["ws"] }
tower = "0.4.12"
[features]
default = []
default = []
+16 -4
View File
@@ -9,21 +9,33 @@ use tokio::sync::mpsc;
use tokio_stream::wrappers::UnboundedReceiverStream;
use tokio_util::task::LocalPoolHandle;
#[cfg(feature = "axum")]
impl crate::Liveview {
pub async fn upgrade(&self, ws: WebSocket, app: fn(Scope) -> Element) {
connect(ws, self.pool.clone(), app).await;
connect(ws, self.pool.clone(), app, ()).await;
}
pub async fn upgrade_with_props<T>(&self, ws: WebSocket, app: fn(Scope<T>) -> Element, props: T)
where
T: Send + Sync + 'static,
{
connect(ws, self.pool.clone(), app, props).await;
}
}
pub async fn connect(socket: WebSocket, pool: LocalPoolHandle, app: fn(Scope) -> Element) {
pub async fn connect<T>(
socket: WebSocket,
pool: LocalPoolHandle,
app: fn(Scope<T>) -> Element,
props: T,
) where
T: Send + Sync + 'static,
{
let (mut user_ws_tx, mut user_ws_rx) = socket.split();
let (event_tx, event_rx) = mpsc::unbounded_channel();
let (edits_tx, edits_rx) = mpsc::unbounded_channel();
let mut edits_rx = UnboundedReceiverStream::new(edits_rx);
let mut event_rx = UnboundedReceiverStream::new(event_rx);
let vdom_fut = pool.clone().spawn_pinned(move || async move {
let mut vdom = VirtualDom::new(app);
let mut vdom = VirtualDom::new_with_props(app, props);
let edits = vdom.rebuild();
let serialized = serde_json::to_string(&edits.edits).unwrap();
edits_tx.send(serialized).unwrap();
+20 -4
View File
@@ -6,14 +6,30 @@ use tokio_stream::wrappers::UnboundedReceiverStream;
use tokio_util::task::LocalPoolHandle;
use warp::ws::{Message, WebSocket};
#[cfg(feature = "warp")]
impl crate::Liveview {
pub async fn upgrade(&self, ws: warp::ws::WebSocket, app: fn(Scope) -> Element) {
connect(ws, self.pool.clone(), app).await;
connect(ws, self.pool.clone(), app, ()).await;
}
pub async fn upgrade_with_props<T>(
&self,
ws: warp::ws::WebSocket,
app: fn(Scope<T>) -> Element,
props: T,
) where
T: Send + Sync + 'static,
{
connect(ws, self.pool.clone(), app, props).await;
}
}
pub async fn connect(ws: WebSocket, pool: LocalPoolHandle, app: fn(Scope) -> Element) {
pub async fn connect<T>(
ws: WebSocket,
pool: LocalPoolHandle,
app: fn(Scope<T>) -> Element,
props: T,
) where
T: Send + Sync + 'static,
{
// Use a counter to assign a new unique ID for this user.
// Split the socket into a sender and receive of messages.
@@ -26,7 +42,7 @@ pub async fn connect(ws: WebSocket, pool: LocalPoolHandle, app: fn(Scope) -> Ele
let mut event_rx = UnboundedReceiverStream::new(event_rx);
let vdom_fut = pool.spawn_pinned(move || async move {
let mut vdom = VirtualDom::new(app);
let mut vdom = VirtualDom::new_with_props(app, props);
let edits = vdom.rebuild();
+4
View File
@@ -418,6 +418,8 @@ function serialize_event(event) {
clientY,
ctrlKey,
metaKey,
offsetX,
offsetY,
pageX,
pageY,
screenX,
@@ -432,6 +434,8 @@ function serialize_event(event) {
client_y: clientY,
ctrl_key: ctrlKey,
meta_key: metaKey,
offset_x: offsetX,
offset_y: offsetY,
page_x: pageX,
page_y: pageY,
screen_x: screenX,
+1 -1
View File
@@ -12,7 +12,7 @@ license = "MIT/Apache-2.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dioxus-desktop = { path = "../desktop", version = "^0.2.0" }
dioxus-desktop = { path = "../desktop", version = "^0.2.3" }
[lib]
doctest = false
+29
View File
@@ -0,0 +1,29 @@
[package]
name = "dioxus-native-core-macro"
version = "0.2.0"
edition = "2021"
description = "Build natively rendered apps with Dioxus"
license = "MIT/Apache-2.0"
repository = "https://github.com/DioxusLabs/dioxus/"
homepage = "https://dioxuslabs.com"
documentation = "https://dioxuslabs.com"
keywords = ["dom", "ui", "gui", "react", "wasm"]
[lib]
proc-macro = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
syn = { version = "1.0.11", features = ["extra-traits"] }
quote = "1.0"
dioxus-native-core = { path = "../native-core", version = "^0.2.0" }
[dev-dependencies]
dioxus-core = { path = "../core", version = "^0.2.1" }
dioxus-html = { path = "../html", version = "^0.2.1" }
dioxus-core-macro = { path = "../core-macro", version = "^0.2.1" }
smallvec = "1.6"
fxhash = "0.2"
anymap = "0.12.1"
+703
View File
@@ -0,0 +1,703 @@
extern crate proc_macro;
mod sorted_slice;
use dioxus_native_core::state::MemberId;
use proc_macro::TokenStream;
use quote::format_ident;
use quote::{quote, ToTokens, __private::Span};
use sorted_slice::StrSlice;
use syn::{
self,
parse::{Parse, ParseStream, Result},
parse_macro_input, parse_quote, Error, Field, Ident, Token, Type,
};
#[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()
}
#[derive(PartialEq, Debug, Clone)]
enum DepKind {
Node,
Child,
Parent,
}
#[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 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"),
}
.iter()
.collect(),
_ => unimplemented!(),
};
let strct = Struct::new(type_name.clone(), &fields);
match StateStruct::parse(&fields, &strct) {
Ok(state_strct) => {
let node_dep_state_fields = state_strct
.state_members
.iter()
.filter(|f| f.dep_kind == DepKind::Node)
.map(|f| f.reduce_self());
let child_dep_state_fields = state_strct
.state_members
.iter()
.filter(|f| f.dep_kind == DepKind::Child)
.map(|f| f.reduce_self());
let parent_dep_state_fields = state_strct
.state_members
.iter()
.filter(|f| f.dep_kind == DepKind::Parent)
.map(|f| f.reduce_self());
let node_iter = state_strct
.state_members
.iter()
.filter(|m| m.dep_kind == DepKind::Node);
let node_ids = node_iter.clone().map(|m| m.member_id.0);
let node_ids_clone = node_ids.clone();
let node_types = node_iter.map(|f| &f.mem.ty);
let child_iter = state_strct
.state_members
.iter()
.filter(|m| m.dep_kind == DepKind::Child);
let child_ids = child_iter.clone().map(|m| m.member_id.0);
let child_ids_clone = child_ids.clone();
let child_types = child_iter.map(|f| &f.mem.ty);
let parent_iter = state_strct
.state_members
.iter()
.filter(|m| m.dep_kind == DepKind::Parent);
let parent_ids = parent_iter.clone().map(|m| m.member_id.0);
let parent_ids_clone = parent_ids.clone();
let parent_types = parent_iter.map(|f| &f.mem.ty);
let type_name_str = type_name.to_string();
let child_states = &state_strct.child_states;
let member_size = state_strct.state_members.len();
let child_state_ty = child_states.iter().map(|m| &m.ty);
let child_state_idents: Vec<_> = child_states.iter().map(|m| &m.ident).collect();
let sum_const_declarations = child_state_ty.clone().enumerate().map(|(i, ty)| {
let ident = format_ident!("__{}_SUM_{}", i, type_name.to_string());
let ident_minus = format_ident!("__{}_SUM_{}_minus", i, type_name.to_string());
if i == 0 {
quote!(const #ident_minus: usize = #member_size + #ty::SIZE - 1;
const #ident: usize = #member_size + #ty::SIZE;)
} else {
let prev_ident = format_ident!("__{}_SUM_{}", i - 1, type_name.to_string());
quote!(const #ident_minus: usize = #prev_ident + #ty::SIZE - 1;
const #ident: usize = #prev_ident + #ty::SIZE;)
}
});
let sum_idents: Vec<_> = std::iter::once(quote!(#member_size))
.chain((0..child_states.len()).map(|i| {
let ident = format_ident!("__{}_SUM_{}", i, type_name.to_string());
quote!(#ident)
}))
.collect();
let child_state_ranges: Vec<_> = (0..child_state_ty.len())
.map(|i| {
let current = format_ident!("__{}_SUM_{}_minus", i, type_name.to_string());
let previous = if i == 0 {
quote!(#member_size)
} else {
let ident = format_ident!("__{}_SUM_{}", i - 1, type_name.to_string());
quote!(#ident)
};
quote!(#previous..=#current)
})
.collect();
let gen = quote! {
#(
#sum_const_declarations
)*
impl State for #type_name{
const SIZE: usize = #member_size #( + #child_state_ty::SIZE)*;
fn update_node_dep_state<'a>(
&'a mut self,
ty: dioxus_native_core::state::MemberId,
node: &'a dioxus_core::VNode<'a>,
vdom: &'a dioxus_core::VirtualDom,
ctx: &anymap::AnyMap,
) -> Option<dioxus_native_core::state::NodeStatesChanged>{
use dioxus_native_core::state::NodeDepState as _;
use dioxus_native_core::state::State as _;
match ty.0{
#(
#node_ids => #node_dep_state_fields,
)*
#(
#child_state_ranges => {
self.#child_state_idents.update_node_dep_state(
ty - #sum_idents,
node,
vdom,
ctx,
).map(|mut changed|{
for id in &mut changed.node_dep{
*id += #sum_idents;
}
changed
})
}
)*
_ => panic!("{:?} not in {}", ty, #type_name_str),
}
}
fn update_parent_dep_state<'a>(
&'a mut self,
ty: dioxus_native_core::state::MemberId,
node: &'a dioxus_core::VNode<'a>,
vdom: &'a dioxus_core::VirtualDom,
parent: Option<&Self>,
ctx: &anymap::AnyMap,
) -> Option<dioxus_native_core::state::ParentStatesChanged>{
use dioxus_native_core::state::ParentDepState as _;
match ty.0{
#(
#parent_ids => #parent_dep_state_fields,
)*
#(
#child_state_ranges => {
self.#child_state_idents.update_parent_dep_state(
ty - #sum_idents,
node,
vdom,
parent.map(|p| &p.#child_state_idents),
ctx,
).map(|mut changed|{
for id in &mut changed.node_dep{
*id += #sum_idents;
}
for id in &mut changed.parent_dep{
*id += #sum_idents;
}
changed
})
}
)*
_ => panic!("{:?} not in {}", ty, #type_name_str),
}
}
fn update_child_dep_state<'a>(
&'a mut self,
ty: dioxus_native_core::state::MemberId,
node: &'a dioxus_core::VNode<'a>,
vdom: &'a dioxus_core::VirtualDom,
children: &Vec<&Self>,
ctx: &anymap::AnyMap,
) -> Option<dioxus_native_core::state::ChildStatesChanged>{
use dioxus_native_core::state::ChildDepState as _;
match ty.0{
#(
#child_ids => #child_dep_state_fields,
)*
#(
#child_state_ranges => {
self.#child_state_idents.update_child_dep_state(
ty - #sum_idents,
node,
vdom,
&children.iter().map(|p| &p.#child_state_idents).collect(),
ctx,
).map(|mut changed|{
for id in &mut changed.node_dep{
*id += #sum_idents;
}
for id in &mut changed.child_dep{
*id += #sum_idents;
}
changed
})
}
)*
_ => panic!("{:?} not in {}", ty, #type_name_str),
}
}
fn child_dep_types(&self, mask: &dioxus_native_core::node_ref::NodeMask) -> Vec<dioxus_native_core::state::MemberId>{
let mut dep_types = Vec::new();
#(if #child_types::NODE_MASK.overlaps(mask) {
dep_types.push(dioxus_native_core::state::MemberId(#child_ids_clone));
})*
#(
dep_types.extend(self.#child_state_idents.child_dep_types(mask).into_iter().map(|id| id + #sum_idents));
)*
dep_types
}
fn parent_dep_types(&self, mask: &dioxus_native_core::node_ref::NodeMask) -> Vec<dioxus_native_core::state::MemberId>{
let mut dep_types = Vec::new();
#(if #parent_types::NODE_MASK.overlaps(mask) {
dep_types.push(dioxus_native_core::state::MemberId(#parent_ids_clone));
})*
#(
dep_types.extend(self.#child_state_idents.parent_dep_types(mask).into_iter().map(|id| id + #sum_idents));
)*
dep_types
}
fn node_dep_types(&self, mask: &dioxus_native_core::node_ref::NodeMask) -> Vec<dioxus_native_core::state::MemberId>{
let mut dep_types = Vec::new();
#(if #node_types::NODE_MASK.overlaps(mask) {
dep_types.push(dioxus_native_core::state::MemberId(#node_ids_clone));
})*
#(
dep_types.extend(self.#child_state_idents.node_dep_types(mask).into_iter().map(|id| id + #sum_idents));
)*
dep_types
}
}
};
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().filter_map(|f| Member::parse(f)).collect();
Self { name, members }
}
}
struct StateStruct<'a> {
state_members: Vec<StateMember<'a>>,
child_states: Vec<&'a Member>,
}
impl<'a> StateStruct<'a> {
fn parse(fields: &[&'a Field], strct: &'a Struct) -> Result<Self> {
let mut parse_err = Ok(());
let state_members = 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
}
});
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);
#[derive(Debug, Clone)]
struct DepNode<'a> {
state_mem: StateMember<'a>,
depandants: Vec<DepNode<'a>>,
}
impl<'a> DepNode<'a> {
fn new(state_mem: StateMember<'a>) -> Self {
Self {
state_mem,
depandants: Vec::new(),
}
}
/// flattens the node in pre order
fn flatten(self) -> Vec<StateMember<'a>> {
let DepNode {
state_mem,
depandants,
} = self;
let mut flat = vec![state_mem];
for d in depandants {
flat.append(&mut d.flatten());
}
flat
}
fn set_ids(&mut self, current_id: &mut usize) {
self.state_mem.member_id = dioxus_native_core::state::MemberId(*current_id);
// if the node depends on itself, we need to add the dependency seperately
if let Some(dep) = self.state_mem.dep_mem {
if dep == self.state_mem.mem {
self.state_mem
.dependants
.push((MemberId(*current_id), self.state_mem.dep_kind.clone()));
}
}
*current_id += 1;
for d in &mut self.depandants {
self.state_mem
.dependants
.push((MemberId(*current_id), d.state_mem.dep_kind.clone()));
d.set_ids(current_id);
}
}
fn contains_member(&self, member: &Member) -> bool {
if self.state_mem.mem == member {
true
} else {
self.depandants.iter().any(|d| d.contains_member(member))
}
}
// check if there are any mixed child/parent dependancies
fn check(&self) -> Option<Error> {
self.kind().err()
}
fn kind(&self) -> Result<&DepKind> {
fn reduce_kind<'a>(dk1: &'a DepKind, dk2: &'a DepKind) -> Result<&'a DepKind> {
match (dk1, dk2) {
(DepKind::Child, DepKind::Parent) | (DepKind::Parent, DepKind::Child) => {
Err(Error::new(
Span::call_site(),
"There is a ChildDepState that depends on a ParentDepState",
))
}
// node dep state takes the lowest priority
(DepKind::Node, important) | (important, DepKind::Node) => Ok(important),
// they are the same
(fst, _) => Ok(fst),
}
}
reduce_kind(
self.depandants
.iter()
.try_fold(&DepKind::Node, |dk1, dk2| reduce_kind(dk1, dk2.kind()?))?,
&self.state_mem.dep_kind,
)
}
fn insert_dependant(&mut self, other: DepNode<'a>) -> bool {
let dep = other.state_mem.dep_mem.unwrap();
if self.contains_member(dep) {
if self.state_mem.mem == dep {
self.depandants.push(other);
true
} else {
self.depandants
.iter_mut()
.find(|d| d.contains_member(dep))
.unwrap()
.insert_dependant(other)
}
} else {
false
}
}
}
// members need to be sorted so that members are updated after the members they depend on
let mut roots: Vec<DepNode> = vec![];
for m in state_members {
if let Some(dep) = m.dep_mem {
let root_depends_on = roots
.iter()
.filter_map(|m| m.state_mem.dep_mem)
.any(|d| m.mem == d);
if let Some(r) = roots.iter_mut().find(|r| r.contains_member(dep)) {
let new = DepNode::new(m);
if root_depends_on {
return Err(Error::new(
new.state_mem.mem.ident.span(),
format!("{} has a circular dependancy", new.state_mem.mem.ident),
));
}
// return Err(Error::new(new.state_mem.mem.ident.span(), "stuff"));
r.insert_dependant(new);
continue;
}
}
let mut new = DepNode::new(m);
let mut i = 0;
while i < roots.len() {
if roots[i].state_mem.dep_mem == Some(new.state_mem.mem) {
let child = roots.remove(i);
new.insert_dependant(child);
} else {
i += 1;
}
}
roots.push(new);
}
parse_err?;
let mut current_id = 0;
for r in &mut roots {
r.set_ids(&mut current_id);
}
if let Some(err) = roots.iter().find_map(DepNode::check) {
Err(err)
} else {
let state_members: Vec<_> = roots
.into_iter()
.flat_map(|r| r.flatten().into_iter())
.collect();
Ok(Self {
state_members,
child_states: child_states.collect(),
})
}
}
}
struct Dependancy {
ctx_ty: Option<Type>,
dep: Option<Ident>,
}
impl Parse for Dependancy {
fn parse(input: ParseStream) -> Result<Self> {
let dep = input
.parse()
.ok()
.filter(|i: &Ident| format!("{}", i) != "NONE");
let comma: Option<Token![,]> = input.parse().ok();
let ctx_ty = input.parse().ok();
Ok(Self {
ctx_ty: comma.and(ctx_ty),
dep,
})
}
}
#[derive(PartialEq, Debug)]
struct Member {
ty: Type,
ident: Ident,
}
impl Member {
fn parse(field: &Field) -> Option<Self> {
Some(Self {
ty: field.ty.clone(),
ident: field.ident.as_ref()?.clone(),
})
}
}
#[derive(Debug, Clone)]
struct StateMember<'a> {
mem: &'a Member,
dep_kind: DepKind,
dep_mem: Option<&'a Member>,
ctx_ty: Option<Type>,
dependants: Vec<(dioxus_native_core::state::MemberId, DepKind)>,
// This is just the index of the final order of the struct it is used to communicate which parts need updated and what order to update them in.
member_id: dioxus_native_core::state::MemberId,
}
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(DepKind::Node),
"child_dep_state" => Some(DepKind::Child),
"parent_dep_state" => Some(DepKind::Parent),
_ => None,
})?;
match a.parse_args::<Dependancy>() {
Ok(dependancy) => {
let dep_mem = if let Some(name) = &dependancy.dep {
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
}
} else {
None
};
Some(Self {
mem,
dep_kind,
dep_mem,
ctx_ty: dependancy.ctx_ty,
dependants: Vec::new(),
member_id: dioxus_native_core::state::MemberId(0),
})
}
Err(e) => {
err = Err(e);
None
}
}
});
err?;
Ok(member)
}
fn reduce_self(&self) -> 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! {&()}
} else {
let msg = ctx_ty.to_token_stream().to_string() + " not found in context";
quote! {ctx.get().expect(#msg)}
}
} else {
quote! {&()}
};
let states_changed = {
let child_dep = self
.dependants
.iter()
.filter(|(_, kind)| kind == &DepKind::Child)
.map(|(id, _)| id.0);
let parent_dep = self
.dependants
.iter()
.filter(|(_, kind)| kind == &DepKind::Parent)
.map(|(id, _)| id.0);
let node_dep = self
.dependants
.iter()
.filter(|(_, kind)| kind == &DepKind::Node)
.map(|(id, _)| id.0);
match self.dep_kind {
DepKind::Node => {
quote! {
dioxus_native_core::state::NodeStatesChanged{
node_dep: vec![#(dioxus_native_core::state::MemberId(#node_dep), )*],
}
}
}
DepKind::Child => {
quote! {
dioxus_native_core::state::ChildStatesChanged{
node_dep: vec![#(dioxus_native_core::state::MemberId(#node_dep), )*],
child_dep: vec![#(dioxus_native_core::state::MemberId(#child_dep), )*],
}
}
}
DepKind::Parent => {
quote! {
dioxus_native_core::state::ParentStatesChanged{
node_dep: vec![#(dioxus_native_core::state::MemberId(#node_dep), )*],
parent_dep: vec![#(dioxus_native_core::state::MemberId(#parent_dep), )*],
}
}
}
}
};
let ty = &self.mem.ty;
let node_view =
quote!(dioxus_native_core::node_ref::NodeView::new(node, #ty::NODE_MASK, vdom));
if let Some(dep_ident) = &self.dep_mem.map(|m| &m.ident) {
match self.dep_kind {
DepKind::Node => {
quote!({
if self.#ident.reduce(#node_view, &self.#dep_ident, #get_ctx){
Some(#states_changed)
} else{
None
}
})
}
DepKind::Child => {
quote!({
if self.#ident.reduce(#node_view, children.iter().map(|s| &s.#dep_ident), #get_ctx){
Some(#states_changed)
} else{
None
}
})
}
DepKind::Parent => {
quote!({
if self.#ident.reduce(#node_view, parent.as_ref().map(|p| &p.#dep_ident), #get_ctx){
Some(#states_changed)
} else{
None
}
})
}
}
} else {
match self.dep_kind {
DepKind::Node => {
quote!({
if self.#ident.reduce(#node_view, &(), #get_ctx){
Some(#states_changed)
} else{
None
}
})
}
DepKind::Child => {
quote!({
if self.#ident.reduce(#node_view, std::iter::empty(), #get_ctx){
Some(#states_changed)
} else{
None
}
})
}
DepKind::Parent => {
quote!({
if self.#ident.reduce(#node_view, Some(&()), #get_ctx){
Some(#states_changed)
} else{
None
}
})
}
}
}
}
}
@@ -0,0 +1,28 @@
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 })
}
}
@@ -0,0 +1,158 @@
use dioxus_core::VNode;
use dioxus_core::*;
use dioxus_core_macro::*;
use dioxus_html as dioxus_elements;
use dioxus_native_core::real_dom::RealDom;
use dioxus_native_core::state::State;
use dioxus_native_core_macro::State;
use std::cell::Cell;
#[derive(State, Default, Clone)]
struct Empty {}
#[test]
fn remove_node() {
#[allow(non_snake_case)]
fn Base(cx: Scope) -> Element {
rsx!(cx, div {})
}
let vdom = VirtualDom::new(Base);
let mutations = vdom.create_vnodes(rsx! {
div{
div{}
}
});
let mut dom: RealDom<Empty> = RealDom::new();
let _to_update = dom.apply_mutations(vec![mutations]);
let child_div = VElement {
id: Cell::new(Some(ElementId(2))),
key: None,
tag: "div",
namespace: None,
parent: Cell::new(Some(ElementId(1))),
listeners: &[],
attributes: &[],
children: &[],
};
let child_div_el = VNode::Element(&child_div);
let root_div = VElement {
id: Cell::new(Some(ElementId(1))),
key: None,
tag: "div",
namespace: None,
parent: Cell::new(Some(ElementId(0))),
listeners: &[],
attributes: &[],
children: &[child_div_el],
};
assert_eq!(dom.size(), 2);
assert!(&dom.contains_node(&VNode::Element(&root_div)));
assert_eq!(dom[1].height, 1);
assert_eq!(dom[2].height, 2);
let vdom = VirtualDom::new(Base);
let mutations = vdom.diff_lazynodes(
rsx! {
div{
div{}
}
},
rsx! {
div{}
},
);
dom.apply_mutations(vec![mutations.1]);
let new_root_div = VElement {
id: Cell::new(Some(ElementId(1))),
key: None,
tag: "div",
namespace: None,
parent: Cell::new(Some(ElementId(0))),
listeners: &[],
attributes: &[],
children: &[],
};
assert_eq!(dom.size(), 1);
assert!(&dom.contains_node(&VNode::Element(&new_root_div)));
assert_eq!(dom[1].height, 1);
}
#[test]
fn add_node() {
#[allow(non_snake_case)]
fn Base(cx: Scope) -> Element {
rsx!(cx, div {})
}
let vdom = VirtualDom::new(Base);
let mutations = vdom.create_vnodes(rsx! {
div{}
});
let mut dom: RealDom<Empty> = RealDom::new();
let _to_update = dom.apply_mutations(vec![mutations]);
let root_div = VElement {
id: Cell::new(Some(ElementId(1))),
key: None,
tag: "div",
namespace: None,
parent: Cell::new(Some(ElementId(0))),
listeners: &[],
attributes: &[],
children: &[],
};
assert_eq!(dom.size(), 1);
assert!(&dom.contains_node(&VNode::Element(&root_div)));
assert_eq!(dom[1].height, 1);
let vdom = VirtualDom::new(Base);
let mutations = vdom.diff_lazynodes(
rsx! {
div{}
},
rsx! {
div{
p{}
}
},
);
dom.apply_mutations(vec![mutations.1]);
let child_div = VElement {
id: Cell::new(Some(ElementId(2))),
key: None,
tag: "p",
namespace: None,
parent: Cell::new(Some(ElementId(1))),
listeners: &[],
attributes: &[],
children: &[],
};
let child_div_el = VNode::Element(&child_div);
let new_root_div = VElement {
id: Cell::new(Some(ElementId(1))),
key: None,
tag: "div",
namespace: None,
parent: Cell::new(Some(ElementId(0))),
listeners: &[],
attributes: &[],
children: &[child_div_el],
};
assert_eq!(dom.size(), 2);
assert!(&dom.contains_node(&VNode::Element(&new_root_div)));
assert_eq!(dom[1].height, 1);
assert_eq!(dom[2].height, 2);
}
@@ -0,0 +1,129 @@
use std::cell::Cell;
use dioxus_core::VNode;
use dioxus_core::*;
use dioxus_core_macro::*;
use dioxus_html as dioxus_elements;
use dioxus_native_core::real_dom::RealDom;
use dioxus_native_core::state::State;
use dioxus_native_core_macro::State;
#[derive(Default, Clone, State)]
struct Empty {}
#[test]
fn initial_build_simple() {
use std::cell::Cell;
#[allow(non_snake_case)]
fn Base(cx: Scope) -> Element {
rsx!(cx, div {})
}
let vdom = VirtualDom::new(Base);
let mutations = vdom.create_vnodes(rsx! {
div{}
});
let mut dom: RealDom<Empty> = RealDom::new();
let _to_update = dom.apply_mutations(vec![mutations]);
let root_div = VElement {
id: Cell::new(Some(ElementId(1))),
key: None,
tag: "div",
namespace: None,
parent: Cell::new(Some(ElementId(0))),
listeners: &[],
attributes: &[],
children: &[],
};
assert_eq!(dom.size(), 1);
assert!(&dom.contains_node(&VNode::Element(&root_div)));
assert_eq!(dom[1].height, 1);
}
#[test]
fn initial_build_with_children() {
#[allow(non_snake_case)]
fn Base(cx: Scope) -> Element {
rsx!(cx, div {})
}
let vdom = VirtualDom::new(Base);
let mutations = vdom.create_vnodes(rsx! {
div{
div{
"hello"
p{
"world"
}
"hello world"
}
}
});
let mut dom: RealDom<Empty> = RealDom::new();
let _to_update = dom.apply_mutations(vec![mutations]);
let first_text = VText {
id: Cell::new(Some(ElementId(3))),
text: "hello",
is_static: true,
};
let first_text_node = VNode::Text(&first_text);
let child_text = VText {
id: Cell::new(Some(ElementId(5))),
text: "world",
is_static: true,
};
let child_text_node = VNode::Text(&child_text);
let child_p_el = VElement {
id: Cell::new(Some(ElementId(4))),
key: None,
tag: "p",
namespace: None,
parent: Cell::new(Some(ElementId(2))),
listeners: &[],
attributes: &[],
children: &[child_text_node],
};
let child_p_node = VNode::Element(&child_p_el);
let second_text = VText {
id: Cell::new(Some(ElementId(6))),
text: "hello world",
is_static: true,
};
let second_text_node = VNode::Text(&second_text);
let child_div_el = VElement {
id: Cell::new(Some(ElementId(2))),
key: None,
tag: "div",
namespace: None,
parent: Cell::new(Some(ElementId(1))),
listeners: &[],
attributes: &[],
children: &[first_text_node, child_p_node, second_text_node],
};
let child_div_node = VNode::Element(&child_div_el);
let root_div = VElement {
id: Cell::new(Some(ElementId(1))),
key: None,
tag: "div",
namespace: None,
parent: Cell::new(Some(ElementId(0))),
listeners: &[],
attributes: &[],
children: &[child_div_node],
};
assert_eq!(dom.size(), 6);
assert!(&dom.contains_node(&VNode::Element(&root_div)));
assert_eq!(dom[1].height, 1);
assert_eq!(dom[2].height, 2);
assert_eq!(dom[3].height, 3);
assert_eq!(dom[4].height, 3);
assert_eq!(dom[5].height, 4);
assert_eq!(dom[6].height, 3);
}
@@ -0,0 +1,534 @@
use anymap::AnyMap;
use dioxus_core::AttributeValue;
use dioxus_core::VNode;
use dioxus_core::*;
use dioxus_core_macro::*;
use dioxus_html as dioxus_elements;
use dioxus_native_core::node_ref::*;
use dioxus_native_core::real_dom::*;
use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State};
use dioxus_native_core_macro::State;
#[derive(Debug, Clone, Default, State)]
struct CallCounterStatePart1 {
#[child_dep_state(child_counter)]
child_counter: ChildDepCallCounter,
}
#[derive(Debug, Clone, Default, State)]
struct CallCounterStatePart2 {
#[parent_dep_state(parent_counter)]
parent_counter: ParentDepCallCounter,
}
#[derive(Debug, Clone, Default, State)]
struct CallCounterStatePart3 {
#[node_dep_state()]
node_counter: NodeDepCallCounter,
}
#[derive(Debug, Clone, Default, State)]
struct CallCounterState {
#[child_dep_state(child_counter)]
child_counter: ChildDepCallCounter,
#[state]
part2: CallCounterStatePart2,
#[parent_dep_state(parent_counter)]
parent_counter: ParentDepCallCounter,
#[state]
part1: CallCounterStatePart1,
#[state]
part3: CallCounterStatePart3,
#[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::DepState>,
_: &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::DepState>,
_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: &Self::DepState, _ctx: &Self::Ctx) -> bool {
self.0 += 1;
true
}
}
#[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::DepState>,
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::DepState>, 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: &Self::DepState, ctx: &Self::Ctx) -> bool {
assert_eq!(*ctx, 42);
*self = NodeStateTester(
node.tag().map(|s| s.to_string()),
node.attributes()
.map(|a| (a.name.to_string(), a.value.to_string()))
.collect(),
);
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 {
rsx!(cx, div {
p{}
h1{}
})
}
let vdom = VirtualDom::new(Base);
let mutations = vdom.create_vnodes(rsx! {
div {
p{
color: "red"
}
h1{}
}
});
let mut dom: RealDom<StateTester> = RealDom::new();
let nodes_updated = dom.apply_mutations(vec![mutations]);
let mut ctx = AnyMap::new();
ctx.insert(42u32);
let _to_rerender = dom.update_state(&vdom, nodes_updated, ctx);
let root_div = &dom[1];
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(None, None)))
);
assert_eq!(root_div.state.node.0, Some("div".to_string()));
assert_eq!(root_div.state.node.1, vec![]);
let child_p = &dom[2];
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(None, 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 = &dom[3];
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(None, None)))
)))
);
assert_eq!(child_h1.state.node.0, Some("h1".to_string()));
assert_eq!(child_h1.state.node.1, vec![]);
}
#[test]
fn state_reduce_initally_called_minimally() {
#[allow(non_snake_case)]
fn Base(cx: Scope) -> Element {
rsx!(cx, div {
div{
div{
p{}
}
p{
"hello"
}
div{
h1{}
}
p{
"world"
}
}
})
}
let vdom = VirtualDom::new(Base);
let mutations = vdom.create_vnodes(rsx! {
div {
div{
div{
p{}
}
p{
"hello"
}
div{
h1{}
}
p{
"world"
}
}
}
});
let mut dom: RealDom<CallCounterState> = RealDom::new();
let nodes_updated = dom.apply_mutations(vec![mutations]);
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
dom.traverse_depth_first(|n| {
assert_eq!(n.state.part1.child_counter.0, 1);
assert_eq!(n.state.child_counter.0, 1);
assert_eq!(n.state.part2.parent_counter.0, 1);
assert_eq!(n.state.parent_counter.0, 1);
assert_eq!(n.state.part3.node_counter.0, 1);
assert_eq!(n.state.node_counter.0, 1);
});
}
#[test]
fn state_reduce_parent_called_minimally_on_update() {
#[allow(non_snake_case)]
fn Base(cx: Scope) -> Element {
rsx!(cx, div {
width: "100%",
div{
div{
p{}
}
p{
"hello"
}
div{
h1{}
}
p{
"world"
}
}
})
}
let vdom = VirtualDom::new(Base);
let mutations = vdom.create_vnodes(rsx! {
div {
width: "100%",
div{
div{
p{}
}
p{
"hello"
}
div{
h1{}
}
p{
"world"
}
}
}
});
let mut dom: RealDom<CallCounterState> = RealDom::new();
let nodes_updated = dom.apply_mutations(vec![mutations]);
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
let nodes_updated = dom.apply_mutations(vec![Mutations {
edits: vec![DomEdit::SetAttribute {
root: 1,
field: "width",
value: AttributeValue::Text("99%"),
ns: Some("style"),
}],
dirty_scopes: fxhash::FxHashSet::default(),
refs: Vec::new(),
}]);
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
dom.traverse_depth_first(|n| {
assert_eq!(n.state.part2.parent_counter.0, 2);
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 {
rsx!(cx, div {
div{
div{
p{
width: "100%",
}
}
p{
"hello"
}
div{
h1{}
}
p{
"world"
}
}
})
}
let vdom = VirtualDom::new(Base);
let mutations = vdom.create_vnodes(rsx! {
div {
div{
div{
p{
width: "100%",
}
}
p{
"hello"
}
div{
h1{}
}
p{
"world"
}
}
}
});
let mut dom: RealDom<CallCounterState> = RealDom::new();
let nodes_updated = dom.apply_mutations(vec![mutations]);
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
let nodes_updated = dom.apply_mutations(vec![Mutations {
edits: vec![DomEdit::SetAttribute {
root: 4,
field: "width",
value: AttributeValue::Text("99%"),
ns: Some("style"),
}],
dirty_scopes: fxhash::FxHashSet::default(),
refs: Vec::new(),
}]);
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
dom.traverse_depth_first(|n| {
println!("{:?}", n);
assert_eq!(
n.state.part1.child_counter.0,
if n.id.0 > 4 { 1 } else { 2 }
);
assert_eq!(n.state.child_counter.0, if n.id.0 > 4 { 1 } else { 2 });
});
}
#[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: &Self::DepState, _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: &Self::DepState, _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: &Self::DepState, _ctx: &Self::Ctx) -> bool {
self.0 += 1;
true
}
}
#[test]
fn dependancies_order_independant() {
#[allow(non_snake_case)]
fn Base(cx: Scope) -> Element {
rsx!(cx, div {
width: "100%",
p{
"hello"
}
})
}
let vdom = VirtualDom::new(Base);
let mutations = vdom.create_vnodes(rsx! {
div {
width: "100%",
p{
"hello"
}
}
});
let mut dom: RealDom<UnorderedDependanciesState> = RealDom::new();
let nodes_updated = dom.apply_mutations(vec![mutations]);
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::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);
});
}
#[derive(Clone, Default, State)]
struct DependanciesStateTest {
#[node_dep_state(c)]
b: BDepCallCounter,
#[node_dep_state()]
c: CDepCallCounter,
#[node_dep_state(b)]
a: ADepCallCounter,
#[state]
child: UnorderedDependanciesState,
}
+24
View File
@@ -0,0 +1,24 @@
[package]
name = "dioxus-native-core"
version = "0.2.0"
edition = "2021"
license = "MIT/Apache-2.0"
repository = "https://github.com/DioxusLabs/dioxus/"
homepage = "https://dioxuslabs.com"
description = "Build natively rendered apps with Dioxus"
documentation = "https://dioxuslabs.com"
keywords = ["dom", "ui", "gui", "react", "wasm"]
[dependencies]
dioxus-core = { path = "../core", version = "^0.2.1" }
dioxus-html = { path = "../html", version = "^0.2.1" }
dioxus-core-macro = { path = "../core-macro", version = "^0.2.1" }
stretch2 = "0.4.2"
smallvec = "1.6"
fxhash = "0.2"
anymap = "0.12.1"
[dev-dependencies]
rand = "0.8.5"
@@ -0,0 +1,636 @@
/*
- [ ] pub display: Display,
- [x] pub position_type: PositionType, --> kinda, stretch doesnt support everything
- [ ] pub direction: Direction,
- [x] pub flex_direction: FlexDirection,
- [x] pub flex_wrap: FlexWrap,
- [x] pub flex_grow: f32,
- [x] pub flex_shrink: f32,
- [x] pub flex_basis: Dimension,
- [x] pub overflow: Overflow, ---> kinda implemented... stretch doesnt have support for directional overflow
- [x] pub align_items: AlignItems,
- [x] pub align_self: AlignSelf,
- [x] pub align_content: AlignContent,
- [x] pub margin: Rect<Dimension>,
- [x] pub padding: Rect<Dimension>,
- [x] pub justify_content: JustifyContent,
- [ ] pub position: Rect<Dimension>,
- [x] pub border: Rect<Dimension>,
- [ ] pub size: Size<Dimension>, ----> ??? seems to only be relevant for input?
- [ ] pub min_size: Size<Dimension>,
- [ ] pub max_size: Size<Dimension>,
- [ ] pub aspect_ratio: Number,
*/
use stretch2::{prelude::*, style::PositionType};
/// applies the entire html namespace defined in dioxus-html
pub fn apply_layout_attributes(name: &str, value: &str, style: &mut Style) {
match name {
"align-content"
| "align-items"
| "align-self" => apply_align(name, value, style),
"animation"
| "animation-delay"
| "animation-direction"
| "animation-duration"
| "animation-fill-mode"
| "animation-iteration-count"
| "animation-name"
| "animation-play-state"
| "animation-timing-function" => apply_animation(name, value, style),
"backface-visibility" => {}
"border"
| "border-bottom"
| "border-bottom-color"
| "border-bottom-left-radius"
| "border-bottom-right-radius"
| "border-bottom-style"
| "border-bottom-width"
| "border-collapse"
| "border-color"
| "border-image"
| "border-image-outset"
| "border-image-repeat"
| "border-image-slice"
| "border-image-source"
| "border-image-width"
| "border-left"
| "border-left-color"
| "border-left-style"
| "border-left-width"
| "border-radius"
| "border-right"
| "border-right-color"
| "border-right-style"
| "border-right-width"
| "border-spacing"
| "border-style"
| "border-top"
| "border-top-color"
| "border-top-left-radius"
| "border-top-right-radius"
| "border-top-style"
| "border-top-width"
| "border-width" => apply_border(name, value, style),
"bottom" => {}
"box-shadow" => {}
"box-sizing" => {}
"caption-side" => {}
"clear" => {}
"clip" => {}
"column-count"
| "column-fill"
| "column-gap"
| "column-rule"
| "column-rule-color"
| "column-rule-style"
| "column-rule-width"
| "column-span"
// add column-width
| "column-width" => apply_column(name, value, style),
"columns" => {}
"content" => {}
"counter-increment" => {}
"counter-reset" => {}
"cursor" => {}
"direction" => {
match value {
"ltr" => style.direction = Direction::LTR,
"rtl" => style.direction = Direction::RTL,
_ => {}
}
}
"display" => apply_display(name, value, style),
"empty-cells" => {}
"flex"
| "flex-basis"
| "flex-direction"
| "flex-flow"
| "flex-grow"
| "flex-shrink"
| "flex-wrap" => apply_flex(name, value, style),
"float" => {}
"height" => {
if let Some(v) = parse_value(value){
style.size.height = match v {
UnitSystem::Percent(v)=> Dimension::Percent(v/100.0),
UnitSystem::Point(v)=> Dimension::Points(v),
};
}
}
"justify-content" => {
use JustifyContent::*;
style.justify_content = match value {
"flex-start" => FlexStart,
"flex-end" => FlexEnd,
"center" => Center,
"space-between" => SpaceBetween,
"space-around" => SpaceAround,
"space-evenly" => SpaceEvenly,
_ => FlexStart,
};
}
"left" => {}
"letter-spacing" => {}
"line-height" => {}
"list-style"
| "list-style-image"
| "list-style-position"
| "list-style-type" => {}
"margin"
| "margin-bottom"
| "margin-left"
| "margin-right"
| "margin-top" => apply_margin(name, value, style),
"max-height" => {}
"max-width" => {}
"min-height" => {}
"min-width" => {}
"opacity" => {}
"order" => {}
"outline" => {}
"outline-color"
| "outline-offset"
| "outline-style"
| "outline-width" => {}
"overflow"
| "overflow-x"
| "overflow-y" => apply_overflow(name, value, style),
"padding"
| "padding-bottom"
| "padding-left"
| "padding-right"
| "padding-top" => apply_padding(name, value, style),
"page-break-after"
| "page-break-before"
| "page-break-inside" => {}
"perspective"
| "perspective-origin" => {}
"position" => {
match value {
"static" => {}
"relative" => style.position_type = PositionType::Relative,
"fixed" => {}
"absolute" => style.position_type = PositionType::Absolute,
"sticky" => {}
_ => {}
}
}
"pointer-events" => {}
"quotes" => {}
"resize" => {}
"right" => {}
"tab-size" => {}
"table-layout" => {}
"top" => {}
"transform"
| "transform-origin"
| "transform-style" => apply_transform(name, value, style),
"transition"
| "transition-delay"
| "transition-duration"
| "transition-property"
| "transition-timing-function" => apply_transition(name, value, style),
"vertical-align" => {}
"visibility" => {}
"white-space" => {}
"width" => {
if let Some(v) = parse_value(value){
style.size.width = match v {
UnitSystem::Percent(v)=> Dimension::Percent(v/100.0),
UnitSystem::Point(v)=> Dimension::Points(v),
};
}
}
"word-break" => {}
"word-spacing" => {}
"word-wrap" => {}
"z-index" => {}
_ => {}
}
}
/// a relative or absolute size
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum UnitSystem {
Percent(f32),
Point(f32),
}
impl From<UnitSystem> for Dimension {
fn from(other: UnitSystem) -> Dimension {
match other {
UnitSystem::Percent(v) => Dimension::Percent(v),
UnitSystem::Point(v) => Dimension::Points(v),
}
}
}
/// parse relative or absolute value
pub fn parse_value(value: &str) -> Option<UnitSystem> {
if value.ends_with("px") {
if let Ok(px) = value.trim_end_matches("px").parse::<f32>() {
Some(UnitSystem::Point(px))
} else {
None
}
} else if value.ends_with('%') {
if let Ok(pct) = value.trim_end_matches('%').parse::<f32>() {
Some(UnitSystem::Percent(pct))
} else {
None
}
} else {
None
}
}
fn apply_overflow(name: &str, value: &str, style: &mut Style) {
match name {
// todo: add more overflow support to stretch2
"overflow" | "overflow-x" | "overflow-y" => {
style.overflow = match value {
"auto" => Overflow::Visible,
"hidden" => Overflow::Hidden,
"scroll" => Overflow::Scroll,
"visible" => Overflow::Visible,
_ => Overflow::Visible,
};
}
_ => {}
}
}
fn apply_display(_name: &str, value: &str, style: &mut Style) {
style.display = match value {
"flex" => Display::Flex,
"block" => Display::None,
_ => Display::Flex,
}
// TODO: there are way more variants
// stretch needs to be updated to handle them
//
// "block" => Display::Block,
// "inline" => Display::Inline,
// "inline-block" => Display::InlineBlock,
// "inline-table" => Display::InlineTable,
// "list-item" => Display::ListItem,
// "run-in" => Display::RunIn,
// "table" => Display::Table,
// "table-caption" => Display::TableCaption,
// "table-cell" => Display::TableCell,
// "table-column" => Display::TableColumn,
// "table-column-group" => Display::TableColumnGroup,
// "table-footer-group" => Display::TableFooterGroup,
// "table-header-group" => Display::TableHeaderGroup,
// "table-row" => Display::TableRow,
// "table-row-group" => Display::TableRowGroup,
// "none" => Display::None,
// _ => Display::Inline,
}
fn apply_border(name: &str, value: &str, style: &mut Style) {
match name {
"border" => {}
"border-bottom" => {}
"border-bottom-color" => {}
"border-bottom-left-radius" => {}
"border-bottom-right-radius" => {}
"border-bottom-style" => {
if style.border.bottom == Dimension::default() {
let v = Dimension::Points(1.0);
style.border.bottom = v;
}
}
"border-bottom-width" => {
if let Some(v) = parse_value(value) {
style.border.bottom = v.into();
}
}
"border-collapse" => {}
"border-color" => {}
"border-image" => {}
"border-image-outset" => {}
"border-image-repeat" => {}
"border-image-slice" => {}
"border-image-source" => {}
"border-image-width" => {}
"border-left" => {}
"border-left-color" => {}
"border-left-style" => {
if style.border.start == Dimension::default() {
let v = Dimension::Points(1.0);
style.border.start = v;
}
}
"border-left-width" => {
if let Some(v) = parse_value(value) {
style.border.start = v.into();
}
}
"border-radius" => {}
"border-right" => {}
"border-right-color" => {}
"border-right-style" => {
let v = Dimension::Points(1.0);
style.border.end = v;
}
"border-right-width" => {
if let Some(v) = parse_value(value) {
style.border.end = v.into();
}
}
"border-spacing" => {}
"border-style" => {
if style.border.top == Dimension::default() {
let v = Dimension::Points(1.0);
style.border.top = v;
}
if style.border.bottom == Dimension::default() {
let v = Dimension::Points(1.0);
style.border.bottom = v;
}
if style.border.start == Dimension::default() {
let v = Dimension::Points(1.0);
style.border.start = v;
}
if style.border.end == Dimension::default() {
let v = Dimension::Points(1.0);
style.border.end = v;
}
}
"border-top" => {}
"border-top-color" => {}
"border-top-left-radius" => {}
"border-top-right-radius" => {}
"border-top-style" => {
if style.border.top == Dimension::default() {
let v = Dimension::Points(1.0);
style.border.top = v;
}
}
"border-top-width" => {
if let Some(v) = parse_value(value) {
style.border.top = v.into();
}
}
"border-width" => {
let values: Vec<_> = value.split(' ').collect();
if values.len() == 1 {
if let Some(w) = parse_value(values[0]) {
style.border.top = w.into();
style.border.bottom = w.into();
style.border.start = w.into();
style.border.end = w.into();
}
} else {
let border_widths = [
&mut style.border.top,
&mut style.border.bottom,
&mut style.border.start,
&mut style.border.end,
];
for (v, width) in values.into_iter().zip(border_widths) {
if let Some(w) = parse_value(v) {
*width = w.into();
}
}
}
}
_ => (),
}
}
fn apply_animation(name: &str, _value: &str, _style: &mut Style) {
match name {
"animation" => {}
"animation-delay" => {}
"animation-direction =>{}" => {}
"animation-duration" => {}
"animation-fill-mode" => {}
"animation-itera =>{}tion-count" => {}
"animation-name" => {}
"animation-play-state" => {}
"animation-timing-function" => {}
_ => {}
}
}
fn apply_column(name: &str, _value: &str, _style: &mut Style) {
match name {
"column-count" => {}
"column-fill" => {}
"column-gap" => {}
"column-rule" => {}
"column-rule-color" => {}
"column-rule-style" => {}
"column-rule-width" => {}
"column-span" => {}
"column-width" => {}
_ => {}
}
}
fn apply_flex(name: &str, value: &str, style: &mut Style) {
// - [x] pub flex_direction: FlexDirection,
// - [x] pub flex_wrap: FlexWrap,
// - [x] pub flex_grow: f32,
// - [x] pub flex_shrink: f32,
// - [x] pub flex_basis: Dimension,
match name {
"flex" => {}
"flex-direction" => {
use FlexDirection::*;
style.flex_direction = match value {
"row" => Row,
"row-reverse" => RowReverse,
"column" => Column,
"column-reverse" => ColumnReverse,
_ => Row,
};
}
"flex-basis" => {
if let Some(v) = parse_value(value) {
style.flex_basis = match v {
UnitSystem::Percent(v) => Dimension::Percent(v / 100.0),
UnitSystem::Point(v) => Dimension::Points(v),
};
}
}
"flex-flow" => {}
"flex-grow" => {
if let Ok(val) = value.parse::<f32>() {
style.flex_grow = val;
}
}
"flex-shrink" => {
if let Ok(px) = value.parse::<f32>() {
style.flex_shrink = px;
}
}
"flex-wrap" => {
use FlexWrap::*;
style.flex_wrap = match value {
"nowrap" => NoWrap,
"wrap" => Wrap,
"wrap-reverse" => WrapReverse,
_ => NoWrap,
};
}
_ => {}
}
}
fn apply_padding(name: &str, value: &str, style: &mut Style) {
match parse_value(value) {
Some(UnitSystem::Percent(v)) => match name {
"padding" => {
let v = Dimension::Percent(v / 100.0);
style.padding.top = v;
style.padding.bottom = v;
style.padding.start = v;
style.padding.end = v;
}
"padding-bottom" => style.padding.bottom = Dimension::Percent(v / 100.0),
"padding-left" => style.padding.start = Dimension::Percent(v / 100.0),
"padding-right" => style.padding.end = Dimension::Percent(v / 100.0),
"padding-top" => style.padding.top = Dimension::Percent(v / 100.0),
_ => {}
},
Some(UnitSystem::Point(v)) => match name {
"padding" => {
style.padding.top = Dimension::Points(v);
style.padding.bottom = Dimension::Points(v);
style.padding.start = Dimension::Points(v);
style.padding.end = Dimension::Points(v);
}
"padding-bottom" => style.padding.bottom = Dimension::Points(v),
"padding-left" => style.padding.start = Dimension::Points(v),
"padding-right" => style.padding.end = Dimension::Points(v),
"padding-top" => style.padding.top = Dimension::Points(v),
_ => {}
},
None => {}
}
}
fn apply_transform(_name: &str, _value: &str, _style: &mut Style) {
todo!()
}
fn apply_transition(_name: &str, _value: &str, _style: &mut Style) {
todo!()
}
fn apply_align(name: &str, value: &str, style: &mut Style) {
match name {
"align-items" => {
use AlignItems::*;
style.align_items = match value {
"flex-start" => FlexStart,
"flex-end" => FlexEnd,
"center" => Center,
"baseline" => Baseline,
"stretch" => Stretch,
_ => FlexStart,
};
}
"align-content" => {
use AlignContent::*;
style.align_content = match value {
"flex-start" => FlexStart,
"flex-end" => FlexEnd,
"center" => Center,
"space-between" => SpaceBetween,
"space-around" => SpaceAround,
_ => FlexStart,
};
}
"align-self" => {
use AlignSelf::*;
style.align_self = match value {
"auto" => Auto,
"flex-start" => FlexStart,
"flex-end" => FlexEnd,
"center" => Center,
"baseline" => Baseline,
"stretch" => Stretch,
_ => Auto,
};
}
_ => {}
}
}
fn apply_margin(name: &str, value: &str, style: &mut Style) {
match parse_value(value) {
Some(UnitSystem::Percent(v)) => match name {
"margin" => {
let v = Dimension::Percent(v / 100.0);
style.margin.top = v;
style.margin.bottom = v;
style.margin.start = v;
style.margin.end = v;
}
"margin-top" => style.margin.top = Dimension::Percent(v / 100.0),
"margin-bottom" => style.margin.bottom = Dimension::Percent(v / 100.0),
"margin-left" => style.margin.start = Dimension::Percent(v / 100.0),
"margin-right" => style.margin.end = Dimension::Percent(v / 100.0),
_ => {}
},
Some(UnitSystem::Point(v)) => match name {
"margin" => {
style.margin.top = Dimension::Points(v);
style.margin.bottom = Dimension::Points(v);
style.margin.start = Dimension::Points(v);
style.margin.end = Dimension::Points(v);
}
"margin-top" => style.margin.top = Dimension::Points(v),
"margin-bottom" => style.margin.bottom = Dimension::Points(v),
"margin-left" => style.margin.start = Dimension::Points(v),
"margin-right" => style.margin.end = Dimension::Points(v),
_ => {}
},
None => {}
}
}
+4
View File
@@ -0,0 +1,4 @@
pub mod layout_attributes;
pub mod node_ref;
pub mod real_dom;
pub mod state;
+249
View File
@@ -0,0 +1,249 @@
use dioxus_core::*;
use crate::state::union_ordered_iter;
#[derive(Debug)]
pub struct NodeView<'a> {
inner: &'a VNode<'a>,
mask: NodeMask,
}
impl<'a> NodeView<'a> {
pub fn new(mut vnode: &'a VNode<'a>, view: NodeMask, vdom: &'a VirtualDom) -> Self {
if let VNode::Component(sc) = vnode {
let scope = vdom.get_scope(sc.scope.get().unwrap()).unwrap();
vnode = scope.root_node();
}
Self {
inner: vnode,
mask: view,
}
}
pub fn id(&self) -> ElementId {
self.inner.mounted_id()
}
pub fn tag(&self) -> Option<&'a str> {
self.mask.tag.then(|| self.el().map(|el| el.tag)).flatten()
}
pub fn namespace(&self) -> Option<&'a str> {
self.mask
.namespace
.then(|| self.el().and_then(|el| el.namespace))
.flatten()
}
pub fn attributes(&self) -> impl Iterator<Item = &Attribute<'a>> {
self.el()
.map(|el| el.attributes)
.unwrap_or_default()
.iter()
.filter(|a| self.mask.attritutes.contains_attribute(a.name))
}
pub fn text(&self) -> Option<&str> {
self.mask
.text
.then(|| self.txt().map(|txt| txt.text))
.flatten()
}
pub fn listeners(&self) -> &'a [Listener<'a>] {
self.el().map(|el| el.listeners).unwrap_or_default()
}
fn el(&self) -> Option<&'a VElement<'a>> {
if let VNode::Element(el) = &self.inner {
Some(el)
} else {
None
}
}
fn txt(&self) -> Option<&'a VText<'a>> {
if let VNode::Text(txt) = &self.inner {
Some(txt)
} else {
None
}
}
}
#[derive(PartialEq, Clone, Debug)]
pub enum AttributeMask {
All,
Dynamic(Vec<&'static str>),
Static(&'static [&'static str]),
}
impl AttributeMask {
pub const NONE: Self = Self::Static(&[]);
fn contains_attribute(&self, attr: &'static str) -> bool {
match self {
AttributeMask::All => true,
AttributeMask::Dynamic(l) => l.binary_search(&attr).is_ok(),
AttributeMask::Static(l) => l.binary_search(&attr).is_ok(),
}
}
pub fn single(new: &'static str) -> Self {
Self::Dynamic(vec![new])
}
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"
),
_ => (),
}
}
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().copied(), o.iter().copied(), s.len() + o.len()),
),
(AttributeMask::Static(s), AttributeMask::Dynamic(o)) => AttributeMask::Dynamic(
union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()),
),
(AttributeMask::Dynamic(s), AttributeMask::Static(o)) => AttributeMask::Dynamic(
union_ordered_iter(s.iter().copied(), 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
}
fn overlaps(&self, other: &Self) -> bool {
fn overlaps_iter(
self_iter: impl Iterator<Item = &'static str>,
mut other_iter: impl Iterator<Item = &'static 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().copied(), v2.iter().copied())
}
(AttributeMask::Dynamic(v), AttributeMask::Static(s)) => {
overlaps_iter(v.iter().copied(), s.iter().copied())
}
(AttributeMask::Static(s), AttributeMask::Dynamic(v)) => {
overlaps_iter(v.iter().copied(), s.iter().copied())
}
(AttributeMask::Static(s1), AttributeMask::Static(s2)) => {
overlaps_iter(s1.iter().copied(), s2.iter().copied())
}
}
}
}
impl Default for AttributeMask {
fn default() -> Self {
AttributeMask::Static(&[])
}
}
#[derive(Default, PartialEq, Clone, Debug)]
pub struct NodeMask {
// must be sorted
attritutes: AttributeMask,
tag: bool,
namespace: bool,
text: bool,
listeners: bool,
}
impl NodeMask {
pub const NONE: Self = Self::new();
pub const ALL: Self = Self::new_with_attrs(AttributeMask::All)
.with_text()
.with_element();
pub fn overlaps(&self, other: &Self) -> bool {
(self.tag && other.tag)
|| (self.namespace && other.namespace)
|| self.attritutes.overlaps(&other.attritutes)
|| (self.text && other.text)
|| (self.listeners && other.listeners)
}
pub fn union(&self, other: &Self) -> Self {
Self {
attritutes: self.attritutes.union(&other.attritutes),
tag: self.tag | other.tag,
namespace: self.namespace | other.namespace,
text: self.text | other.text,
listeners: self.listeners | other.listeners,
}
}
pub const fn new_with_attrs(attritutes: AttributeMask) -> Self {
Self {
attritutes,
tag: false,
namespace: false,
text: false,
listeners: false,
}
}
pub const fn new() -> Self {
Self::new_with_attrs(AttributeMask::NONE)
}
pub const fn with_tag(mut self) -> Self {
self.tag = true;
self
}
pub const fn with_namespace(mut self) -> Self {
self.namespace = true;
self
}
pub const fn with_element(self) -> Self {
self.with_namespace().with_tag()
}
pub const fn with_text(mut self) -> Self {
self.text = true;
self
}
pub const fn with_listeners(mut self) -> Self {
self.listeners = true;
self
}
}
+774
View File
@@ -0,0 +1,774 @@
use anymap::AnyMap;
use fxhash::{FxHashMap, FxHashSet};
use std::{
collections::VecDeque,
ops::{Index, IndexMut},
};
use dioxus_core::{ElementId, Mutations, VNode, VirtualDom};
use crate::state::{union_ordered_iter, State};
use crate::{
node_ref::{AttributeMask, NodeMask},
state::MemberId,
};
/// A Dom that can sync with the VirtualDom mutations intended for use in lazy renderers.
/// The render state passes from parent to children and or accumulates state from children to parents.
/// To get started implement [PushedDownState] and or [BubbledUpState] and call [RealDom::apply_mutations] to update the dom and [RealDom::update_state] to update the state of the nodes.
#[derive(Debug)]
pub struct RealDom<S: State> {
root: usize,
nodes: Vec<Option<Node<S>>>,
nodes_listening: FxHashMap<&'static str, FxHashSet<usize>>,
node_stack: smallvec::SmallVec<[usize; 10]>,
}
impl<S: State> Default for RealDom<S> {
fn default() -> Self {
Self::new()
}
}
impl<S: State> RealDom<S> {
pub fn new() -> RealDom<S> {
RealDom {
root: 0,
nodes: {
let v = vec![Some(Node::new(
0,
NodeType::Element {
tag: "Root".to_string(),
namespace: Some("Root"),
children: Vec::new(),
},
))];
v
},
nodes_listening: FxHashMap::default(),
node_stack: smallvec::SmallVec::new(),
}
}
/// Updates the dom, up and down state and return a set of nodes that were updated pass this to update_state.
pub fn apply_mutations(&mut self, mutations_vec: Vec<Mutations>) -> Vec<(usize, NodeMask)> {
let mut nodes_updated = Vec::new();
for mutations in mutations_vec {
for e in mutations.edits {
use dioxus_core::DomEdit::*;
match e {
PushRoot { root } => self.node_stack.push(root as usize),
AppendChildren { many } => {
let target = if self.node_stack.len() > many as usize {
*self
.node_stack
.get(self.node_stack.len() - (many as usize + 1))
.unwrap()
} else {
0
};
let drained: Vec<_> = self
.node_stack
.drain(self.node_stack.len() - many as usize..)
.collect();
for ns in drained {
self.link_child(ns, target).unwrap();
nodes_updated.push((ns, NodeMask::ALL));
}
}
ReplaceWith { root, m } => {
let root = self.remove(root as usize).unwrap();
let target = root.parent.unwrap().0;
let drained: Vec<_> = self.node_stack.drain(0..m as usize).collect();
for ns in drained {
nodes_updated.push((ns, NodeMask::ALL));
self.link_child(ns, target).unwrap();
}
}
InsertAfter { root, n } => {
let target = self[root as usize].parent.unwrap().0;
let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect();
for ns in drained {
nodes_updated.push((ns, NodeMask::ALL));
self.link_child(ns, target).unwrap();
}
}
InsertBefore { root, n } => {
let target = self[root as usize].parent.unwrap().0;
let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect();
for ns in drained {
nodes_updated.push((ns, NodeMask::ALL));
self.link_child(ns, target).unwrap();
}
}
Remove { root } => {
if let Some(parent) = self[root as usize].parent {
nodes_updated.push((parent.0, NodeMask::NONE));
}
self.remove(root as usize).unwrap();
}
CreateTextNode { root, text } => {
let n = Node::new(
root,
NodeType::Text {
text: text.to_string(),
},
);
self.insert(n);
self.node_stack.push(root as usize)
}
CreateElement { root, tag } => {
let n = Node::new(
root,
NodeType::Element {
tag: tag.to_string(),
namespace: None,
children: Vec::new(),
},
);
self.insert(n);
self.node_stack.push(root as usize)
}
CreateElementNs { root, tag, ns } => {
let n = Node::new(
root,
NodeType::Element {
tag: tag.to_string(),
namespace: Some(ns),
children: Vec::new(),
},
);
self.insert(n);
self.node_stack.push(root as usize)
}
CreatePlaceholder { root } => {
let n = Node::new(root, NodeType::Placeholder);
self.insert(n);
self.node_stack.push(root as usize)
}
NewEventListener {
event_name,
scope: _,
root,
} => {
nodes_updated.push((root as usize, NodeMask::new().with_listeners()));
if let Some(v) = self.nodes_listening.get_mut(event_name) {
v.insert(root as usize);
} else {
let mut hs = FxHashSet::default();
hs.insert(root as usize);
self.nodes_listening.insert(event_name, hs);
}
}
RemoveEventListener { root, event } => {
nodes_updated.push((root as usize, NodeMask::new().with_listeners()));
let v = self.nodes_listening.get_mut(event).unwrap();
v.remove(&(root as usize));
}
SetText {
root,
text: new_text,
} => {
let target = &mut self[root as usize];
nodes_updated.push((root as usize, NodeMask::new().with_text()));
match &mut target.node_type {
NodeType::Text { text } => {
*text = new_text.to_string();
}
_ => unreachable!(),
}
}
SetAttribute { root, field, .. } => {
nodes_updated.push((
root as usize,
NodeMask::new_with_attrs(AttributeMask::single(field)),
));
}
RemoveAttribute {
root, name: field, ..
} => {
nodes_updated.push((
root as usize,
NodeMask::new_with_attrs(AttributeMask::single(field)),
));
}
PopRoot {} => {
self.node_stack.pop();
}
}
}
}
nodes_updated
}
/// Seperated from apply_mutations because Mutations require a mutable reference to the VirtualDom.
pub fn update_state(
&mut self,
vdom: &VirtualDom,
nodes_updated: Vec<(usize, NodeMask)>,
ctx: AnyMap,
) -> Option<FxHashSet<usize>> {
#[derive(PartialEq, Clone, Debug)]
enum StatesToCheck {
All,
Some(Vec<MemberId>),
}
impl StatesToCheck {
fn union(&self, other: &Self) -> Self {
match (self, other) {
(Self::Some(s), Self::Some(o)) => Self::Some(union_ordered_iter(
s.iter().copied(),
o.iter().copied(),
s.len() + o.len(),
)),
_ => Self::All,
}
}
}
#[derive(Debug, Clone)]
struct NodeRef {
id: usize,
height: u16,
node_mask: NodeMask,
to_check: StatesToCheck,
}
impl NodeRef {
fn union_with(&mut self, other: &Self) {
self.node_mask = self.node_mask.union(&other.node_mask);
self.to_check = self.to_check.union(&other.to_check);
}
}
impl Eq for NodeRef {}
impl PartialEq for NodeRef {
fn eq(&self, other: &Self) -> bool {
self.id == other.id && self.height == other.height
}
}
impl Ord for NodeRef {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.partial_cmp(other).unwrap()
}
}
impl PartialOrd for NodeRef {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
// Sort nodes first by height, then if the height is the same id.
// The order of the id does not matter it just helps with binary search later
Some(self.height.cmp(&other.height).then(self.id.cmp(&other.id)))
}
}
let mut to_rerender = FxHashSet::default();
to_rerender.extend(nodes_updated.iter().map(|(id, _)| id));
let mut nodes_updated: Vec<_> = nodes_updated
.into_iter()
.map(|(id, mask)| NodeRef {
id,
height: self[id].height,
node_mask: mask,
to_check: StatesToCheck::All,
})
.collect();
nodes_updated.sort();
// Combine mutations that affect the same node.
let mut last_node: Option<NodeRef> = None;
let mut new_nodes_updated = VecDeque::new();
for current in nodes_updated.into_iter() {
if let Some(node) = &mut last_node {
if *node == current {
node.union_with(&current);
} else {
new_nodes_updated.push_back(last_node.replace(current).unwrap());
}
} else {
last_node = Some(current);
}
}
if let Some(node) = last_node {
new_nodes_updated.push_back(node);
}
let nodes_updated = new_nodes_updated;
// update the state that only depends on nodes. The order does not matter.
for node_ref in &nodes_updated {
let mut changed = false;
let node = &mut self[node_ref.id];
let mut ids = match &node_ref.to_check {
StatesToCheck::All => node.state.node_dep_types(&node_ref.node_mask),
// this should only be triggered from the current node, so all members will need to be checked
StatesToCheck::Some(_) => unreachable!(),
};
let mut i = 0;
while i < ids.len() {
let id = ids[i];
let node = &mut self[node_ref.id];
let vnode = node.element(vdom);
if let Some(members_effected) =
node.state.update_node_dep_state(id, vnode, vdom, &ctx)
{
debug_assert!(members_effected.node_dep.iter().all(|i| i >= &id));
for m in &members_effected.node_dep {
if let Err(idx) = ids.binary_search(m) {
ids.insert(idx, *m);
}
}
changed = true;
}
i += 1;
}
if changed {
to_rerender.insert(node_ref.id);
}
}
// bubble up state. To avoid calling reduce more times than nessisary start from the bottom and go up.
let mut to_bubble = nodes_updated.clone();
while let Some(node_ref) = to_bubble.pop_back() {
let NodeRef {
id,
height,
node_mask,
to_check,
} = node_ref;
let (node, children) = self.get_node_children_mut(id).unwrap();
let children_state: Vec<_> = children.iter().map(|c| &c.state).collect();
let mut ids = match to_check {
StatesToCheck::All => node.state.child_dep_types(&node_mask),
StatesToCheck::Some(ids) => ids,
};
let mut changed = Vec::new();
let mut i = 0;
while i < ids.len() {
let id = ids[i];
let vnode = node.element(vdom);
if let Some(members_effected) =
node.state
.update_child_dep_state(id, vnode, vdom, &children_state, &ctx)
{
debug_assert!(members_effected.node_dep.iter().all(|i| i >= &id));
for m in members_effected.node_dep {
if let Err(idx) = ids.binary_search(&m) {
ids.insert(idx, m);
}
}
for m in members_effected.child_dep {
changed.push(m);
}
}
i += 1;
}
if let Some(parent_id) = node.parent {
if !changed.is_empty() {
to_rerender.insert(id);
let i = to_bubble.partition_point(
|NodeRef {
id: other_id,
height: h,
..
}| {
*h < height - 1 || (*h == height - 1 && *other_id < parent_id.0)
},
);
// make sure the parent is not already queued
if i < to_bubble.len() && to_bubble[i].id == parent_id.0 {
to_bubble[i].to_check =
to_bubble[i].to_check.union(&StatesToCheck::Some(changed));
} else {
to_bubble.insert(
i,
NodeRef {
id: parent_id.0,
height: height - 1,
node_mask: NodeMask::NONE,
to_check: StatesToCheck::Some(changed),
},
);
}
}
}
}
// push down state. To avoid calling reduce more times than nessisary start from the top and go down.
let mut to_push = nodes_updated;
while let Some(node_ref) = to_push.pop_front() {
let NodeRef {
id,
height,
node_mask,
to_check,
} = node_ref;
let node = &self[id];
let mut ids = match to_check {
StatesToCheck::All => node.state.parent_dep_types(&node_mask),
StatesToCheck::Some(ids) => ids,
};
let mut changed = Vec::new();
let (node, parent) = self.get_node_parent_mut(id).unwrap();
let mut i = 0;
while i < ids.len() {
let id = ids[i];
let vnode = node.element(vdom);
let parent = parent.as_deref();
let state = &mut node.state;
if let Some(members_effected) =
state.update_parent_dep_state(id, vnode, vdom, parent.map(|n| &n.state), &ctx)
{
debug_assert!(members_effected.node_dep.iter().all(|i| i >= &id));
for m in members_effected.node_dep {
if let Err(idx) = ids.binary_search(&m) {
ids.insert(idx, m);
}
}
for m in members_effected.parent_dep {
changed.push(m);
}
}
i += 1;
}
to_rerender.insert(id);
if !changed.is_empty() {
let node = &self[id];
if let NodeType::Element { children, .. } = &node.node_type {
for c in children {
let i = to_push.partition_point(
|NodeRef {
id: other_id,
height: h,
..
}| {
*h < height + 1 || (*h == height + 1 && *other_id < c.0)
},
);
if i < to_push.len() && to_push[i].id == c.0 {
to_push[i].to_check = to_push[i]
.to_check
.union(&StatesToCheck::Some(changed.clone()));
} else {
to_push.insert(
i,
NodeRef {
id: c.0,
height: height + 1,
node_mask: NodeMask::NONE,
to_check: StatesToCheck::Some(changed.clone()),
},
);
}
}
}
}
}
Some(to_rerender)
}
fn link_child(&mut self, child_id: usize, parent_id: usize) -> Option<()> {
debug_assert_ne!(child_id, parent_id);
let parent = &mut self[parent_id];
parent.add_child(ElementId(child_id));
let parent_height = parent.height + 1;
self[child_id].set_parent(ElementId(parent_id));
self.increase_height(child_id, parent_height);
Some(())
}
fn increase_height(&mut self, id: usize, amount: u16) {
let n = &mut self[id];
n.height += amount;
if let NodeType::Element { children, .. } = &n.node_type {
for c in children.clone() {
self.increase_height(c.0, amount);
}
}
}
// remove a node and it's children from the dom.
fn remove(&mut self, id: usize) -> Option<Node<S>> {
// We do not need to remove the node from the parent's children list for children.
fn inner<S: State>(dom: &mut RealDom<S>, id: usize) -> Option<Node<S>> {
let mut node = dom.nodes[id as usize].take()?;
if let NodeType::Element { children, .. } = &mut node.node_type {
for c in children {
inner(dom, c.0)?;
}
}
Some(node)
}
let mut node = self.nodes[id as usize].take()?;
if let Some(parent) = node.parent {
let parent = &mut self[parent];
parent.remove_child(ElementId(id));
}
if let NodeType::Element { children, .. } = &mut node.node_type {
for c in children {
inner(self, c.0)?;
}
}
Some(node)
}
fn insert(&mut self, node: Node<S>) {
let current_len = self.nodes.len();
let id = node.id.0;
if current_len - 1 < node.id.0 {
// self.nodes.reserve(1 + id - current_len);
self.nodes.extend((0..1 + id - current_len).map(|_| None));
}
self.nodes[id] = Some(node);
}
pub fn get(&self, id: usize) -> Option<&Node<S>> {
self.nodes.get(id)?.as_ref()
}
pub fn get_mut(&mut self, id: usize) -> Option<&mut Node<S>> {
self.nodes.get_mut(id)?.as_mut()
}
// this is safe because no node will have itself as a child
pub fn get_node_children_mut(
&mut self,
id: usize,
) -> Option<(&mut Node<S>, Vec<&mut Node<S>>)> {
let ptr = self.nodes.as_mut_ptr();
unsafe {
if id >= self.nodes.len() {
None
} else {
let node = &mut *ptr.add(id);
if let Some(node) = node.as_mut() {
let children = match &node.node_type {
NodeType::Element { children, .. } => children
.iter()
.map(|id| (&mut *ptr.add(id.0)).as_mut().unwrap())
.collect(),
_ => Vec::new(),
};
Some((node, children))
} else {
None
}
}
}
}
// this is safe because no node will have itself as a parent
pub fn get_node_parent_mut(
&mut self,
id: usize,
) -> Option<(&mut Node<S>, Option<&mut Node<S>>)> {
let ptr = self.nodes.as_mut_ptr();
unsafe {
let node = &mut *ptr.add(id);
if id >= self.nodes.len() {
None
} else if let Some(node) = node.as_mut() {
let parent = node
.parent
.map(|id| (&mut *ptr.add(id.0)).as_mut().unwrap());
Some((node, parent))
} else {
None
}
}
}
pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&Node<S>> {
if let Some(nodes) = self.nodes_listening.get(event) {
let mut listening: Vec<_> = nodes.iter().map(|id| &self[*id]).collect();
listening.sort_by(|n1, n2| (n1.height).cmp(&n2.height).reverse());
listening
} else {
Vec::new()
}
}
/// Check if the dom contains a node and its children.
pub fn contains_node(&self, node: &VNode) -> bool {
match node {
VNode::Component(_) => {
todo!()
}
VNode::Element(e) => {
if let Some(id) = e.id.get() {
let dom_node = &self[id];
match &dom_node.node_type {
NodeType::Element {
tag,
namespace,
children,
} => {
tag == e.tag
&& namespace == &e.namespace
&& children.iter().copied().collect::<FxHashSet<_>>()
== e.children
.iter()
.map(|c| c.mounted_id())
.collect::<FxHashSet<_>>()
&& e.children.iter().all(|c| {
self.contains_node(c)
&& self[c.mounted_id()].parent == e.id.get()
})
}
_ => false,
}
} else {
true
}
}
VNode::Fragment(f) => f.children.iter().all(|c| self.contains_node(c)),
VNode::Placeholder(_) => true,
VNode::Text(t) => {
if let Some(id) = t.id.get() {
let dom_node = &self[id];
match &dom_node.node_type {
NodeType::Text { text } => t.text == text,
_ => false,
}
} else {
true
}
}
}
}
/// Return the number of nodes in the dom.
pub fn size(&self) -> usize {
// The dom has a root node, ignore it.
self.nodes.iter().filter(|n| n.is_some()).count() - 1
}
/// Returns the id of the root node.
pub fn root_id(&self) -> usize {
self.root
}
/// Call a function for each node in the dom, depth first.
pub fn traverse_depth_first(&self, mut f: impl FnMut(&Node<S>)) {
fn inner<S: State>(dom: &RealDom<S>, id: ElementId, f: &mut impl FnMut(&Node<S>)) {
let node = &dom[id];
f(node);
if let NodeType::Element { children, .. } = &node.node_type {
for c in children {
inner(dom, *c, f);
}
}
}
if let NodeType::Element { children, .. } = &self[self.root].node_type {
for c in children {
inner(self, *c, &mut f);
}
}
}
/// Call a function for each node in the dom, depth first.
pub fn traverse_depth_first_mut(&mut self, mut f: impl FnMut(&mut Node<S>)) {
fn inner<S: State>(dom: &mut RealDom<S>, id: ElementId, f: &mut impl FnMut(&mut Node<S>)) {
let node = &mut dom[id];
f(node);
if let NodeType::Element { children, .. } = &mut node.node_type {
for c in children.clone() {
inner(dom, c, f);
}
}
}
let root = self.root;
if let NodeType::Element { children, .. } = &mut self[root].node_type {
for c in children.clone() {
inner(self, c, &mut f);
}
}
}
}
impl<S: State> Index<usize> for RealDom<S> {
type Output = Node<S>;
fn index(&self, idx: usize) -> &Self::Output {
self.get(idx).expect("Node does not exist")
}
}
impl<S: State> Index<ElementId> for RealDom<S> {
type Output = Node<S>;
fn index(&self, idx: ElementId) -> &Self::Output {
&self[idx.0]
}
}
impl<S: State> IndexMut<usize> for RealDom<S> {
fn index_mut(&mut self, idx: usize) -> &mut Self::Output {
self.get_mut(idx).expect("Node does not exist")
}
}
impl<S: State> IndexMut<ElementId> for RealDom<S> {
fn index_mut(&mut self, idx: ElementId) -> &mut Self::Output {
&mut self[idx.0]
}
}
/// The node is stored client side and stores only basic data about the node. For more complete information about the node see [`domNode::element`].
#[derive(Debug, Clone)]
pub struct Node<S: State> {
/// The id of the node this node was created from.
pub id: ElementId,
/// The parent id of the node.
pub parent: Option<ElementId>,
/// State of the node.
pub state: S,
/// Additional inforation specific to the node type
pub node_type: NodeType,
/// The number of parents before the root node. The root node has height 1.
pub height: u16,
}
#[derive(Debug, Clone)]
pub enum NodeType {
Text {
text: String,
},
Element {
tag: String,
namespace: Option<&'static str>,
children: Vec<ElementId>,
},
Placeholder,
}
impl<S: State> Node<S> {
fn new(id: u64, node_type: NodeType) -> Self {
Node {
id: ElementId(id as usize),
parent: None,
node_type,
state: S::default(),
height: 0,
}
}
/// Returns a reference to the element that this node refrences.
pub fn element<'b>(&self, vdom: &'b VirtualDom) -> &'b VNode<'b> {
vdom.get_element(self.id).unwrap()
}
fn add_child(&mut self, child: ElementId) {
if let NodeType::Element { children, .. } = &mut self.node_type {
children.push(child);
}
}
fn remove_child(&mut self, child: ElementId) {
if let NodeType::Element { children, .. } = &mut self.node_type {
children.retain(|c| c != &child);
}
}
fn set_parent(&mut self, parent: ElementId) {
self.parent = Some(parent);
}
}
+202
View File
@@ -0,0 +1,202 @@
use std::{
cmp::Ordering,
fmt::Debug,
ops::{Add, AddAssign, Sub, SubAssign},
};
use anymap::AnyMap;
use dioxus_core::VNode;
use crate::node_ref::{NodeMask, NodeView};
pub(crate) fn union_ordered_iter<T: Ord + Debug>(
s_iter: impl Iterator<Item = T>,
o_iter: impl Iterator<Item = T>,
new_len_guess: usize,
) -> Vec<T> {
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());
}
Ordering::Equal => {
o_peekable.next();
break;
}
}
}
v.push(s_peekable.next().unwrap());
}
for o_i in o_peekable {
v.push(o_i);
}
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 [BubbledUpState] is modified or a child is removed.
/// Called at most once per update.
pub trait ChildDepState {
/// The context is passed to the [PushedDownState::reduce] when it is pushed down.
/// This is sometimes nessisary for lifetime purposes.
type Ctx;
/// This must be either a [ChildDepState] or [NodeDepState]
type DepState;
const NODE_MASK: NodeMask = NodeMask::NONE;
fn reduce<'a>(
&mut self,
node: NodeView,
children: impl Iterator<Item = &'a Self::DepState>,
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 [PushedDownState] is modified.
/// Called at most once per update.
pub trait ParentDepState {
/// The context is passed to the [PushedDownState::reduce] when it is pushed down.
/// This is sometimes nessisary for lifetime purposes.
type Ctx;
/// This must be either a [ParentDepState] or [NodeDepState]
type DepState;
const NODE_MASK: NodeMask = NodeMask::NONE;
fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, 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 parrent's [PushedDownState] is modified.
/// Called at most once per update.
pub trait NodeDepState {
type Ctx;
type DepState: NodeDepState;
const NODE_MASK: NodeMask = NodeMask::NONE;
fn reduce(&mut self, node: NodeView, sibling: &Self::DepState, ctx: &Self::Ctx) -> bool;
}
#[derive(Debug)]
pub struct ChildStatesChanged {
pub node_dep: Vec<MemberId>,
pub child_dep: Vec<MemberId>,
}
#[derive(Debug)]
pub struct ParentStatesChanged {
pub node_dep: Vec<MemberId>,
pub parent_dep: Vec<MemberId>,
}
#[derive(Debug)]
pub struct NodeStatesChanged {
pub node_dep: Vec<MemberId>,
}
pub trait State: Default + Clone {
const SIZE: usize;
fn update_node_dep_state<'a>(
&'a mut self,
ty: MemberId,
node: &'a VNode<'a>,
vdom: &'a dioxus_core::VirtualDom,
ctx: &AnyMap,
) -> Option<NodeStatesChanged>;
/// This must be a valid resolution order. (no nodes updated before a state they rely on)
fn child_dep_types(&self, mask: &NodeMask) -> Vec<MemberId>;
fn update_parent_dep_state<'a>(
&'a mut self,
ty: MemberId,
node: &'a VNode<'a>,
vdom: &'a dioxus_core::VirtualDom,
parent: Option<&Self>,
ctx: &AnyMap,
) -> Option<ParentStatesChanged>;
/// This must be a valid resolution order. (no nodes updated before a state they rely on)
fn parent_dep_types(&self, mask: &NodeMask) -> Vec<MemberId>;
fn update_child_dep_state<'a>(
&'a mut self,
ty: MemberId,
node: &'a VNode<'a>,
vdom: &'a dioxus_core::VirtualDom,
children: &Vec<&Self>,
ctx: &AnyMap,
) -> Option<ChildStatesChanged>;
/// This must be a valid resolution order. (no nodes updated before a state they rely on)
fn node_dep_types(&self, mask: &NodeMask) -> Vec<MemberId>;
}
// Todo: once GATs land we can model multable dependencies
impl ChildDepState for () {
type Ctx = ();
type DepState = ();
fn reduce<'a>(
&mut self,
_: NodeView,
_: impl Iterator<Item = &'a Self::DepState>,
_: &Self::Ctx,
) -> bool
where
Self::DepState: 'a,
{
false
}
}
impl ParentDepState for () {
type Ctx = ();
type DepState = ();
fn reduce(&mut self, _: NodeView, _: Option<&Self::DepState>, _: &Self::Ctx) -> bool {
false
}
}
impl NodeDepState for () {
type Ctx = ();
type DepState = ();
fn reduce(&mut self, _: NodeView, _sibling: &Self::DepState, _: &Self::Ctx) -> bool {
false
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct MemberId(pub usize);
impl Sub<usize> for MemberId {
type Output = MemberId;
fn sub(self, rhs: usize) -> Self::Output {
MemberId(self.0 - rhs)
}
}
impl Add<usize> for MemberId {
type Output = MemberId;
fn add(self, rhs: usize) -> Self::Output {
MemberId(self.0 + rhs)
}
}
impl SubAssign<usize> for MemberId {
fn sub_assign(&mut self, rhs: usize) {
*self = *self - rhs;
}
}
impl AddAssign<usize> for MemberId {
fn add_assign(&mut self, rhs: usize) {
*self = *self + rhs;
}
}
+4 -4
View File
@@ -1,6 +1,6 @@
[package]
name = "dioxus-router"
version = "0.2.2"
version = "0.2.3"
edition = "2018"
description = "Cross-platform router for Dioxus apps"
license = "MIT/Apache-2.0"
@@ -11,9 +11,9 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dioxus-core = { path = "../core", version = "^0.2.0", default-features = false }
dioxus-html = { path = "../html", version = "^0.2.0", default-features = false }
dioxus-core-macro = { path = "../core-macro", version = "^0.2.0" }
dioxus-core = { path = "../core", version = "^0.2.1", default-features = false }
dioxus-html = { path = "../html", version = "^0.2.1", default-features = false }
dioxus-core-macro = { path = "../core-macro", version = "^0.2.1" }
futures-channel = "0.3.21"
url = { version = "2.2.2", default-features = false }
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "dioxus-ssr"
version = "0.2.0"
version = "0.2.1"
authors = ["Jonathan Kelley"]
edition = "2018"
description = "Dioxus render-to-string"
@@ -13,7 +13,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dioxus-core = { path = "../core", version = "^0.2.0", features = ["serialize"] }
dioxus-core = { path = "../core", version = "^0.2.1", features = ["serialize"] }
[dev-dependencies]
+4 -2
View File
@@ -186,7 +186,9 @@ impl<'a> TextRenderer<'a, '_> {
while let Some(attr) = attr_iter.next() {
match attr.namespace {
None => match attr.name {
"dangerous_inner_html" => inner_html = Some(attr.value),
"dangerous_inner_html" => {
inner_html = Some(attr.value.as_text().unwrap())
}
"allowfullscreen"
| "allowpaymentrequest"
| "async"
@@ -213,7 +215,7 @@ impl<'a> TextRenderer<'a, '_> {
| "reversed"
| "selected"
| "truespeed" => {
if attr.value != "false" {
if attr.value.is_truthy() {
write!(f, " {}=\"{}\"", attr.name, attr.value)?
}
}
+9 -5
View File
@@ -1,6 +1,6 @@
[package]
name = "dioxus-tui"
version = "0.2.1"
version = "0.2.2"
authors = ["Jonathan Kelley, @dementhos"]
edition = "2021"
description = "TUI-based renderer for Dioxus"
@@ -13,13 +13,17 @@ license = "MIT/Apache-2.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dioxus-core = { path = "../core", version = "^0.2.0" }
dioxus-html = { path = "../html", version = "^0.2.0" }
dioxus-core = { path = "../core", version = "^0.2.1" }
dioxus-html = { path = "../html", version = "^0.2.1" }
dioxus-native-core = { path = "../native-core", version = "^0.2.0" }
dioxus-native-core-macro = { path = "../native-core-macro", version = "^0.2.0" }
tui = "0.17.0"
crossterm = "0.23.0"
anyhow = "1.0.42"
tokio = { version = "1.15.0", features = ["full"] }
futures = "0.3.19"
stretch2 = "0.4.1"
stretch2 = "0.4.2"
smallvec = "1.6"
fxhash = "0.2"
anymap = "0.12.1"
-968
View File
@@ -1,968 +0,0 @@
/*
- [ ] pub display: Display,
- [x] pub position_type: PositionType, --> kinda, stretch doesnt support everything
- [ ] pub direction: Direction,
- [x] pub flex_direction: FlexDirection,
- [x] pub flex_wrap: FlexWrap,
- [x] pub flex_grow: f32,
- [x] pub flex_shrink: f32,
- [x] pub flex_basis: Dimension,
- [x] pub overflow: Overflow, ---> kinda implemented... stretch doesnt have support for directional overflow
- [x] pub align_items: AlignItems,
- [x] pub align_self: AlignSelf,
- [x] pub align_content: AlignContent,
- [x] pub margin: Rect<Dimension>,
- [x] pub padding: Rect<Dimension>,
- [x] pub justify_content: JustifyContent,
- [ ] pub position: Rect<Dimension>,
- [ ] pub border: Rect<Dimension>,
- [ ] pub size: Size<Dimension>, ----> ??? seems to only be relevant for input?
- [ ] pub min_size: Size<Dimension>,
- [ ] pub max_size: Size<Dimension>,
- [ ] pub aspect_ratio: Number,
*/
use stretch2::{prelude::*, style::PositionType, style::Style};
use crate::style::{RinkColor, RinkStyle};
pub struct StyleModifer {
pub style: Style,
pub tui_style: RinkStyle,
pub tui_modifier: TuiModifier,
}
#[derive(Default)]
pub struct TuiModifier {
pub borders: Borders,
}
#[derive(Default)]
pub struct Borders {
pub top: BorderEdge,
pub right: BorderEdge,
pub bottom: BorderEdge,
pub left: BorderEdge,
}
impl Borders {
fn slice(&mut self) -> [&mut BorderEdge; 4] {
[
&mut self.top,
&mut self.right,
&mut self.bottom,
&mut self.left,
]
}
}
pub struct BorderEdge {
pub color: Option<RinkColor>,
pub style: BorderStyle,
pub width: UnitSystem,
pub radius: UnitSystem,
}
impl Default for BorderEdge {
fn default() -> Self {
Self {
color: None,
style: BorderStyle::NONE,
width: UnitSystem::Point(0.0),
radius: UnitSystem::Point(0.0),
}
}
}
#[derive(Clone, Copy)]
pub enum BorderStyle {
DOTTED,
DASHED,
SOLID,
DOUBLE,
GROOVE,
RIDGE,
INSET,
OUTSET,
HIDDEN,
NONE,
}
impl BorderStyle {
pub fn symbol_set(&self) -> Option<tui::symbols::line::Set> {
use tui::symbols::line::*;
const DASHED: Set = Set {
horizontal: "╌",
vertical: "╎",
..NORMAL
};
const DOTTED: Set = Set {
horizontal: "┈",
vertical: "┊",
..NORMAL
};
match self {
BorderStyle::DOTTED => Some(DOTTED),
BorderStyle::DASHED => Some(DASHED),
BorderStyle::SOLID => Some(NORMAL),
BorderStyle::DOUBLE => Some(DOUBLE),
BorderStyle::GROOVE => Some(NORMAL),
BorderStyle::RIDGE => Some(NORMAL),
BorderStyle::INSET => Some(NORMAL),
BorderStyle::OUTSET => Some(NORMAL),
BorderStyle::HIDDEN => None,
BorderStyle::NONE => None,
}
}
}
/// applies the entire html namespace defined in dioxus-html
pub fn apply_attributes(
//
name: &str,
value: &str,
style: &mut StyleModifer,
) {
match name {
"align-content"
| "align-items"
| "align-self" => apply_align(name, value, style),
"animation"
| "animation-delay"
| "animation-direction"
| "animation-duration"
| "animation-fill-mode"
| "animation-iteration-count"
| "animation-name"
| "animation-play-state"
| "animation-timing-function" => apply_animation(name, value, style),
"backface-visibility" => {}
"background"
| "background-attachment"
| "background-clip"
| "background-color"
| "background-image"
| "background-origin"
| "background-position"
| "background-repeat"
| "background-size" => apply_background(name, value, style),
"border"
| "border-bottom"
| "border-bottom-color"
| "border-bottom-left-radius"
| "border-bottom-right-radius"
| "border-bottom-style"
| "border-bottom-width"
| "border-collapse"
| "border-color"
| "border-image"
| "border-image-outset"
| "border-image-repeat"
| "border-image-slice"
| "border-image-source"
| "border-image-width"
| "border-left"
| "border-left-color"
| "border-left-style"
| "border-left-width"
| "border-radius"
| "border-right"
| "border-right-color"
| "border-right-style"
| "border-right-width"
| "border-spacing"
| "border-style"
| "border-top"
| "border-top-color"
| "border-top-left-radius"
| "border-top-right-radius"
| "border-top-style"
| "border-top-width"
| "border-width" => apply_border(name, value, style),
"bottom" => {}
"box-shadow" => {}
"box-sizing" => {}
"caption-side" => {}
"clear" => {}
"clip" => {}
"color" => {
if let Ok(c) = value.parse() {
style.tui_style.fg.replace(c);
}
}
"column-count"
| "column-fill"
| "column-gap"
| "column-rule"
| "column-rule-color"
| "column-rule-style"
| "column-rule-width"
| "column-span"
// add column-width
| "column-width" => apply_column(name, value, style),
"columns" => {}
"content" => {}
"counter-increment" => {}
"counter-reset" => {}
"cursor" => {}
"direction" => {
match value {
"ltr" => style.style.direction = Direction::LTR,
"rtl" => style.style.direction = Direction::RTL,
_ => {}
}
}
"display" => apply_display(name, value, style),
"empty-cells" => {}
"flex"
| "flex-basis"
| "flex-direction"
| "flex-flow"
| "flex-grow"
| "flex-shrink"
| "flex-wrap" => apply_flex(name, value, style),
"float" => {}
"font"
| "font-family"
| "font-size"
| "font-size-adjust"
| "font-stretch"
| "font-style"
| "font-variant"
| "font-weight" => apply_font(name, value, style),
"height" => {
if let Some(v) = parse_value(value){
style.style.size.height = match v {
UnitSystem::Percent(v)=> Dimension::Percent(v/100.0),
UnitSystem::Point(v)=> Dimension::Points(v),
};
}
}
"justify-content" => {
use JustifyContent::*;
style.style.justify_content = match value {
"flex-start" => FlexStart,
"flex-end" => FlexEnd,
"center" => Center,
"space-between" => SpaceBetween,
"space-around" => SpaceAround,
"space-evenly" => SpaceEvenly,
_ => FlexStart,
};
}
"left" => {}
"letter-spacing" => {}
"line-height" => {}
"list-style"
| "list-style-image"
| "list-style-position"
| "list-style-type" => {}
"margin"
| "margin-bottom"
| "margin-left"
| "margin-right"
| "margin-top" => apply_margin(name, value, style),
"max-height" => {}
"max-width" => {}
"min-height" => {}
"min-width" => {}
"opacity" => {}
"order" => {}
"outline" => {}
"outline-color"
| "outline-offset"
| "outline-style"
| "outline-width" => {}
"overflow"
| "overflow-x"
| "overflow-y" => apply_overflow(name, value, style),
"padding"
| "padding-bottom"
| "padding-left"
| "padding-right"
| "padding-top" => apply_padding(name, value, style),
"page-break-after"
| "page-break-before"
| "page-break-inside" => {}
"perspective"
| "perspective-origin" => {}
"position" => {
match value {
"static" => {}
"relative" => style.style.position_type = PositionType::Relative,
"fixed" => {}
"absolute" => style.style.position_type = PositionType::Absolute,
"sticky" => {}
_ => {}
}
}
"pointer-events" => {}
"quotes" => {}
"resize" => {}
"right" => {}
"tab-size" => {}
"table-layout" => {}
"text-align"
| "text-align-last"
| "text-decoration"
| "text-decoration-color"
| "text-decoration-line"
| "text-decoration-style"
| "text-indent"
| "text-justify"
| "text-overflow"
| "text-shadow"
| "text-transform" => apply_text(name, value, style),
"top" => {}
"transform"
| "transform-origin"
| "transform-style" => apply_transform(name, value, style),
"transition"
| "transition-delay"
| "transition-duration"
| "transition-property"
| "transition-timing-function" => apply_transition(name, value, style),
"vertical-align" => {}
"visibility" => {}
"white-space" => {}
"width" => {
if let Some(v) = parse_value(value){
style.style.size.width = match v {
UnitSystem::Percent(v)=> Dimension::Percent(v/100.0),
UnitSystem::Point(v)=> Dimension::Points(v),
};
}
}
"word-break" => {}
"word-spacing" => {}
"word-wrap" => {}
"z-index" => {}
_ => {}
}
}
#[derive(Clone, Copy)]
pub enum UnitSystem {
Percent(f32),
Point(f32),
}
impl Into<Dimension> for UnitSystem {
fn into(self) -> Dimension {
match self {
Self::Percent(v) => Dimension::Percent(v),
Self::Point(v) => Dimension::Points(v),
}
}
}
fn parse_value(value: &str) -> Option<UnitSystem> {
if value.ends_with("px") {
if let Ok(px) = value.trim_end_matches("px").parse::<f32>() {
Some(UnitSystem::Point(px))
} else {
None
}
} else if value.ends_with('%') {
if let Ok(pct) = value.trim_end_matches('%').parse::<f32>() {
Some(UnitSystem::Percent(pct))
} else {
None
}
} else {
None
}
}
fn apply_overflow(name: &str, value: &str, style: &mut StyleModifer) {
match name {
// todo: add more overflow support to stretch2
"overflow" | "overflow-x" | "overflow-y" => {
style.style.overflow = match value {
"auto" => Overflow::Visible,
"hidden" => Overflow::Hidden,
"scroll" => Overflow::Scroll,
"visible" => Overflow::Visible,
_ => Overflow::Visible,
};
}
_ => {}
}
}
fn apply_display(_name: &str, value: &str, style: &mut StyleModifer) {
style.style.display = match value {
"flex" => Display::Flex,
"block" => Display::None,
_ => Display::Flex,
}
// TODO: there are way more variants
// stretch needs to be updated to handle them
//
// "block" => Display::Block,
// "inline" => Display::Inline,
// "inline-block" => Display::InlineBlock,
// "inline-table" => Display::InlineTable,
// "list-item" => Display::ListItem,
// "run-in" => Display::RunIn,
// "table" => Display::Table,
// "table-caption" => Display::TableCaption,
// "table-cell" => Display::TableCell,
// "table-column" => Display::TableColumn,
// "table-column-group" => Display::TableColumnGroup,
// "table-footer-group" => Display::TableFooterGroup,
// "table-header-group" => Display::TableHeaderGroup,
// "table-row" => Display::TableRow,
// "table-row-group" => Display::TableRowGroup,
// "none" => Display::None,
// _ => Display::Inline,
}
fn apply_background(name: &str, value: &str, style: &mut StyleModifer) {
match name {
"background-color" => {
if let Ok(c) = value.parse() {
style.tui_style.bg.replace(c);
}
}
"background" => {}
"background-attachment" => {}
"background-clip" => {}
"background-image" => {}
"background-origin" => {}
"background-position" => {}
"background-repeat" => {}
"background-size" => {}
_ => {}
}
}
fn apply_border(name: &str, value: &str, style: &mut StyleModifer) {
fn parse_border_style(v: &str) -> BorderStyle {
match v {
"dotted" => BorderStyle::DOTTED,
"dashed" => BorderStyle::DASHED,
"solid" => BorderStyle::SOLID,
"double" => BorderStyle::DOUBLE,
"groove" => BorderStyle::GROOVE,
"ridge" => BorderStyle::RIDGE,
"inset" => BorderStyle::INSET,
"outset" => BorderStyle::OUTSET,
"none" => BorderStyle::NONE,
"hidden" => BorderStyle::HIDDEN,
_ => todo!(),
}
}
match name {
"border" => {}
"border-bottom" => {}
"border-bottom-color" => {
if let Ok(c) = value.parse() {
style.tui_modifier.borders.bottom.color = Some(c);
}
}
"border-bottom-left-radius" => {
if let Some(v) = parse_value(value) {
style.tui_modifier.borders.left.radius = v;
}
}
"border-bottom-right-radius" => {
if let Some(v) = parse_value(value) {
style.tui_modifier.borders.right.radius = v;
}
}
"border-bottom-style" => {
if style.style.border.bottom == Dimension::default() {
let v = Dimension::Points(1.0);
style.style.border.bottom = v;
}
style.tui_modifier.borders.bottom.style = parse_border_style(value)
}
"border-bottom-width" => {
if let Some(v) = parse_value(value) {
style.tui_modifier.borders.bottom.width = v;
style.style.border.bottom = v.into();
}
}
"border-collapse" => {}
"border-color" => {
let values: Vec<_> = value.split(' ').collect();
if values.len() == 1 {
if let Ok(c) = values[0].parse() {
style
.tui_modifier
.borders
.slice()
.iter_mut()
.for_each(|b| b.color = Some(c));
}
} else {
for (v, b) in values
.into_iter()
.zip(style.tui_modifier.borders.slice().iter_mut())
{
if let Ok(c) = v.parse() {
b.color = Some(c);
}
}
}
}
"border-image" => {}
"border-image-outset" => {}
"border-image-repeat" => {}
"border-image-slice" => {}
"border-image-source" => {}
"border-image-width" => {}
"border-left" => {}
"border-left-color" => {
if let Ok(c) = value.parse() {
style.tui_modifier.borders.left.color = Some(c);
}
}
"border-left-style" => {
if style.style.border.start == Dimension::default() {
let v = Dimension::Points(1.0);
style.style.border.start = v;
}
style.tui_modifier.borders.left.style = parse_border_style(value)
}
"border-left-width" => {
if let Some(v) = parse_value(value) {
style.tui_modifier.borders.left.width = v;
style.style.border.start = v.into();
}
}
"border-radius" => {
let values: Vec<_> = value.split(' ').collect();
if values.len() == 1 {
if let Some(r) = parse_value(values[0]) {
style
.tui_modifier
.borders
.slice()
.iter_mut()
.for_each(|b| b.radius = r);
}
} else {
for (v, b) in values
.into_iter()
.zip(style.tui_modifier.borders.slice().iter_mut())
{
if let Some(r) = parse_value(v) {
b.radius = r;
}
}
}
}
"border-right" => {}
"border-right-color" => {
if let Ok(c) = value.parse() {
style.tui_modifier.borders.right.color = Some(c);
}
}
"border-right-style" => {
let v = Dimension::Points(1.0);
style.style.border.end = v;
style.tui_modifier.borders.right.style = parse_border_style(value)
}
"border-right-width" => {
if let Some(v) = parse_value(value) {
style.tui_modifier.borders.right.width = v;
}
}
"border-spacing" => {}
"border-style" => {
let values: Vec<_> = value.split(' ').collect();
if style.style.border.top == Dimension::default() {
let v = Dimension::Points(1.0);
style.style.border.top = v;
}
if style.style.border.bottom == Dimension::default() {
let v = Dimension::Points(1.0);
style.style.border.bottom = v;
}
if style.style.border.start == Dimension::default() {
let v = Dimension::Points(1.0);
style.style.border.start = v;
}
if style.style.border.end == Dimension::default() {
let v = Dimension::Points(1.0);
style.style.border.end = v;
}
if values.len() == 1 {
let border_style = parse_border_style(values[0]);
style
.tui_modifier
.borders
.slice()
.iter_mut()
.for_each(|b| b.style = border_style);
} else {
for (v, b) in values
.into_iter()
.zip(style.tui_modifier.borders.slice().iter_mut())
{
b.style = parse_border_style(v);
}
}
}
"border-top" => {}
"border-top-color" => {
if let Ok(c) = value.parse() {
style.tui_modifier.borders.top.color = Some(c);
}
}
"border-top-left-radius" => {
if let Some(v) = parse_value(value) {
style.tui_modifier.borders.left.radius = v;
}
}
"border-top-right-radius" => {
if let Some(v) = parse_value(value) {
style.tui_modifier.borders.right.radius = v;
}
}
"border-top-style" => {
if style.style.border.top == Dimension::default() {
let v = Dimension::Points(1.0);
style.style.border.top = v;
}
style.tui_modifier.borders.top.style = parse_border_style(value)
}
"border-top-width" => {
if let Some(v) = parse_value(value) {
style.style.border.top = v.into();
style.tui_modifier.borders.top.width = v;
}
}
"border-width" => {
let values: Vec<_> = value.split(' ').collect();
if values.len() == 1 {
if let Some(w) = parse_value(values[0]) {
style.style.border.top = w.into();
style.style.border.bottom = w.into();
style.style.border.start = w.into();
style.style.border.end = w.into();
style
.tui_modifier
.borders
.slice()
.iter_mut()
.for_each(|b| b.width = w);
}
} else {
let border_widths = [
&mut style.style.border.top,
&mut style.style.border.bottom,
&mut style.style.border.start,
&mut style.style.border.end,
];
for ((v, b), width) in values
.into_iter()
.zip(style.tui_modifier.borders.slice().iter_mut())
.zip(border_widths)
{
if let Some(w) = parse_value(v) {
*width = w.into();
b.width = w;
}
}
}
}
_ => (),
}
}
fn apply_animation(name: &str, _value: &str, _style: &mut StyleModifer) {
match name {
"animation" => {}
"animation-delay" => {}
"animation-direction =>{}" => {}
"animation-duration" => {}
"animation-fill-mode" => {}
"animation-itera =>{}tion-count" => {}
"animation-name" => {}
"animation-play-state" => {}
"animation-timing-function" => {}
_ => {}
}
}
fn apply_column(name: &str, _value: &str, _style: &mut StyleModifer) {
match name {
"column-count" => {}
"column-fill" => {}
"column-gap" => {}
"column-rule" => {}
"column-rule-color" => {}
"column-rule-style" => {}
"column-rule-width" => {}
"column-span" => {}
"column-width" => {}
_ => {}
}
}
fn apply_flex(name: &str, value: &str, style: &mut StyleModifer) {
// - [x] pub flex_direction: FlexDirection,
// - [x] pub flex_wrap: FlexWrap,
// - [x] pub flex_grow: f32,
// - [x] pub flex_shrink: f32,
// - [x] pub flex_basis: Dimension,
match name {
"flex" => {}
"flex-direction" => {
use FlexDirection::*;
style.style.flex_direction = match value {
"row" => Row,
"row-reverse" => RowReverse,
"column" => Column,
"column-reverse" => ColumnReverse,
_ => Row,
};
}
"flex-basis" => {
if let Some(v) = parse_value(value) {
style.style.flex_basis = match v {
UnitSystem::Percent(v) => Dimension::Percent(v / 100.0),
UnitSystem::Point(v) => Dimension::Points(v),
};
}
}
"flex-flow" => {}
"flex-grow" => {
if let Ok(val) = value.parse::<f32>() {
style.style.flex_grow = val;
}
}
"flex-shrink" => {
if let Ok(px) = value.parse::<f32>() {
style.style.flex_shrink = px;
}
}
"flex-wrap" => {
use FlexWrap::*;
style.style.flex_wrap = match value {
"nowrap" => NoWrap,
"wrap" => Wrap,
"wrap-reverse" => WrapReverse,
_ => NoWrap,
};
}
_ => {}
}
}
fn apply_font(name: &str, value: &str, style: &mut StyleModifer) {
use tui::style::Modifier;
match name {
"font" => (),
"font-family" => (),
"font-size" => (),
"font-size-adjust" => (),
"font-stretch" => (),
"font-style" => match value {
"italic" => style.tui_style = style.tui_style.add_modifier(Modifier::ITALIC),
"oblique" => style.tui_style = style.tui_style.add_modifier(Modifier::ITALIC),
_ => (),
},
"font-variant" => todo!(),
"font-weight" => match value {
"bold" => style.tui_style = style.tui_style.add_modifier(Modifier::BOLD),
"normal" => style.tui_style = style.tui_style.remove_modifier(Modifier::BOLD),
_ => (),
},
_ => (),
}
}
fn apply_padding(name: &str, value: &str, style: &mut StyleModifer) {
match parse_value(value) {
Some(UnitSystem::Percent(v)) => match name {
"padding" => {
let v = Dimension::Percent(v / 100.0);
style.style.padding.top = v;
style.style.padding.bottom = v;
style.style.padding.start = v;
style.style.padding.end = v;
}
"padding-bottom" => style.style.padding.bottom = Dimension::Percent(v / 100.0),
"padding-left" => style.style.padding.start = Dimension::Percent(v / 100.0),
"padding-right" => style.style.padding.end = Dimension::Percent(v / 100.0),
"padding-top" => style.style.padding.top = Dimension::Percent(v / 100.0),
_ => {}
},
Some(UnitSystem::Point(v)) => match name {
"padding" => {
style.style.padding.top = Dimension::Points(v);
style.style.padding.bottom = Dimension::Points(v);
style.style.padding.start = Dimension::Points(v);
style.style.padding.end = Dimension::Points(v);
}
"padding-bottom" => style.style.padding.bottom = Dimension::Points(v),
"padding-left" => style.style.padding.start = Dimension::Points(v),
"padding-right" => style.style.padding.end = Dimension::Points(v),
"padding-top" => style.style.padding.top = Dimension::Points(v),
_ => {}
},
None => {}
}
}
fn apply_text(name: &str, value: &str, style: &mut StyleModifer) {
use tui::style::Modifier;
match name {
"text-align" => todo!(),
"text-align-last" => todo!(),
"text-decoration" | "text-decoration-line" => {
for v in value.split(' ') {
match v {
"line-through" => {
style.tui_style = style.tui_style.add_modifier(Modifier::CROSSED_OUT)
}
"underline" => {
style.tui_style = style.tui_style.add_modifier(Modifier::UNDERLINED)
}
_ => (),
}
}
}
"text-decoration-color" => todo!(),
"text-decoration-style" => todo!(),
"text-indent" => todo!(),
"text-justify" => todo!(),
"text-overflow" => todo!(),
"text-shadow" => todo!(),
"text-transform" => todo!(),
_ => todo!(),
}
}
fn apply_transform(_name: &str, _value: &str, _style: &mut StyleModifer) {
todo!()
}
fn apply_transition(_name: &str, _value: &str, _style: &mut StyleModifer) {
todo!()
}
fn apply_align(name: &str, value: &str, style: &mut StyleModifer) {
match name {
"align-items" => {
use AlignItems::*;
style.style.align_items = match value {
"flex-start" => FlexStart,
"flex-end" => FlexEnd,
"center" => Center,
"baseline" => Baseline,
"stretch" => Stretch,
_ => FlexStart,
};
}
"align-content" => {
use AlignContent::*;
style.style.align_content = match value {
"flex-start" => FlexStart,
"flex-end" => FlexEnd,
"center" => Center,
"space-between" => SpaceBetween,
"space-around" => SpaceAround,
_ => FlexStart,
};
}
"align-self" => {
use AlignSelf::*;
style.style.align_self = match value {
"auto" => Auto,
"flex-start" => FlexStart,
"flex-end" => FlexEnd,
"center" => Center,
"baseline" => Baseline,
"stretch" => Stretch,
_ => Auto,
};
}
_ => {}
}
}
pub fn apply_size(_name: &str, _value: &str, _style: &mut StyleModifer) {
//
}
pub fn apply_margin(name: &str, value: &str, style: &mut StyleModifer) {
match parse_value(value) {
Some(UnitSystem::Percent(v)) => match name {
"margin" => {
let v = Dimension::Percent(v / 100.0);
style.style.margin.top = v;
style.style.margin.bottom = v;
style.style.margin.start = v;
style.style.margin.end = v;
}
"margin-top" => style.style.margin.top = Dimension::Percent(v / 100.0),
"margin-bottom" => style.style.margin.bottom = Dimension::Percent(v / 100.0),
"margin-left" => style.style.margin.start = Dimension::Percent(v / 100.0),
"margin-right" => style.style.margin.end = Dimension::Percent(v / 100.0),
_ => {}
},
Some(UnitSystem::Point(v)) => match name {
"margin" => {
style.style.margin.top = Dimension::Points(v);
style.style.margin.bottom = Dimension::Points(v);
style.style.margin.start = Dimension::Points(v);
style.style.margin.end = Dimension::Points(v);
}
"margin-top" => style.style.margin.top = Dimension::Points(v),
"margin-bottom" => style.style.margin.bottom = Dimension::Points(v),
"margin-left" => style.style.margin.start = Dimension::Points(v),
"margin-right" => style.style.margin.end = Dimension::Points(v),
_ => {}
},
None => {}
}
}
+2 -2
View File
@@ -21,9 +21,9 @@ impl Config {
}
}
pub fn with_ctrl_c_quit(self) -> Self {
pub fn without_ctrl_c_quit(self) -> Self {
Self {
ctrl_c_quit: true,
ctrl_c_quit: false,
..self
}
}
+461 -359
View File
@@ -2,20 +2,25 @@ use crossterm::event::{
Event as TermEvent, KeyCode as TermKeyCode, KeyModifiers, MouseButton, MouseEventKind,
};
use dioxus_core::*;
use fxhash::{FxHashMap, FxHashSet};
use dioxus_html::geometry::euclid::{Point2D, Rect, Size2D};
use dioxus_html::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint};
use dioxus_html::input_data::keyboard_types::Modifiers;
use dioxus_html::input_data::MouseButtonSet as DioxusMouseButtons;
use dioxus_html::input_data::{MouseButton as DioxusMouseButton, MouseButtonSet};
use dioxus_html::{on::*, KeyCode};
use futures::{channel::mpsc::UnboundedReceiver, StreamExt};
use std::{
any::Any,
cell::RefCell,
collections::{HashMap, HashSet},
rc::Rc,
sync::Arc,
time::{Duration, Instant},
};
use stretch2::geometry::{Point, Size};
use stretch2::{prelude::Layout, Stretch};
use crate::TuiNode;
use crate::{Dom, Node};
// a wrapper around the input state for easier access
// todo: fix loop
@@ -31,12 +36,12 @@ use crate::TuiNode;
// pub fn mouse(&self) -> Option<MouseData> {
// let data = (**self.0).borrow();
// data.mouse.as_ref().map(|m| clone_mouse_data(m))
// data.mouse.as_ref().map(|m| m.clone())
// }
// pub fn wheel(&self) -> Option<WheelData> {
// let data = (**self.0).borrow();
// data.wheel.as_ref().map(|w| clone_wheel_data(w))
// data.wheel.as_ref().map(|w| w.clone())
// }
// pub fn screen(&self) -> Option<(u16, u16)> {
@@ -48,7 +53,7 @@ use crate::TuiNode;
// let data = (**self.0).borrow();
// data.last_key_pressed
// .as_ref()
// .map(|k| clone_keyboard_data(&k.0))
// .map(|k| &k.0.clone())
// }
// }
@@ -75,7 +80,7 @@ impl EventData {
const MAX_REPEAT_TIME: Duration = Duration::from_millis(100);
pub struct InnerInputState {
mouse: Option<(MouseData, Vec<u16>)>,
mouse: Option<MouseData>,
wheel: Option<WheelData>,
last_key_pressed: Option<(KeyboardData, Instant)>,
screen: Option<(u16, u16)>,
@@ -97,55 +102,39 @@ impl InnerInputState {
fn apply_event(&mut self, evt: &mut EventCore) {
match evt.1 {
// limitations: only two buttons may be held at once
EventData::Mouse(ref mut m) => match &mut self.mouse {
Some(state) => {
let mut buttons = state.0.buttons;
state.0 = clone_mouse_data(m);
match evt.0 {
// this code only runs when there are no buttons down
"mouseup" => {
buttons = 0;
state.1 = Vec::new();
}
"mousedown" => {
if state.1.contains(&m.buttons) {
// if we already pressed a button and there is another button released the button crossterm sends is the button remaining
if state.1.len() > 1 {
evt.0 = "mouseup";
state.1 = vec![m.buttons];
}
// otherwise some other button was pressed. In testing it was consistantly this mapping
else {
match m.buttons {
0x01 => state.1.push(0x02),
0x02 => state.1.push(0x01),
0x04 => state.1.push(0x01),
_ => (),
}
}
} else {
state.1.push(m.buttons);
}
EventData::Mouse(ref mut m) => {
let mut held_buttons = match &self.mouse {
Some(previous_data) => previous_data.held_buttons(),
None => MouseButtonSet::empty(),
};
buttons = state.1.iter().copied().reduce(|a, b| a | b).unwrap();
}
_ => (),
match evt.0 {
"mousedown" => {
held_buttons.insert(
m.trigger_button()
.expect("No trigger button for mousedown event"),
);
}
state.0.buttons = buttons;
m.buttons = buttons;
"mouseup" => {
held_buttons.remove(
m.trigger_button()
.expect("No trigger button for mouseup event"),
);
}
_ => {}
}
None => {
self.mouse = Some((
clone_mouse_data(m),
if m.buttons == 0 {
Vec::new()
} else {
vec![m.buttons]
},
));
}
},
EventData::Wheel(ref w) => self.wheel = Some(clone_wheel_data(w)),
let new_mouse_data = MouseData::new(
m.coordinates(),
m.trigger_button(),
held_buttons,
m.modifiers(),
);
self.mouse = Some(new_mouse_data.clone());
*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 repeat = self
@@ -154,160 +143,20 @@ impl InnerInputState {
.filter(|k2| k2.0.key == k.key && k2.1.elapsed() < MAX_REPEAT_TIME)
.is_some();
k.repeat = repeat;
let new = clone_keyboard_data(k);
let new = k.clone();
self.last_key_pressed = Some((new, Instant::now()));
}
}
}
fn update<'a>(
fn update(
&mut self,
dom: &'a VirtualDom,
evts: &mut Vec<EventCore>,
evts: &mut [EventCore],
resolved_events: &mut Vec<UserEvent>,
layout: &Stretch,
layouts: &mut HashMap<ElementId, TuiNode<'a>>,
node: &'a VNode<'a>,
dom: &mut Dom,
) {
struct Data<'b> {
new_pos: (i32, i32),
old_pos: Option<(i32, i32)>,
clicked: bool,
released: bool,
wheel_delta: f64,
mouse_data: &'b MouseData,
wheel_data: &'b Option<WheelData>,
}
fn layout_contains_point(layout: &Layout, point: (i32, i32)) -> bool {
layout.location.x as i32 <= point.0
&& layout.location.x as i32 + layout.size.width as i32 >= point.0
&& layout.location.y as i32 <= point.1
&& layout.location.y as i32 + layout.size.height as i32 >= point.1
}
fn get_mouse_events<'c, 'd>(
dom: &'c VirtualDom,
resolved_events: &mut Vec<UserEvent>,
layout: &Stretch,
layouts: &HashMap<ElementId, TuiNode<'c>>,
node: &'c VNode<'c>,
data: &'d Data<'d>,
) -> HashSet<&'static str> {
match node {
VNode::Fragment(f) => {
let mut union = HashSet::new();
for child in f.children {
union = union
.union(&get_mouse_events(
dom,
resolved_events,
layout,
layouts,
child,
data,
))
.copied()
.collect();
}
return union;
}
VNode::Component(vcomp) => {
let idx = vcomp.scope.get().unwrap();
let new_node = dom.get_scope(idx).unwrap().root_node();
return get_mouse_events(dom, resolved_events, layout, layouts, new_node, data);
}
VNode::Placeholder(_) => return HashSet::new(),
VNode::Element(_) | VNode::Text(_) => {}
}
let id = node.try_mounted_id().unwrap();
let node = layouts.get(&id).unwrap();
let node_layout = layout.layout(node.layout).unwrap();
let previously_contained = data
.old_pos
.filter(|pos| layout_contains_point(node_layout, *pos))
.is_some();
let currently_contains = layout_contains_point(node_layout, data.new_pos);
match node.node {
VNode::Element(el) => {
let mut events = HashSet::new();
if previously_contained || currently_contains {
for c in el.children {
events = events
.union(&get_mouse_events(
dom,
resolved_events,
layout,
layouts,
c,
data,
))
.copied()
.collect();
}
}
let mut try_create_event = |name| {
// only trigger event if the event was not triggered already by a child
if events.insert(name) {
resolved_events.push(UserEvent {
scope_id: None,
priority: EventPriority::Medium,
name,
element: Some(el.id.get().unwrap()),
data: Arc::new(clone_mouse_data(data.mouse_data)),
})
}
};
if currently_contains {
if !previously_contained {
try_create_event("mouseenter");
try_create_event("mouseover");
}
if data.clicked {
try_create_event("mousedown");
}
if data.released {
try_create_event("mouseup");
match data.mouse_data.button {
0 => try_create_event("click"),
2 => try_create_event("contextmenu"),
_ => (),
}
}
if let Some(w) = data.wheel_data {
if data.wheel_delta != 0.0 {
resolved_events.push(UserEvent {
scope_id: None,
priority: EventPriority::Medium,
name: "wheel",
element: Some(el.id.get().unwrap()),
data: Arc::new(clone_wheel_data(w)),
})
}
}
} else if previously_contained {
try_create_event("mouseleave");
try_create_event("mouseout");
}
events
}
VNode::Text(_) => HashSet::new(),
_ => todo!(),
}
}
let previous_mouse = self
.mouse
.as_ref()
.map(|m| (clone_mouse_data(&m.0), m.1.clone()));
// println!("{previous_mouse:?}");
let previous_mouse = self.mouse.clone();
self.wheel = None;
@@ -315,36 +164,324 @@ impl InnerInputState {
self.apply_event(e);
}
// resolve hover events
if let Some(mouse) = &self.mouse {
let new_pos = (mouse.0.screen_x, mouse.0.screen_y);
let old_pos = previous_mouse
.as_ref()
.map(|m| (m.0.screen_x, m.0.screen_y));
let clicked =
(!mouse.0.buttons & previous_mouse.as_ref().map(|m| m.0.buttons).unwrap_or(0)) > 0;
let released =
(mouse.0.buttons & !previous_mouse.map(|m| m.0.buttons).unwrap_or(0)) > 0;
let wheel_delta = self.wheel.as_ref().map_or(0.0, |w| w.delta_y);
let mouse_data = &mouse.0;
let wheel_data = &self.wheel;
let data = Data {
new_pos,
old_pos,
clicked,
released,
wheel_delta,
mouse_data,
wheel_data,
};
get_mouse_events(dom, resolved_events, layout, layouts, node, &data);
}
self.resolve_mouse_events(previous_mouse, resolved_events, layout, dom);
// for s in &self.subscribers {
// s();
// }
}
fn resolve_mouse_events(
&self,
previous_mouse: Option<MouseData>,
resolved_events: &mut Vec<UserEvent>,
layout: &Stretch,
dom: &mut Dom,
) {
fn layout_contains_point(layout: &Layout, point: ScreenPoint) -> bool {
let Point { x, y } = layout.location;
let Size { width, height } = layout.size;
let layout_rect = Rect::new(Point2D::new(x, y), Size2D::new(width, height));
layout_rect.contains(point.cast())
}
fn try_create_event(
name: &'static str,
data: Arc<dyn Any + Send + Sync>,
will_bubble: &mut FxHashSet<ElementId>,
resolved_events: &mut Vec<UserEvent>,
node: &Node,
dom: &Dom,
) {
// only trigger event if the event was not triggered already by a child
if will_bubble.insert(node.id) {
let mut parent = node.parent;
while let Some(parent_id) = parent {
will_bubble.insert(parent_id);
parent = dom[parent_id.0].parent;
}
resolved_events.push(UserEvent {
scope_id: None,
priority: EventPriority::Medium,
name,
element: Some(node.id),
data,
})
}
}
fn prepare_mouse_data(mouse_data: &MouseData, layout: &Layout) -> MouseData {
let Point { x, y } = layout.location;
let node_origin = ClientPoint::new(x.into(), y.into());
let new_client_coordinates = (mouse_data.client_coordinates() - node_origin)
.to_point()
.cast_unit();
let coordinates = Coordinates::new(
mouse_data.screen_coordinates(),
mouse_data.client_coordinates(),
new_client_coordinates,
mouse_data.page_coordinates(),
);
MouseData::new(
coordinates,
mouse_data.trigger_button(),
mouse_data.held_buttons(),
mouse_data.modifiers(),
)
}
if let Some(mouse_data) = &self.mouse {
let new_pos = mouse_data.screen_coordinates();
let old_pos = previous_mouse.as_ref().map(|m| m.screen_coordinates());
// a mouse button is pressed if a button was not down and is now down
let previous_buttons = previous_mouse
.map_or(MouseButtonSet::empty(), |previous_data| {
previous_data.held_buttons()
});
let was_pressed = !(mouse_data.held_buttons() - previous_buttons).is_empty();
// a mouse button is released if a button was down and is now not down
let was_released = !(previous_buttons - mouse_data.held_buttons()).is_empty();
let wheel_delta = self.wheel.as_ref().map_or(0.0, |w| w.delta_y);
let wheel_data = &self.wheel;
{
// mousemove
if old_pos != Some(new_pos) {
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("mousemove") {
let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
let previously_contained = old_pos
.filter(|pos| layout_contains_point(node_layout, *pos))
.is_some();
let currently_contains = layout_contains_point(node_layout, new_pos);
if currently_contains && previously_contained {
try_create_event(
"mousemove",
Arc::new(prepare_mouse_data(mouse_data, node_layout)),
&mut will_bubble,
resolved_events,
node,
dom,
);
}
}
}
}
{
// mouseenter
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("mouseenter") {
let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
let previously_contained = old_pos
.filter(|pos| layout_contains_point(node_layout, *pos))
.is_some();
let currently_contains = layout_contains_point(node_layout, new_pos);
if currently_contains && !previously_contained {
try_create_event(
"mouseenter",
Arc::new(mouse_data.clone()),
&mut will_bubble,
resolved_events,
node,
dom,
);
}
}
}
{
// mouseover
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("mouseover") {
let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
let previously_contained = old_pos
.filter(|pos| layout_contains_point(node_layout, *pos))
.is_some();
let currently_contains = layout_contains_point(node_layout, new_pos);
if currently_contains && !previously_contained {
try_create_event(
"mouseover",
Arc::new(prepare_mouse_data(mouse_data, node_layout)),
&mut will_bubble,
resolved_events,
node,
dom,
);
}
}
}
// mousedown
if was_pressed {
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("mousedown") {
let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
let currently_contains = layout_contains_point(node_layout, new_pos);
if currently_contains {
try_create_event(
"mousedown",
Arc::new(prepare_mouse_data(mouse_data, node_layout)),
&mut will_bubble,
resolved_events,
node,
dom,
);
}
}
}
{
// mouseup
if was_released {
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("mouseup") {
let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
let currently_contains = layout_contains_point(node_layout, new_pos);
if currently_contains {
try_create_event(
"mouseup",
Arc::new(prepare_mouse_data(mouse_data, node_layout)),
&mut will_bubble,
resolved_events,
node,
dom,
);
}
}
}
}
{
// click
if mouse_data.trigger_button() == Some(DioxusMouseButton::Primary) && was_released {
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("click") {
let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
let currently_contains = layout_contains_point(node_layout, new_pos);
if currently_contains {
try_create_event(
"click",
Arc::new(prepare_mouse_data(mouse_data, node_layout)),
&mut will_bubble,
resolved_events,
node,
dom,
);
}
}
}
}
{
// contextmenu
if mouse_data.trigger_button() == Some(DioxusMouseButton::Secondary) && was_released
{
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("contextmenu") {
let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
let currently_contains = layout_contains_point(node_layout, new_pos);
if currently_contains {
try_create_event(
"contextmenu",
Arc::new(prepare_mouse_data(mouse_data, node_layout)),
&mut will_bubble,
resolved_events,
node,
dom,
);
}
}
}
}
{
// wheel
if let Some(w) = wheel_data {
if wheel_delta != 0.0 {
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("wheel") {
let node_layout =
layout.layout(node.state.layout.node.unwrap()).unwrap();
let currently_contains = layout_contains_point(node_layout, new_pos);
if currently_contains {
try_create_event(
"wheel",
Arc::new(w.clone()),
&mut will_bubble,
resolved_events,
node,
dom,
);
}
}
}
}
}
{
// mouseleave
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("mouseleave") {
let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
let previously_contained = old_pos
.filter(|pos| layout_contains_point(node_layout, *pos))
.is_some();
let currently_contains = layout_contains_point(node_layout, new_pos);
if !currently_contains && previously_contained {
try_create_event(
"mouseleave",
Arc::new(prepare_mouse_data(mouse_data, node_layout)),
&mut will_bubble,
resolved_events,
node,
dom,
);
}
}
}
{
// mouseout
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("mouseout") {
let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
let previously_contained = old_pos
.filter(|pos| layout_contains_point(node_layout, *pos))
.is_some();
let currently_contains = layout_contains_point(node_layout, new_pos);
if !currently_contains && previously_contained {
try_create_event(
"mouseout",
Arc::new(prepare_mouse_data(mouse_data, node_layout)),
&mut will_bubble,
resolved_events,
node,
dom,
);
}
}
}
}
}
// fn subscribe(&mut self, f: Rc<dyn Fn() + 'static>) {
// self.subscribers.push(f)
// }
@@ -358,24 +495,21 @@ 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(
mut receiver: UnboundedReceiver<TermEvent>,
cx: &ScopeState,
) -> (Self, Rc<RefCell<InnerInputState>>) {
pub fn new() -> (
Self,
Rc<RefCell<InnerInputState>>,
impl FnMut(crossterm::event::Event),
) {
let queued_events = Rc::new(RefCell::new(Vec::new()));
let queued_events2 = Rc::<RefCell<std::vec::Vec<_>>>::downgrade(&queued_events);
let queued_events2 = Rc::downgrade(&queued_events);
cx.push_future(async move {
while let Some(evt) = receiver.next().await {
if let Some(evt) = get_event(evt) {
if let Some(v) = queued_events2.upgrade() {
(*v).borrow_mut().push(evt);
} else {
break;
}
let regester_event = move |evt: crossterm::event::Event| {
if let Some(evt) = get_event(evt) {
if let Some(v) = queued_events2.upgrade() {
(*v).borrow_mut().push(evt);
}
}
});
};
let state = Rc::new(RefCell::new(InnerInputState::new()));
@@ -385,73 +519,65 @@ impl RinkInputHandler {
queued_events,
},
state,
regester_event,
)
}
pub fn get_events<'a>(
&self,
dom: &'a VirtualDom,
layout: &Stretch,
layouts: &mut HashMap<ElementId, TuiNode<'a>>,
node: &'a VNode<'a>,
) -> Vec<UserEvent> {
// todo: currently resolves events in all nodes, but once the focus system is added it should filter by focus
fn inner(
queue: &[(&'static str, Arc<dyn Any + Send + Sync>)],
resolved: &mut Vec<UserEvent>,
node: &VNode,
) {
match node {
VNode::Fragment(frag) => {
for c in frag.children {
inner(queue, resolved, c);
}
}
VNode::Element(el) => {
for l in el.listeners {
for (name, data) in queue.iter() {
if *name == l.event {
if let Some(id) = el.id.get() {
resolved.push(UserEvent {
scope_id: None,
priority: EventPriority::Medium,
name: *name,
element: Some(id),
data: data.clone(),
});
}
}
}
}
for c in el.children {
inner(queue, resolved, c);
}
}
_ => (),
}
}
pub(crate) fn get_events(&self, layout: &Stretch, dom: &mut Dom) -> Vec<UserEvent> {
let mut resolved_events = Vec::new();
(*self.state).borrow_mut().update(
dom,
&mut (*self.queued_events).borrow_mut(),
&mut resolved_events,
layout,
layouts,
node,
dom,
);
let events: Vec<_> = self
let events = self
.queued_events
.replace(Vec::new())
.into_iter()
// these events were added in the update stage
.filter(|e| !["mousedown", "mouseup", "mousemove", "drag", "wheel"].contains(&e.0))
.map(|e| (e.0, e.1.into_any()))
.collect();
.filter(|e| {
![
"mouseenter",
"mouseover",
"mouseleave",
"mouseout",
"mousedown",
"mouseup",
"mousemove",
"drag",
"wheel",
"click",
"contextmenu",
]
.contains(&e.0)
})
.map(|evt| (evt.0, evt.1.into_any()));
inner(&events, &mut resolved_events, node);
// todo: currently resolves events in all nodes, but once the focus system is added it should filter by focus
let mut hm: FxHashMap<&'static str, Vec<Arc<dyn Any + Send + Sync>>> = FxHashMap::default();
for (event, data) in events {
if let Some(v) = hm.get_mut(event) {
v.push(data);
} else {
hm.insert(event, vec![data]);
}
}
for (event, datas) in hm {
for node in dom.get_listening_sorted(event) {
for data in &datas {
resolved_events.push(UserEvent {
scope_id: None,
priority: EventPriority::Medium,
name: event,
element: Some(node.id),
data: data.clone(),
});
}
}
}
resolved_events
}
@@ -468,34 +594,47 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> {
let ctrl = m.modifiers.contains(KeyModifiers::CONTROL);
let meta = false;
let get_mouse_data = |b| {
let buttons = match b {
None => 0,
Some(MouseButton::Left) => 1,
Some(MouseButton::Right) => 2,
Some(MouseButton::Middle) => 4,
};
let button_state = match b {
None => 0,
Some(MouseButton::Left) => 0,
Some(MouseButton::Middle) => 1,
Some(MouseButton::Right) => 2,
};
let get_mouse_data = |crossterm_button: Option<MouseButton>| {
let button = crossterm_button.map(|b| match b {
MouseButton::Left => DioxusMouseButton::Primary,
MouseButton::Right => DioxusMouseButton::Secondary,
MouseButton::Middle => DioxusMouseButton::Auxiliary,
});
// from https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent
EventData::Mouse(MouseData {
alt_key: alt,
button: button_state,
buttons,
client_x: x,
client_y: y,
ctrl_key: ctrl,
meta_key: meta,
page_x: x,
page_y: y,
screen_x: x,
screen_y: y,
shift_key: shift,
})
// The `page` and `screen` coordinates are inconsistent with the MDN definition, as they are relative to the viewport (client), not the target element/page/screen, respectively.
// todo?
// But then, MDN defines them in terms of pixels, yet crossterm provides only row/column, and it might not be possible to get pixels. So we can't get 100% consistency anyway.
let coordinates = Coordinates::new(
ScreenPoint::new(x, y),
ClientPoint::new(x, y),
// offset x/y are set when the origin of the event is assigned to an element
ElementPoint::new(0., 0.),
PagePoint::new(x, y),
);
let mut modifiers = Modifiers::empty();
if shift {
modifiers.insert(Modifiers::SHIFT);
}
if ctrl {
modifiers.insert(Modifiers::CONTROL);
}
if meta {
modifiers.insert(Modifiers::META);
}
if alt {
modifiers.insert(Modifiers::ALT);
}
// held mouse buttons get set later by maintaining state, as crossterm does not provide them
EventData::Mouse(MouseData::new(
coordinates,
button,
DioxusMouseButtons::empty(),
modifiers,
))
};
let get_wheel_data = |up| {
@@ -525,6 +664,7 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> {
fn translate_key_event(event: crossterm::event::KeyEvent) -> Option<EventData> {
let (code, key_str);
let mut shift_key = event.modifiers.contains(KeyModifiers::SHIFT);
if let TermKeyCode::Char(c) = event.code {
code = match c {
'A'..='Z' | 'a'..='z' => match c.to_ascii_uppercase() {
@@ -638,7 +778,11 @@ fn translate_key_event(event: crossterm::event::KeyEvent) -> Option<EventData> {
12 => KeyCode::F12,
_ => return None,
},
TermKeyCode::BackTab => return None,
// backtab is Shift + Tab
TermKeyCode::BackTab => {
shift_key = true;
KeyCode::Tab
}
TermKeyCode::Null => return None,
_ => return None,
};
@@ -651,57 +795,15 @@ fn translate_key_event(event: crossterm::event::KeyEvent) -> Option<EventData> {
// from https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
Some(EventData::Keyboard(KeyboardData {
char_code: code.raw_code(),
key: key_str.to_string(),
key: key_str,
key_code: code,
alt_key: event.modifiers.contains(KeyModifiers::ALT),
ctrl_key: event.modifiers.contains(KeyModifiers::CONTROL),
meta_key: false,
shift_key: event.modifiers.contains(KeyModifiers::SHIFT),
shift_key,
locale: Default::default(),
location: 0x00,
repeat: Default::default(),
which: Default::default(),
}))
}
fn clone_mouse_data(m: &MouseData) -> MouseData {
MouseData {
client_x: m.client_x,
client_y: m.client_y,
page_x: m.page_x,
page_y: m.page_y,
screen_x: m.screen_x,
screen_y: m.screen_y,
alt_key: m.alt_key,
ctrl_key: m.ctrl_key,
meta_key: m.meta_key,
shift_key: m.shift_key,
button: m.button,
buttons: m.buttons,
}
}
fn clone_keyboard_data(k: &KeyboardData) -> KeyboardData {
KeyboardData {
char_code: k.char_code,
key: k.key.clone(),
key_code: k.key_code,
alt_key: k.alt_key,
ctrl_key: k.ctrl_key,
meta_key: k.meta_key,
shift_key: k.shift_key,
locale: k.locale.clone(),
location: k.location,
repeat: k.repeat,
which: k.which,
}
}
fn clone_wheel_data(w: &WheelData) -> WheelData {
WheelData {
delta_mode: w.delta_mode,
delta_x: w.delta_x,
delta_y: w.delta_y,
delta_z: w.delta_x,
}
}
+248 -102
View File
@@ -1,32 +1,64 @@
use std::cell::RefCell;
use std::rc::Rc;
use dioxus_core::*;
use std::collections::HashMap;
use dioxus_native_core::layout_attributes::apply_layout_attributes;
use dioxus_native_core::node_ref::{AttributeMask, NodeMask, NodeView};
use dioxus_native_core::state::ChildDepState;
use dioxus_native_core_macro::sorted_str_slice;
use stretch2::prelude::*;
use crate::{
attributes::{apply_attributes, StyleModifer},
style::RinkStyle,
TuiModifier, TuiNode,
};
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) enum PossiblyUninitalized<T> {
Uninitalized,
Initialized(T),
}
impl<T> PossiblyUninitalized<T> {
pub fn unwrap(self) -> T {
match self {
Self::Initialized(i) => i,
_ => panic!(),
}
}
}
impl<T> Default for PossiblyUninitalized<T> {
fn default() -> Self {
Self::Uninitalized
}
}
/*
The layout system uses the lineheight as one point.
#[derive(Clone, PartialEq, Default, Debug)]
pub(crate) struct StretchLayout {
pub style: Style,
pub node: PossiblyUninitalized<Node>,
}
stretch uses fractional points, so we can rasterize if we need too, but not with characters
this means anything thats "1px" is 1 lineheight. Unfortunately, text cannot be smaller or bigger
*/
pub fn collect_layout<'a>(
layout: &mut stretch2::Stretch,
nodes: &mut HashMap<ElementId, TuiNode<'a>>,
vdom: &'a VirtualDom,
node: &'a VNode<'a>,
) {
use stretch2::prelude::*;
impl ChildDepState for StretchLayout {
type Ctx = Rc<RefCell<Stretch>>;
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();
match node {
VNode::Text(t) => {
let id = t.id.get().unwrap();
let char_len = t.text.chars().count();
/// Setup the layout
fn reduce<'a>(
&mut self,
node: NodeView,
children: impl Iterator<Item = &'a Self::DepState>,
ctx: &Self::Ctx,
) -> bool
where
Self::DepState: 'a,
{
let mut changed = false;
let mut stretch = ctx.borrow_mut();
let mut style = Style::default();
if let Some(text) = node.text() {
let char_len = text.chars().count();
let style = Style {
style = Style {
size: Size {
// characters are 1 point tall
height: Dimension::Points(1.0),
@@ -36,96 +68,210 @@ pub fn collect_layout<'a>(
},
..Default::default()
};
nodes.insert(
id,
TuiNode {
node,
block_style: RinkStyle::default(),
tui_modifier: TuiModifier::default(),
layout: layout.new_node(style, &[]).unwrap(),
},
);
}
VNode::Element(el) => {
if let PossiblyUninitalized::Initialized(n) = self.node {
if self.style != style {
stretch.set_style(n, style).unwrap();
}
} else {
self.node =
PossiblyUninitalized::Initialized(stretch.new_node(style, &[]).unwrap());
changed = true;
}
} else {
// gather up all the styles from the attribute list
let mut modifier = StyleModifer {
style: Style::default(),
tui_style: RinkStyle::default(),
tui_modifier: TuiModifier::default(),
};
// handle text modifier elements
if el.namespace.is_none() {
match el.tag {
"b" => apply_attributes("font-weight", "bold", &mut modifier),
"strong" => apply_attributes("font-weight", "bold", &mut modifier),
"u" => apply_attributes("text-decoration", "underline", &mut modifier),
"ins" => apply_attributes("text-decoration", "underline", &mut modifier),
"del" => apply_attributes("text-decoration", "line-through", &mut modifier),
"i" => apply_attributes("font-style", "italic", &mut modifier),
"em" => apply_attributes("font-style", "italic", &mut modifier),
"mark" => apply_attributes(
"background-color",
"rgba(241, 231, 64, 50%)",
&mut modifier,
),
_ => (),
for Attribute { name, value, .. } in node.attributes() {
assert!(SORTED_LAYOUT_ATTRS.binary_search(name).is_ok());
if let Some(text) = value.as_text() {
apply_layout_attributes(name, text, &mut style);
}
}
for &Attribute { name, value, .. } in el.attributes {
apply_attributes(name, value, &mut modifier);
}
// Layout the children
for child in el.children {
collect_layout(layout, nodes, vdom, child);
// the root node fills the entire area
if node.id() == ElementId(0) {
apply_layout_attributes("width", "100%", &mut style);
apply_layout_attributes("height", "100%", &mut style);
}
// Set all direct nodes as our children
let mut child_layout = vec![];
for el in el.children {
let ite = ElementIdIterator::new(vdom, el);
for node in ite {
match node {
VNode::Element(_) | VNode::Text(_) => {
//
child_layout.push(nodes[&node.mounted_id()].layout)
}
VNode::Placeholder(_) => {}
VNode::Fragment(_) => todo!(),
VNode::Component(_) => todo!(),
}
for l in children {
child_layout.push(l.node.unwrap());
}
// child_layout.push(nodes[&node.mounted_id()].layout)
if let PossiblyUninitalized::Initialized(n) = self.node {
if self.style != style {
stretch.set_style(n, style).unwrap();
}
}
nodes.insert(
node.mounted_id(),
TuiNode {
node,
block_style: modifier.tui_style,
tui_modifier: modifier.tui_modifier,
layout: layout.new_node(modifier.style, &child_layout).unwrap(),
},
);
}
VNode::Fragment(el) => {
//
for child in el.children {
collect_layout(layout, nodes, vdom, child);
if stretch.children(n).unwrap() != child_layout {
stretch.set_children(n, &child_layout).unwrap();
}
} else {
self.node = PossiblyUninitalized::Initialized(
stretch.new_node(style, &child_layout).unwrap(),
);
changed = true;
}
}
VNode::Component(sc) => {
//
let scope = vdom.get_scope(sc.scope.get().unwrap()).unwrap();
let root = scope.root_node();
collect_layout(layout, nodes, vdom, root);
if self.style != style {
changed = true;
self.style = style;
}
VNode::Placeholder(_) => {
//
}
};
changed
}
}
// these are the attributes in layout_attiributes in native-core
const SORTED_LAYOUT_ATTRS: &[&str] = &sorted_str_slice!([
"align-content",
"align-items",
"align-self",
"animation",
"animation-delay",
"animation-direction",
"animation-duration",
"animation-fill-mode",
"animation-iteration-count",
"animation-name",
"animation-play-state",
"animation-timing-function",
"backface-visibility",
"border",
"border-bottom",
"border-bottom-color",
"border-bottom-left-radius",
"border-bottom-right-radius",
"border-bottom-style",
"border-bottom-width",
"border-collapse",
"border-color",
"border-image",
"border-image-outset",
"border-image-repeat",
"border-image-slice",
"border-image-source",
"border-image-width",
"border-left",
"border-left-color",
"border-left-style",
"border-left-width",
"border-radius",
"border-right",
"border-right-color",
"border-right-style",
"border-right-width",
"border-spacing",
"border-style",
"border-top",
"border-top-color",
"border-top-left-radius",
"border-top-right-radius",
"border-top-style",
"border-top-width",
"border-width",
"bottom",
"box-shadow",
"box-sizing",
"caption-side",
"clear",
"clip",
"column-count",
"column-fill",
"column-gap",
"column-rule",
"column-rule-color",
"column-rule-style",
"column-rule-width",
"column-span",
"column-width",
"columns",
"content",
"counter-increment",
"counter-reset",
"cursor",
"direction",
"ltr",
"rtl",
"display",
"empty-cells",
"flex",
"flex-basis",
"flex-direction",
"flex-flow",
"flex-grow",
"flex-shrink",
"flex-wrap",
"float",
"height",
"justify-content",
"flex-start",
"flex-end",
"center",
"space-between",
"space-around",
"space-evenly",
"left",
"letter-spacing",
"line-height",
"list-style",
"list-style-image",
"list-style-position",
"list-style-type",
"margin",
"margin-bottom",
"margin-left",
"margin-right",
"margin-top",
"max-height",
"max-width",
"min-height",
"min-width",
"opacity",
"order",
"outline",
"outline-color",
"outline-offset",
"outline-style",
"outline-width",
"overflow",
"overflow-x",
"overflow-y",
"padding",
"padding-bottom",
"padding-left",
"padding-right",
"padding-top",
"page-break-after",
"page-break-before",
"page-break-inside",
"perspective",
"perspective-origin",
"position",
"static",
"relative",
"fixed",
"absolute",
"sticky",
"pointer-events",
"quotes",
"resize",
"right",
"tab-size",
"table-layout",
"top",
"transform",
"transform-origin",
"transform-style",
"transition",
"transition-delay",
"transition-duration",
"transition-property",
"transition-timing-function",
"vertical-align",
"visibility",
"white-space",
"width",
"word-break",
"word-spacing",
"word-wrap",
"z-index"
]);
+109 -118
View File
@@ -1,4 +1,5 @@
use anyhow::Result;
use anymap::AnyMap;
use crossterm::{
event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyModifiers},
execute,
@@ -6,35 +7,42 @@ use crossterm::{
};
use dioxus_core::exports::futures_channel::mpsc::unbounded;
use dioxus_core::*;
use dioxus_native_core::{real_dom::RealDom, state::*};
use dioxus_native_core_macro::State;
use futures::{
channel::mpsc::{UnboundedReceiver, UnboundedSender},
pin_mut, StreamExt,
};
use std::{
collections::HashMap,
io,
time::{Duration, Instant},
};
use stretch2::{
prelude::{Node, Size},
Stretch,
};
use style::RinkStyle;
use tui::{backend::CrosstermBackend, Terminal};
use layout::StretchLayout;
use std::cell::RefCell;
use std::rc::Rc;
use std::{io, time::Duration};
use stretch2::{prelude::Size, Stretch};
use style_attributes::StyleModifier;
use tui::{backend::CrosstermBackend, layout::Rect, Terminal};
mod attributes;
mod config;
mod hooks;
mod layout;
mod render;
mod style;
mod style_attributes;
mod widget;
pub use attributes::*;
pub use config::*;
pub use hooks::*;
pub use layout::*;
pub use render::*;
type Dom = RealDom<NodeState>;
type Node = dioxus_native_core::real_dom::Node<NodeState>;
#[derive(Debug, Clone, State, Default)]
struct NodeState {
#[child_dep_state(layout, RefCell<Stretch>)]
layout: StretchLayout,
// depends on attributes, the C component of it's parent and a u8 context
#[parent_dep_state(style)]
style: StyleModifier,
}
#[derive(Clone)]
pub struct TuiContext {
@@ -52,66 +60,64 @@ pub fn launch(app: Component<()>) {
pub fn launch_cfg(app: Component<()>, cfg: Config) {
let mut dom = VirtualDom::new(app);
let (tx, rx) = unbounded();
let (handler, state, register_event) = RinkInputHandler::new();
// Setup input handling
let (event_tx, event_rx) = unbounded();
let event_tx_clone = event_tx.clone();
if !cfg.headless {
std::thread::spawn(move || {
let tick_rate = Duration::from_millis(100);
let mut last_tick = Instant::now();
let tick_rate = Duration::from_millis(1000);
loop {
// poll for tick rate duration, if no events, sent tick event.
let timeout = tick_rate
.checked_sub(last_tick.elapsed())
.unwrap_or_else(|| Duration::from_secs(0));
if crossterm::event::poll(timeout).unwrap() {
if crossterm::event::poll(tick_rate).unwrap() {
// if crossterm::event::poll(timeout).unwrap() {
let evt = crossterm::event::read().unwrap();
event_tx.unbounded_send(InputEvent::UserInput(evt)).unwrap();
}
if last_tick.elapsed() >= tick_rate {
event_tx.unbounded_send(InputEvent::Tick).unwrap();
last_tick = Instant::now();
if event_tx.unbounded_send(InputEvent::UserInput(evt)).is_err() {
break;
}
}
}
});
}
let cx = dom.base_scope();
cx.provide_root_context(state);
cx.provide_root_context(TuiContext { tx: event_tx_clone });
let (handler, state) = RinkInputHandler::new(rx, cx);
let mut rdom: Dom = RealDom::new();
let mutations = dom.rebuild();
let to_update = rdom.apply_mutations(vec![mutations]);
let stretch = Rc::new(RefCell::new(Stretch::new()));
let mut any_map = AnyMap::new();
any_map.insert(stretch.clone());
let _to_rerender = rdom.update_state(&dom, to_update, any_map).unwrap();
cx.provide_root_context(state);
dom.rebuild();
render_vdom(&mut dom, event_rx, tx, handler, cfg).unwrap();
}
pub struct TuiNode<'a> {
pub layout: stretch2::node::Node,
pub block_style: RinkStyle,
pub tui_modifier: TuiModifier,
pub node: &'a VNode<'a>,
render_vdom(
&mut dom,
event_rx,
handler,
cfg,
rdom,
stretch,
register_event,
)
.unwrap();
}
fn render_vdom(
vdom: &mut VirtualDom,
mut event_reciever: UnboundedReceiver<InputEvent>,
ctx: UnboundedSender<TermEvent>,
handler: RinkInputHandler,
cfg: Config,
mut rdom: Dom,
stretch: Rc<RefCell<Stretch>>,
mut register_event: impl FnMut(crossterm::event::Event),
) -> Result<()> {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()?
.block_on(async {
/*
Get the terminal to calcualte the layout from
*/
let mut terminal = (!cfg.headless).then(|| {
enable_raw_mode().unwrap();
let mut stdout = std::io::stdout();
@@ -119,85 +125,61 @@ fn render_vdom(
let backend = CrosstermBackend::new(io::stdout());
Terminal::new(backend).unwrap()
});
if let Some(terminal) = &mut terminal {
terminal.clear().unwrap();
}
let mut to_rerender: fxhash::FxHashSet<usize> = vec![0].into_iter().collect();
let mut resized = true;
loop {
/*
-> collect all the nodes with their layout
-> solve their layout
-> render the nodes in the right place with tui/crossterm
-> wait for changes
-> resolve events
-> render the nodes in the right place with tui/crosstream
-> while rendering, apply styling
-> lazily update the layout and style based on nodes changed
use simd to compare lines for diffing?
todo: reuse the layout and node objects.
our work_with_deadline method can tell us which nodes are dirty.
todo: lazy re-rendering
*/
let mut layout = Stretch::new();
let mut nodes = HashMap::new();
let root_node = vdom.base_scope().root_node();
layout::collect_layout(&mut layout, &mut nodes, vdom, root_node);
/*
Compute the layout given the terminal size
*/
let node_id = root_node.try_mounted_id().unwrap();
let root_layout = nodes[&node_id].layout;
let mut events = Vec::new();
if !to_rerender.is_empty() || resized {
resized = false;
fn resize(dims: Rect, stretch: &mut Stretch, rdom: &Dom) {
let width = dims.width;
let height = dims.height;
let root_node = rdom[0].state.layout.node.unwrap();
fn resize(dims: tui::layout::Rect, stretch: &mut Stretch, root_layout: Node) {
let width = dims.width;
let height = dims.height;
stretch
.compute_layout(
root_layout,
Size {
width: stretch2::prelude::Number::Defined((width - 1) as f32),
height: stretch2::prelude::Number::Defined((height - 1) as f32),
stretch
.compute_layout(
root_node,
Size {
width: stretch2::prelude::Number::Defined((width - 1) as f32),
height: stretch2::prelude::Number::Defined((height - 1) as f32),
},
)
.unwrap();
}
if let Some(terminal) = &mut terminal {
terminal.draw(|frame| {
// size is guaranteed to not change when rendering
resize(frame.size(), &mut stretch.borrow_mut(), &rdom);
let root = &rdom[0];
render::render_vnode(frame, &stretch.borrow(), &rdom, root, cfg);
})?;
} else {
resize(
Rect {
x: 0,
y: 0,
width: 300,
height: 300,
},
)
.unwrap();
}
if let Some(terminal) = &mut terminal {
terminal.draw(|frame| {
// size is guaranteed to not change when rendering
resize(frame.size(), &mut layout, root_layout);
// resolve events before rendering
events = handler.get_events(vdom, &layout, &mut nodes, root_node);
render::render_vnode(
frame,
&layout,
&mut nodes,
vdom,
root_node,
&RinkStyle::default(),
cfg,
&mut stretch.borrow_mut(),
&rdom,
);
assert!(nodes.is_empty());
})?;
} else {
resize(
tui::layout::Rect {
x: 0,
y: 0,
width: 100,
height: 100,
},
&mut layout,
root_layout,
);
}
for e in events {
vdom.handle_message(SchedulerMsg::Event(e));
}
}
use futures::future::{select, Either};
@@ -220,20 +202,32 @@ fn render_vdom(
break;
}
}
TermEvent::Resize(_, _) | TermEvent::Mouse(_) => {}
TermEvent::Resize(_, _) => resized = true,
TermEvent::Mouse(_) => {}
},
InputEvent::Tick => {} // tick
InputEvent::Close => break,
};
if let InputEvent::UserInput(evt) = evt.unwrap() {
ctx.unbounded_send(evt).unwrap();
register_event(evt);
}
}
}
}
vdom.work_with_deadline(|| false);
{
let evts = handler.get_events(&stretch.borrow(), &mut rdom);
for e in evts {
vdom.handle_message(SchedulerMsg::Event(e));
}
let mutations = vdom.work_with_deadline(|| false);
// updates the dom's nodes
let to_update = rdom.apply_mutations(mutations);
// update the style and layout
let mut any_map = AnyMap::new();
any_map.insert(stretch.clone());
to_rerender = rdom.update_state(vdom, to_update, any_map).unwrap();
}
}
if let Some(terminal) = &mut terminal {
@@ -252,8 +246,5 @@ fn render_vdom(
enum InputEvent {
UserInput(TermEvent),
Tick,
#[allow(dead_code)]
Close,
}
+35 -61
View File
@@ -1,5 +1,5 @@
use dioxus_core::*;
use std::{collections::HashMap, io::Stdout};
use dioxus_native_core::layout_attributes::UnitSystem;
use std::io::Stdout;
use stretch2::{
geometry::Point,
prelude::{Layout, Size},
@@ -9,52 +9,33 @@ use tui::{backend::CrosstermBackend, layout::Rect};
use crate::{
style::{RinkColor, RinkStyle},
style_attributes::{BorderEdge, BorderStyle},
widget::{RinkBuffer, RinkCell, RinkWidget, WidgetWithContext},
BorderEdge, BorderStyle, Config, TuiNode, UnitSystem,
Config, Dom, Node,
};
const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5];
pub fn render_vnode<'a>(
pub(crate) fn render_vnode(
frame: &mut tui::Frame<CrosstermBackend<Stdout>>,
layout: &Stretch,
layouts: &mut HashMap<ElementId, TuiNode<'a>>,
vdom: &'a VirtualDom,
node: &'a VNode<'a>,
// this holds the accumulated syle state for styled text rendering
style: &RinkStyle,
rdom: &Dom,
node: &Node,
cfg: Config,
) {
match node {
VNode::Fragment(f) => {
for child in f.children {
render_vnode(frame, layout, layouts, vdom, child, style, cfg);
}
return;
}
use dioxus_native_core::real_dom::NodeType;
VNode::Component(vcomp) => {
let idx = vcomp.scope.get().unwrap();
let new_node = vdom.get_scope(idx).unwrap().root_node();
render_vnode(frame, layout, layouts, vdom, new_node, style, cfg);
return;
}
VNode::Placeholder(_) => return,
VNode::Element(_) | VNode::Text(_) => {}
if let NodeType::Placeholder = &node.node_type {
return;
}
let id = node.try_mounted_id().unwrap();
let mut node = layouts.remove(&id).unwrap();
let Layout { location, size, .. } = layout.layout(node.layout).unwrap();
let Layout { location, size, .. } = layout.layout(node.state.layout.node.unwrap()).unwrap();
let Point { x, y } = location;
let Size { width, height } = size;
match node.node {
VNode::Text(t) => {
match &node.node_type {
NodeType::Text { text } => {
#[derive(Default)]
struct Label<'a> {
text: &'a str,
@@ -67,14 +48,14 @@ pub fn render_vnode<'a>(
let mut new_cell = RinkCell::default();
new_cell.set_style(self.style);
new_cell.symbol = c.to_string();
buf.set(area.left() + i as u16, area.top(), &new_cell);
buf.set(area.left() + i as u16, area.top(), new_cell);
}
}
}
let label = Label {
text: t.text,
style: *style,
text,
style: node.state.style.core,
};
let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16);
@@ -83,30 +64,23 @@ pub fn render_vnode<'a>(
frame.render_widget(WidgetWithContext::new(label, cfg), area);
}
}
VNode::Element(el) => {
NodeType::Element { children, .. } => {
let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16);
let mut new_style = node.block_style.merge(*style);
node.block_style = new_style;
// the renderer will panic if a node is rendered out of range even if the size is zero
if area.width > 0 && area.height > 0 {
frame.render_widget(WidgetWithContext::new(node, cfg), area);
}
// do not pass background color to children
new_style.bg = None;
for el in el.children {
render_vnode(frame, layout, layouts, vdom, el, &new_style, cfg);
for c in children {
render_vnode(frame, layout, rdom, &rdom[c.0], cfg);
}
}
VNode::Fragment(_) => todo!(),
VNode::Component(_) => todo!(),
VNode::Placeholder(_) => todo!(),
NodeType::Placeholder => unreachable!(),
}
}
impl<'a> RinkWidget for TuiNode<'a> {
impl RinkWidget for &Node {
fn render(self, area: Rect, mut buf: RinkBuffer<'_>) {
use tui::symbols::line::*;
@@ -176,7 +150,7 @@ impl<'a> RinkWidget for TuiNode<'a> {
buf.set(
(current[0] + pos[0] as i32) as u16,
(current[1] + pos[1] as i32) as u16,
&new_cell,
new_cell,
);
}
@@ -270,8 +244,8 @@ impl<'a> RinkWidget for TuiNode<'a> {
fn get_radius(border: &BorderEdge, area: Rect) -> f32 {
match border.style {
BorderStyle::HIDDEN => 0.0,
BorderStyle::NONE => 0.0,
BorderStyle::Hidden => 0.0,
BorderStyle::None => 0.0,
_ => match border.radius {
UnitSystem::Percent(p) => p * area.width as f32 / 100.0,
UnitSystem::Point(p) => p,
@@ -290,14 +264,14 @@ impl<'a> RinkWidget for TuiNode<'a> {
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.block_style.bg {
if let Some(c) = self.state.style.core.bg {
new_cell.bg = c;
}
buf.set(x, y, &new_cell);
buf.set(x, y, new_cell);
}
}
let borders = self.tui_modifier.borders;
let borders = &self.state.style.modifier.borders;
let last_edge = &borders.left;
let current_edge = &borders.top;
@@ -314,14 +288,14 @@ impl<'a> RinkWidget for TuiNode<'a> {
(last_r * RADIUS_MULTIPLIER[0]) as u16,
(last_r * RADIUS_MULTIPLIER[1]) as u16,
];
let color = current_edge.color.or(self.block_style.fg);
let color = current_edge.color.or(self.state.style.core.fg);
let mut new_cell = RinkCell::default();
if let Some(c) = color {
new_cell.fg = c;
}
for x in (area.left() + last_radius[0] + 1)..(area.right() - radius[0]) {
new_cell.symbol = symbols.horizontal.to_string();
buf.set(x, area.top(), &new_cell);
buf.set(x, area.top(), new_cell.clone());
}
draw_arc(
[area.right() - radius[0] - 1, area.top() + radius[1]],
@@ -349,14 +323,14 @@ impl<'a> RinkWidget for TuiNode<'a> {
(last_r * RADIUS_MULTIPLIER[0]) as u16,
(last_r * RADIUS_MULTIPLIER[1]) as u16,
];
let color = current_edge.color.or(self.block_style.fg);
let color = current_edge.color.or(self.state.style.core.fg);
let mut new_cell = RinkCell::default();
if let Some(c) = color {
new_cell.fg = c;
}
for y in (area.top() + last_radius[1] + 1)..(area.bottom() - radius[1]) {
new_cell.symbol = symbols.vertical.to_string();
buf.set(area.right() - 1, y, &new_cell);
buf.set(area.right() - 1, y, new_cell.clone());
}
draw_arc(
[area.right() - radius[0] - 1, area.bottom() - radius[1] - 1],
@@ -384,14 +358,14 @@ impl<'a> RinkWidget for TuiNode<'a> {
(last_r * RADIUS_MULTIPLIER[0]) as u16,
(last_r * RADIUS_MULTIPLIER[1]) as u16,
];
let color = current_edge.color.or(self.block_style.fg);
let color = current_edge.color.or(self.state.style.core.fg);
let mut new_cell = RinkCell::default();
if let Some(c) = color {
new_cell.fg = c;
}
for x in (area.left() + radius[0])..(area.right() - last_radius[0] - 1) {
new_cell.symbol = symbols.horizontal.to_string();
buf.set(x, area.bottom() - 1, &new_cell);
buf.set(x, area.bottom() - 1, new_cell.clone());
}
draw_arc(
[area.left() + radius[0], area.bottom() - radius[1] - 1],
@@ -419,14 +393,14 @@ impl<'a> RinkWidget for TuiNode<'a> {
(last_r * RADIUS_MULTIPLIER[0]) as u16,
(last_r * RADIUS_MULTIPLIER[1]) as u16,
];
let color = current_edge.color.or(self.block_style.fg);
let color = current_edge.color.or(self.state.style.core.fg);
let mut new_cell = RinkCell::default();
if let Some(c) = color {
new_cell.fg = c;
}
for y in (area.top() + radius[1])..(area.bottom() - last_radius[1] - 1) {
new_cell.symbol = symbols.vertical.to_string();
buf.set(area.left(), y, &new_cell);
buf.set(area.left(), y, new_cell.clone());
}
draw_arc(
[area.left() + radius[0], area.top() + radius[1]],
+46 -42
View File
@@ -4,17 +4,17 @@ use tui::style::{Color, Modifier, Style};
use crate::RenderingMode;
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct RinkColor {
pub color: Color,
pub alpha: f32,
pub alpha: u8,
}
impl Default for RinkColor {
fn default() -> Self {
Self {
color: Color::Black,
alpha: 0.0,
alpha: 0,
}
}
}
@@ -23,22 +23,17 @@ impl RinkColor {
pub fn blend(self, other: Color) -> Color {
if self.color == Color::Reset {
Color::Reset
} else if self.alpha == 0.0 {
} else if self.alpha == 0 {
other
} else {
let [sr, sg, sb] = to_rgb(self.color);
let [or, og, ob] = to_rgb(other);
let (sr, sg, sb, sa) = (
sr as f32 / 255.0,
sg as f32 / 255.0,
sb as f32 / 255.0,
self.alpha,
);
let (or, og, ob) = (or as f32 / 255.0, og as f32 / 255.0, ob as f32 / 255.0);
let [sr, sg, sb] = to_rgb(self.color).map(|e| e as u16);
let [or, og, ob] = to_rgb(other).map(|e| e as u16);
let sa = self.alpha as u16;
let rsa = 255 - sa;
Color::Rgb(
(255.0 * (sr * sa + or * (1.0 - sa))) as u8,
(255.0 * (sg * sa + og * (1.0 - sa))) as u8,
(255.0 * (sb * sa + ob * (1.0 - sa))) as u8,
((sr * sa + or * rsa) / 255) as u8,
((sg * sa + og * rsa) / 255) as u8,
((sb * sa + ob * rsa) / 255) as u8,
)
}
}
@@ -151,75 +146,75 @@ impl FromStr for RinkColor {
match color {
"red" => Ok(RinkColor {
color: Color::Red,
alpha: 1.0,
alpha: 255,
}),
"black" => Ok(RinkColor {
color: Color::Black,
alpha: 1.0,
alpha: 255,
}),
"green" => Ok(RinkColor {
color: Color::Green,
alpha: 1.0,
alpha: 255,
}),
"yellow" => Ok(RinkColor {
color: Color::Yellow,
alpha: 1.0,
alpha: 255,
}),
"blue" => Ok(RinkColor {
color: Color::Blue,
alpha: 1.0,
alpha: 255,
}),
"magenta" => Ok(RinkColor {
color: Color::Magenta,
alpha: 1.0,
alpha: 255,
}),
"cyan" => Ok(RinkColor {
color: Color::Cyan,
alpha: 1.0,
alpha: 255,
}),
"gray" => Ok(RinkColor {
color: Color::Gray,
alpha: 1.0,
alpha: 255,
}),
"darkgray" => Ok(RinkColor {
color: Color::DarkGray,
alpha: 1.0,
alpha: 255,
}),
// light red does not exist
"orangered" => Ok(RinkColor {
color: Color::LightRed,
alpha: 1.0,
alpha: 255,
}),
"lightgreen" => Ok(RinkColor {
color: Color::LightGreen,
alpha: 1.0,
alpha: 255,
}),
"lightyellow" => Ok(RinkColor {
color: Color::LightYellow,
alpha: 1.0,
alpha: 255,
}),
"lightblue" => Ok(RinkColor {
color: Color::LightBlue,
alpha: 1.0,
alpha: 255,
}),
// light magenta does not exist
"orchid" => Ok(RinkColor {
color: Color::LightMagenta,
alpha: 1.0,
alpha: 255,
}),
"lightcyan" => Ok(RinkColor {
color: Color::LightCyan,
alpha: 1.0,
alpha: 255,
}),
"white" => Ok(RinkColor {
color: Color::White,
alpha: 1.0,
alpha: 255,
}),
_ => {
if color.len() == 7 && color.starts_with('#') {
parse_hex(color).map(|c| RinkColor {
color: c,
alpha: 1.0,
alpha: 255,
})
} else if let Some(stripped) = color.strip_prefix("rgb(") {
let color_values = stripped.trim_end_matches(')');
@@ -234,7 +229,7 @@ impl FromStr for RinkColor {
} else {
parse_rgb(color_values).map(|c| RinkColor {
color: c,
alpha: 1.0,
alpha: 255,
})
}
} else if let Some(stripped) = color.strip_prefix("rgba(") {
@@ -243,14 +238,17 @@ impl FromStr for RinkColor {
let (rgb_values, alpha) =
color_values.rsplit_once(',').ok_or(ParseColorError)?;
if let Ok(a) = parse_value(alpha, 1.0, 1.0) {
parse_rgb(rgb_values).map(|c| RinkColor { color: c, alpha: a })
parse_rgb(rgb_values).map(|c| RinkColor {
color: c,
alpha: (a * 255.0) as u8,
})
} else {
Err(ParseColorError)
}
} else {
parse_rgb(color_values).map(|c| RinkColor {
color: c,
alpha: 1.0,
alpha: 255,
})
}
} else if let Some(stripped) = color.strip_prefix("hsl(") {
@@ -259,14 +257,17 @@ impl FromStr for RinkColor {
let (rgb_values, alpha) =
color_values.rsplit_once(',').ok_or(ParseColorError)?;
if let Ok(a) = parse_value(alpha, 1.0, 1.0) {
parse_hsl(rgb_values).map(|c| RinkColor { color: c, alpha: a })
parse_hsl(rgb_values).map(|c| RinkColor {
color: c,
alpha: (a * 255.0) as u8,
})
} else {
Err(ParseColorError)
}
} else {
parse_hsl(color_values).map(|c| RinkColor {
color: c,
alpha: 1.0,
alpha: 255,
})
}
} else if let Some(stripped) = color.strip_prefix("hsla(") {
@@ -275,14 +276,17 @@ impl FromStr for RinkColor {
let (rgb_values, alpha) =
color_values.rsplit_once(',').ok_or(ParseColorError)?;
if let Ok(a) = parse_value(alpha, 1.0, 1.0) {
parse_hsl(rgb_values).map(|c| RinkColor { color: c, alpha: a })
parse_hsl(rgb_values).map(|c| RinkColor {
color: c,
alpha: (a * 255.0) as u8,
})
} else {
Err(ParseColorError)
}
} else {
parse_hsl(color_values).map(|c| RinkColor {
color: c,
alpha: 1.0,
alpha: 255,
})
}
} else {
@@ -393,7 +397,7 @@ fn rgb_to_ansi() {
}
}
#[derive(Clone, Copy)]
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct RinkStyle {
pub fg: Option<RinkColor>,
pub bg: Option<RinkColor>,
@@ -406,7 +410,7 @@ impl Default for RinkStyle {
Self {
fg: Some(RinkColor {
color: Color::White,
alpha: 1.0,
alpha: 255,
}),
bg: None,
add_modifier: Modifier::empty(),
+797
View File
@@ -0,0 +1,797 @@
/*
- [ ] pub display: Display,
- [x] pub position_type: PositionType, --> kinda, stretch doesnt support everything
- [ ] pub direction: Direction,
- [x] pub flex_direction: FlexDirection,
- [x] pub flex_wrap: FlexWrap,
- [x] pub flex_grow: f32,
- [x] pub flex_shrink: f32,
- [x] pub flex_basis: Dimension,
- [x] pub overflow: Overflow, ---> kinda implemented... stretch doesnt have support for directional overflow
- [x] pub align_items: AlignItems,
- [x] pub align_self: AlignSelf,
- [x] pub align_content: AlignContent,
- [x] pub margin: Rect<Dimension>,
- [x] pub padding: Rect<Dimension>,
- [x] pub justify_content: JustifyContent,
- [ ] pub position: Rect<Dimension>,
- [x] pub border: Rect<Dimension>,
- [ ] pub size: Size<Dimension>, ----> ??? seems to only be relevant for input?
- [ ] pub min_size: Size<Dimension>,
- [ ] pub max_size: Size<Dimension>,
- [ ] pub aspect_ratio: Number,
*/
use dioxus_core::Attribute;
use dioxus_native_core::{
layout_attributes::{parse_value, UnitSystem},
node_ref::{AttributeMask, NodeMask, NodeView},
state::ParentDepState,
};
use dioxus_native_core_macro::sorted_str_slice;
use crate::style::{RinkColor, RinkStyle};
#[derive(Default, Clone, PartialEq, Debug)]
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();
fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, _: &Self::Ctx) -> 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() {
match tag {
"b" => apply_style_attributes("font-weight", "bold", &mut new),
"strong" => apply_style_attributes("font-weight", "bold", &mut new),
"u" => apply_style_attributes("text-decoration", "underline", &mut new),
"ins" => apply_style_attributes("text-decoration", "underline", &mut new),
"del" => apply_style_attributes("text-decoration", "line-through", &mut new),
"i" => apply_style_attributes("font-style", "italic", &mut new),
"em" => apply_style_attributes("font-style", "italic", &mut new),
"mark" => {
apply_style_attributes("background-color", "rgba(241, 231, 64, 50%)", self)
}
_ => (),
}
}
}
// gather up all the styles from the attribute list
for Attribute { name, value, .. } in node.attributes() {
if let Some(text) = value.as_text() {
apply_style_attributes(name, text, &mut new);
}
}
// keep the text styling from the parent element
if let Some(parent) = parent {
let mut new_style = new.core.merge(parent.core);
new_style.bg = new.core.bg;
new.core = new_style;
}
if &mut new != self {
*self = new;
true
} else {
false
}
}
}
#[derive(Default, Clone, PartialEq, Debug)]
pub struct TuiModifier {
pub borders: Borders,
}
#[derive(Default, Clone, PartialEq, Debug)]
pub struct Borders {
pub top: BorderEdge,
pub right: BorderEdge,
pub bottom: BorderEdge,
pub left: BorderEdge,
}
impl Borders {
fn slice(&mut self) -> [&mut BorderEdge; 4] {
[
&mut self.top,
&mut self.right,
&mut self.bottom,
&mut self.left,
]
}
}
#[derive(Clone, PartialEq, Debug)]
pub struct BorderEdge {
pub color: Option<RinkColor>,
pub style: BorderStyle,
pub width: UnitSystem,
pub radius: UnitSystem,
}
impl Default for BorderEdge {
fn default() -> Self {
Self {
color: None,
style: BorderStyle::None,
width: UnitSystem::Point(0.0),
radius: UnitSystem::Point(0.0),
}
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum BorderStyle {
Dotted,
Dashed,
Solid,
Double,
Groove,
Ridge,
Inset,
Outset,
Hidden,
None,
}
impl BorderStyle {
pub fn symbol_set(&self) -> Option<tui::symbols::line::Set> {
use tui::symbols::line::*;
const DASHED: Set = Set {
horizontal: "╌",
vertical: "╎",
..NORMAL
};
const DOTTED: Set = Set {
horizontal: "┈",
vertical: "┊",
..NORMAL
};
match self {
BorderStyle::Dotted => Some(DOTTED),
BorderStyle::Dashed => Some(DASHED),
BorderStyle::Solid => Some(NORMAL),
BorderStyle::Double => Some(DOUBLE),
BorderStyle::Groove => Some(NORMAL),
BorderStyle::Ridge => Some(NORMAL),
BorderStyle::Inset => Some(NORMAL),
BorderStyle::Outset => Some(NORMAL),
BorderStyle::Hidden => None,
BorderStyle::None => None,
}
}
}
/// applies the entire html namespace defined in dioxus-html
pub fn apply_style_attributes(
//
name: &str,
value: &str,
style: &mut StyleModifier,
) {
match name {
"animation"
| "animation-delay"
| "animation-direction"
| "animation-duration"
| "animation-fill-mode"
| "animation-iteration-count"
| "animation-name"
| "animation-play-state"
| "animation-timing-function" => apply_animation(name, value, style),
"backface-visibility" => {}
"background"
| "background-attachment"
| "background-clip"
| "background-color"
| "background-image"
| "background-origin"
| "background-position"
| "background-repeat"
| "background-size" => apply_background(name, value, style),
"border"
| "border-bottom"
| "border-bottom-color"
| "border-bottom-left-radius"
| "border-bottom-right-radius"
| "border-bottom-style"
| "border-bottom-width"
| "border-collapse"
| "border-color"
| "border-image"
| "border-image-outset"
| "border-image-repeat"
| "border-image-slice"
| "border-image-source"
| "border-image-width"
| "border-left"
| "border-left-color"
| "border-left-style"
| "border-left-width"
| "border-radius"
| "border-right"
| "border-right-color"
| "border-right-style"
| "border-right-width"
| "border-spacing"
| "border-style"
| "border-top"
| "border-top-color"
| "border-top-left-radius"
| "border-top-right-radius"
| "border-top-style"
| "border-top-width"
| "border-width" => apply_border(name, value, style),
"bottom" => {}
"box-shadow" => {}
"box-sizing" => {}
"caption-side" => {}
"clear" => {}
"clip" => {}
"color" => {
if let Ok(c) = value.parse() {
style.core.fg.replace(c);
}
}
"columns" => {}
"content" => {}
"counter-increment" => {}
"counter-reset" => {}
"cursor" => {}
"empty-cells" => {}
"float" => {}
"font" | "font-family" | "font-size" | "font-size-adjust" | "font-stretch"
| "font-style" | "font-variant" | "font-weight" => apply_font(name, value, style),
"letter-spacing" => {}
"line-height" => {}
"list-style" | "list-style-image" | "list-style-position" | "list-style-type" => {}
"opacity" => {}
"order" => {}
"outline" => {}
"outline-color" | "outline-offset" | "outline-style" | "outline-width" => {}
"page-break-after" | "page-break-before" | "page-break-inside" => {}
"perspective" | "perspective-origin" => {}
"pointer-events" => {}
"quotes" => {}
"resize" => {}
"tab-size" => {}
"table-layout" => {}
"text-align"
| "text-align-last"
| "text-decoration"
| "text-decoration-color"
| "text-decoration-line"
| "text-decoration-style"
| "text-indent"
| "text-justify"
| "text-overflow"
| "text-shadow"
| "text-transform" => apply_text(name, value, style),
"transition"
| "transition-delay"
| "transition-duration"
| "transition-property"
| "transition-timing-function" => apply_transition(name, value, style),
"visibility" => {}
"white-space" => {}
_ => {}
}
}
fn apply_background(name: &str, value: &str, style: &mut StyleModifier) {
match name {
"background-color" => {
if let Ok(c) = value.parse() {
style.core.bg.replace(c);
}
}
"background" => {}
"background-attachment" => {}
"background-clip" => {}
"background-image" => {}
"background-origin" => {}
"background-position" => {}
"background-repeat" => {}
"background-size" => {}
_ => {}
}
}
fn apply_border(name: &str, value: &str, style: &mut StyleModifier) {
fn parse_border_style(v: &str) -> BorderStyle {
match v {
"dotted" => BorderStyle::Dotted,
"dashed" => BorderStyle::Dashed,
"solid" => BorderStyle::Solid,
"double" => BorderStyle::Double,
"groove" => BorderStyle::Groove,
"ridge" => BorderStyle::Ridge,
"inset" => BorderStyle::Inset,
"outset" => BorderStyle::Outset,
"none" => BorderStyle::None,
"hidden" => BorderStyle::Hidden,
_ => todo!(),
}
}
match name {
"border" => {}
"border-bottom" => {}
"border-bottom-color" => {
if let Ok(c) = value.parse() {
style.modifier.borders.bottom.color = Some(c);
}
}
"border-bottom-left-radius" => {
if let Some(v) = parse_value(value) {
style.modifier.borders.left.radius = v;
}
}
"border-bottom-right-radius" => {
if let Some(v) = parse_value(value) {
style.modifier.borders.right.radius = v;
}
}
"border-bottom-style" => style.modifier.borders.bottom.style = parse_border_style(value),
"border-bottom-width" => {
if let Some(v) = parse_value(value) {
style.modifier.borders.bottom.width = v;
}
}
"border-collapse" => {}
"border-color" => {
let values: Vec<_> = value.split(' ').collect();
if values.len() == 1 {
if let Ok(c) = values[0].parse() {
style
.modifier
.borders
.slice()
.iter_mut()
.for_each(|b| b.color = Some(c));
}
} else {
for (v, b) in values
.into_iter()
.zip(style.modifier.borders.slice().iter_mut())
{
if let Ok(c) = v.parse() {
b.color = Some(c);
}
}
}
}
"border-image" => {}
"border-image-outset" => {}
"border-image-repeat" => {}
"border-image-slice" => {}
"border-image-source" => {}
"border-image-width" => {}
"border-left" => {}
"border-left-color" => {
if let Ok(c) = value.parse() {
style.modifier.borders.left.color = Some(c);
}
}
"border-left-style" => style.modifier.borders.left.style = parse_border_style(value),
"border-left-width" => {
if let Some(v) = parse_value(value) {
style.modifier.borders.left.width = v;
}
}
"border-radius" => {
let values: Vec<_> = value.split(' ').collect();
if values.len() == 1 {
if let Some(r) = parse_value(values[0]) {
style
.modifier
.borders
.slice()
.iter_mut()
.for_each(|b| b.radius = r);
}
} else {
for (v, b) in values
.into_iter()
.zip(style.modifier.borders.slice().iter_mut())
{
if let Some(r) = parse_value(v) {
b.radius = r;
}
}
}
}
"border-right" => {}
"border-right-color" => {
if let Ok(c) = value.parse() {
style.modifier.borders.right.color = Some(c);
}
}
"border-right-style" => style.modifier.borders.right.style = parse_border_style(value),
"border-right-width" => {
if let Some(v) = parse_value(value) {
style.modifier.borders.right.width = v;
}
}
"border-spacing" => {}
"border-style" => {
let values: Vec<_> = value.split(' ').collect();
if values.len() == 1 {
let border_style = parse_border_style(values[0]);
style
.modifier
.borders
.slice()
.iter_mut()
.for_each(|b| b.style = border_style);
} else {
for (v, b) in values
.into_iter()
.zip(style.modifier.borders.slice().iter_mut())
{
b.style = parse_border_style(v);
}
}
}
"border-top" => {}
"border-top-color" => {
if let Ok(c) = value.parse() {
style.modifier.borders.top.color = Some(c);
}
}
"border-top-left-radius" => {
if let Some(v) = parse_value(value) {
style.modifier.borders.left.radius = v;
}
}
"border-top-right-radius" => {
if let Some(v) = parse_value(value) {
style.modifier.borders.right.radius = v;
}
}
"border-top-style" => style.modifier.borders.top.style = parse_border_style(value),
"border-top-width" => {
if let Some(v) = parse_value(value) {
style.modifier.borders.top.width = v;
}
}
"border-width" => {
let values: Vec<_> = value.split(' ').collect();
if values.len() == 1 {
if let Some(w) = parse_value(values[0]) {
style
.modifier
.borders
.slice()
.iter_mut()
.for_each(|b| b.width = w);
}
} else {
for (v, width) in values
.into_iter()
.zip(style.modifier.borders.slice().iter_mut())
{
if let Some(w) = parse_value(v) {
width.width = w;
}
}
}
}
_ => (),
}
}
fn apply_animation(name: &str, _value: &str, _style: &mut StyleModifier) {
match name {
"animation" => {}
"animation-delay" => {}
"animation-direction =>{}" => {}
"animation-duration" => {}
"animation-fill-mode" => {}
"animation-itera =>{}tion-count" => {}
"animation-name" => {}
"animation-play-state" => {}
"animation-timing-function" => {}
_ => {}
}
}
fn apply_font(name: &str, value: &str, style: &mut StyleModifier) {
use tui::style::Modifier;
match name {
"font" => (),
"font-family" => (),
"font-size" => (),
"font-size-adjust" => (),
"font-stretch" => (),
"font-style" => match value {
"italic" => style.core = style.core.add_modifier(Modifier::ITALIC),
"oblique" => style.core = style.core.add_modifier(Modifier::ITALIC),
_ => (),
},
"font-variant" => todo!(),
"font-weight" => match value {
"bold" => style.core = style.core.add_modifier(Modifier::BOLD),
"normal" => style.core = style.core.remove_modifier(Modifier::BOLD),
_ => (),
},
_ => (),
}
}
fn apply_text(name: &str, value: &str, style: &mut StyleModifier) {
use tui::style::Modifier;
match name {
"text-align" => todo!(),
"text-align-last" => todo!(),
"text-decoration" | "text-decoration-line" => {
for v in value.split(' ') {
match v {
"line-through" => style.core = style.core.add_modifier(Modifier::CROSSED_OUT),
"underline" => style.core = style.core.add_modifier(Modifier::UNDERLINED),
_ => (),
}
}
}
"text-decoration-color" => todo!(),
"text-decoration-style" => todo!(),
"text-indent" => todo!(),
"text-justify" => todo!(),
"text-overflow" => todo!(),
"text-shadow" => todo!(),
"text-transform" => todo!(),
_ => todo!(),
}
}
fn apply_transition(_name: &str, _value: &str, _style: &mut StyleModifier) {
todo!()
}
const SORTED_STYLE_ATTRS: &[&str] = &sorted_str_slice!([
"animation",
"animation-delay",
"animation-direction",
"animation-duration",
"animation-fill-mode",
"animation-iteration-count",
"animation-name",
"animation-play-state",
"animation-timing-function",
"backface-visibility",
"background",
"background-attachment",
"background-clip",
"background-color",
"background-image",
"background-origin",
"background-position",
"background-repeat",
"background-size",
"border",
"border-bottom",
"border-bottom-color",
"border-bottom-left-radius",
"border-bottom-right-radius",
"border-bottom-style",
"border-bottom-width",
"border-collapse",
"border-color",
"border-image",
"border-image-outset",
"border-image-repeat",
"border-image-slice",
"border-image-source",
"border-image-width",
"border-left",
"border-left-color",
"border-left-style",
"border-left-width",
"border-radius",
"border-right",
"border-right-color",
"border-right-style",
"border-right-width",
"border-spacing",
"border-style",
"border-top",
"border-top-color",
"border-top-left-radius",
"border-top-right-radius",
"border-top-style",
"border-top-width",
"border-width",
"bottom",
"box-shadow",
"box-sizing",
"caption-side",
"clear",
"clip",
"color",
"columns",
"content",
"counter-increment",
"counter-reset",
"cursor",
"empty-cells",
"float",
"font",
"font-family",
"font-size",
"font-size-adjust",
"font-stretch",
"font-style",
"font-variant",
"font-weight",
"letter-spacing",
"line-height",
"list-style",
"list-style-image",
"list-style-position",
"list-style-type",
"opacity",
"order",
"outline",
"outline-color",
"outline-offset",
"outline-style",
"outline-width",
"page-break-after",
"page-break-before",
"page-break-inside",
"perspective",
"perspective-origin",
"pointer-events",
"quotes",
"resize",
"tab-size",
"table-layout",
"text-align",
"text-align-last",
"text-decoration",
"text-decoration-color",
"text-decoration-line",
"text-decoration-style",
"text-indent",
"text-justify",
"text-overflow",
"text-shadow",
"text-transform",
"transition",
"transition-delay",
"transition-duration",
"transition-property",
"transition-timing-function",
"visibility",
"white-space",
"background-color",
"background",
"background-attachment",
"background-clip",
"background-image",
"background-origin",
"background-position",
"background-repeat",
"background-size",
"dotted",
"dashed",
"solid",
"double",
"groove",
"ridge",
"inset",
"outset",
"none",
"hidden",
"border",
"border-bottom",
"border-bottom-color",
"border-bottom-left-radius",
"border-bottom-right-radius",
"border-bottom-style",
"border-bottom-width",
"border-collapse",
"border-color",
"border-image",
"border-image-outset",
"border-image-repeat",
"border-image-slice",
"border-image-source",
"border-image-width",
"border-left",
"border-left-color",
"border-left-style",
"border-left-width",
"border-radius",
"border-right",
"border-right-color",
"border-right-style",
"border-right-width",
"border-spacing",
"border-style",
"border-top",
"border-top-color",
"border-top-left-radius",
"border-top-right-radius",
"border-top-style",
"border-top-width",
"border-width",
"animation",
"animation-delay",
"animation-direction",
"animation-duration",
"animation-fill-mode",
"animation-itera ",
"animation-name",
"animation-play-state",
"animation-timing-function",
"font",
"font-family",
"font-size",
"font-size-adjust",
"font-stretch",
"font-style",
"italic",
"oblique",
"font-variant",
"font-weight",
"bold",
"normal",
"text-align",
"text-align-last",
"text-decoration",
"text-decoration-line",
"line-through",
"underline",
"text-decoration-color",
"text-decoration-style",
"text-indent",
"text-justify",
"text-overflow",
"text-shadow",
"text-transform"
]);
+8 -4
View File
@@ -20,7 +20,11 @@ impl<'a> RinkBuffer<'a> {
Self { buf, cfg }
}
pub fn set(&mut self, x: u16, y: u16, new: &RinkCell) {
pub fn set(&mut self, x: u16, y: u16, new: RinkCell) {
let area = self.buf.area();
if x < area.x || x > area.width || y < area.y || y > area.height {
panic!("({x}, {y}) is not in {area:?}");
}
let mut cell = self.buf.get_mut(x, y);
cell.bg = convert(self.cfg.rendering_mode, new.bg.blend(cell.bg));
if new.symbol.is_empty() {
@@ -30,7 +34,7 @@ impl<'a> RinkBuffer<'a> {
}
} else {
cell.modifier = new.modifier;
cell.symbol = new.symbol.clone();
cell.symbol = new.symbol;
cell.fg = convert(self.cfg.rendering_mode, new.fg.blend(cell.bg));
}
}
@@ -71,11 +75,11 @@ impl Default for RinkCell {
symbol: "".to_string(),
fg: RinkColor {
color: Color::Rgb(0, 0, 0),
alpha: 0.0,
alpha: 0,
},
bg: RinkColor {
color: Color::Rgb(0, 0, 0),
alpha: 0.0,
alpha: 0,
},
modifier: Modifier::empty(),
}
+5 -5
View File
@@ -1,6 +1,6 @@
[package]
name = "dioxus-web"
version = "0.2.0"
version = "0.2.1"
authors = ["Jonathan Kelley"]
edition = "2018"
description = "Dioxus VirtualDOM renderer for the web browser using websys"
@@ -11,10 +11,10 @@ documentation = "https://dioxuslabs.com"
keywords = ["dom", "ui", "gui", "react", "wasm"]
[dependencies]
dioxus-core = { path = "../core", version = "^0.2.0" }
dioxus-html = { path = "../html", version = "^0.2.0" }
dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.0", features = [
"web",
dioxus-core = { path = "../core", version = "^0.2.1" }
dioxus-html = { path = "../html", version = "^0.2.1", features = ["wasm-bind"] }
dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.1", features = [
"web"
] }
js-sys = "0.3.56"
+8 -91
View File
@@ -146,7 +146,7 @@ impl WebsysDom {
value,
ns,
} => {
let value = serde_wasm_bindgen::to_value(value).unwrap();
let value = serde_wasm_bindgen::to_value(&value).unwrap();
self.interpreter.SetAttribute(root, field, value, ns)
}
}
@@ -168,7 +168,6 @@ fn virtual_event_from_websys_event(
target: Element,
) -> Arc<dyn Any + Send + Sync> {
use dioxus_html::on::*;
use dioxus_html::KeyCode;
match event.type_().as_str() {
"copy" | "cut" | "paste" => Arc::new(ClipboardData {}),
@@ -178,22 +177,7 @@ fn virtual_event_from_websys_event(
data: evt.data().unwrap_or_default(),
})
}
"keydown" | "keypress" | "keyup" => {
let evt: &web_sys::KeyboardEvent = event.dyn_ref().unwrap();
Arc::new(KeyboardData {
alt_key: evt.alt_key(),
char_code: evt.char_code(),
key: evt.key(),
key_code: KeyCode::from_raw_code(evt.key_code() as u8),
ctrl_key: evt.ctrl_key(),
locale: "not implemented".to_string(),
location: evt.location() as usize,
meta_key: evt.meta_key(),
repeat: evt.repeat(),
shift_key: evt.shift_key(),
which: evt.which() as usize,
})
}
"keydown" | "keypress" | "keyup" => Arc::new(KeyboardData::from(event)),
"focus" | "blur" => Arc::new(FocusData {}),
// todo: these handlers might get really slow if the input box gets large and allocation pressure is heavy
@@ -277,88 +261,21 @@ fn virtual_event_from_websys_event(
"click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
| "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter"
| "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
let evt: &web_sys::MouseEvent = event.dyn_ref().unwrap();
Arc::new(MouseData {
alt_key: evt.alt_key(),
button: evt.button(),
buttons: evt.buttons(),
client_x: evt.client_x(),
client_y: evt.client_y(),
ctrl_key: evt.ctrl_key(),
meta_key: evt.meta_key(),
screen_x: evt.screen_x(),
screen_y: evt.screen_y(),
shift_key: evt.shift_key(),
page_x: evt.page_x(),
page_y: evt.page_y(),
})
Arc::new(MouseData::from(event))
}
"pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
| "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
let evt: &web_sys::PointerEvent = event.dyn_ref().unwrap();
Arc::new(PointerData {
alt_key: evt.alt_key(),
button: evt.button(),
buttons: evt.buttons(),
client_x: evt.client_x(),
client_y: evt.client_y(),
ctrl_key: evt.ctrl_key(),
meta_key: evt.meta_key(),
page_x: evt.page_x(),
page_y: evt.page_y(),
screen_x: evt.screen_x(),
screen_y: evt.screen_y(),
shift_key: evt.shift_key(),
pointer_id: evt.pointer_id(),
width: evt.width(),
height: evt.height(),
pressure: evt.pressure(),
tangential_pressure: evt.tangential_pressure(),
tilt_x: evt.tilt_x(),
tilt_y: evt.tilt_y(),
twist: evt.twist(),
pointer_type: evt.pointer_type(),
is_primary: evt.is_primary(),
// get_modifier_state: evt.get_modifier_state(),
})
Arc::new(PointerData::from(event))
}
"select" => Arc::new(SelectionData {}),
"touchcancel" | "touchend" | "touchmove" | "touchstart" => {
let evt: &web_sys::TouchEvent = event.dyn_ref().unwrap();
Arc::new(TouchData {
alt_key: evt.alt_key(),
ctrl_key: evt.ctrl_key(),
meta_key: evt.meta_key(),
shift_key: evt.shift_key(),
})
}
"touchcancel" | "touchend" | "touchmove" | "touchstart" => Arc::new(TouchData::from(event)),
"scroll" => Arc::new(()),
"wheel" => {
let evt: &web_sys::WheelEvent = event.dyn_ref().unwrap();
Arc::new(WheelData {
delta_x: evt.delta_x(),
delta_y: evt.delta_y(),
delta_z: evt.delta_z(),
delta_mode: evt.delta_mode(),
})
}
"wheel" => Arc::new(WheelData::from(event)),
"animationstart" | "animationend" | "animationiteration" => {
let evt: &web_sys::AnimationEvent = event.dyn_ref().unwrap();
Arc::new(AnimationData {
elapsed_time: evt.elapsed_time(),
animation_name: evt.animation_name(),
pseudo_element: evt.pseudo_element(),
})
}
"transitionend" => {
let evt: &web_sys::TransitionEvent = event.dyn_ref().unwrap();
Arc::new(TransitionData {
elapsed_time: evt.elapsed_time(),
property_name: evt.property_name(),
pseudo_element: evt.pseudo_element(),
})
Arc::new(AnimationData::from(event))
}
"transitionend" => Arc::new(TransitionData::from(event)),
"abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
| "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
| "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
+7 -2
View File
@@ -6,10 +6,10 @@ pub mod hooks {
#[cfg(feature = "hooks")]
pub use dioxus_hooks::*;
#[cfg(all(target = "wasm", feature = "web"))]
#[cfg(all(target_arch = "wasm32", feature = "web"))]
pub use dioxus_web::use_eval;
#[cfg(all(not(target = "wasm"), feature = "desktop"))]
#[cfg(all(not(target_arch = "wasm32"), feature = "desktop"))]
pub use dioxus_desktop::use_eval;
}
@@ -31,6 +31,11 @@ pub use dioxus_desktop as desktop;
#[cfg(feature = "tui")]
pub use dioxus_tui as tui;
#[cfg(feature = "native-core")]
pub use dioxus_native_core as native_core;
#[cfg(feature = "native-core")]
pub use dioxus_native_core_macro as native_core_macro;
#[cfg(feature = "fermi")]
pub use fermi;