Merge remote-tracking branch 'upstream/master' into pr/Demonthos/473

This commit is contained in:
Demonthos 2022-07-02 18:48:32 -05:00
commit 55e262b2c6
64 changed files with 1123 additions and 765 deletions

View file

@ -86,7 +86,6 @@ If you know React, then you already know Dioxus.
<th><a href="https://dioxuslabs.com/reference/desktop/">Desktop</a></th>
<th><a href="https://dioxuslabs.com/reference/ssr/">SSR</a></th>
<th><a href="https://dioxuslabs.com/reference/mobile/">Mobile</a></th>
<th><a href="https://dioxuslabs.com/guide/concepts/managing_state.html">State</a></th>
<tr>
</table>

View file

@ -32,18 +32,12 @@
- [Working with Async](async/index.md)
- [UseFuture](async/use_future.md)
- [UseCoroutine](async/coroutines.md)
<!-- - [Fetching](async/fetching.md) -->
<!-- - [Updating State](async/loading_state.md)
- [WebSockets](async/sockets.md) -->
<!-- - [Tasks](async/asynctasks.md) -->
- [Updating State](async/loading_state.md)
- [Tasks](async/asynctasks.md)
<!--
- [Putting it all together: Dog Search Engine](tutorial/index.md)
- [New app](tutorial/new_app.md)
- [Structuring our app](tutorial/structure.md)
- [Defining State](tutorial/state.md)
- [Defining Components](tutorial/components.md)
- [Styling](tutorial/styling.md)
- [Bundling](tutorial/publishing.md) -->

View file

@ -204,7 +204,6 @@ fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
fn ctrl_key(&self) -> bool { self.0.ctrl_key() }
fn key(&self) -> String { self.0.key() }
fn key_code(&self) -> usize { self.0.key_code() }
fn locale(&self) -> String { self.0.locale() }
fn location(&self) -> usize { self.0.location() }
fn meta_key(&self) -> bool { self.0.meta_key() }
fn repeat(&self) -> bool { self.0.repeat() }

View file

@ -1,2 +0,0 @@
# Fetching

View file

@ -1 +0,0 @@
# WebSockets

View file

@ -1 +0,0 @@
# Channels

View file

@ -1 +0,0 @@
# Advanced Guides

View file

@ -1,3 +0,0 @@
# Defining Components
This section is currently under construction! 🏗

View file

@ -1,3 +0,0 @@
# Defining State
This section is currently under construction! 🏗

View file

@ -1,4 +0,0 @@
# Structuring our app
This section is currently under construction! 🏗

View file

@ -1,4 +0,0 @@
# Styling
This section is currently under construction! 🏗

View file

@ -11,35 +11,5 @@
- [Advanced Guides](guide/index.md)
- [RSX in Depth](guide/rsx_in_depth.md)
- [Components](guide/components.md)
- [Props](guide/props.md)
- [Memoization](guide/memoization.md)
- [Performance](guide/performance.md)
- [Testing](guide/testing.md)
- [Building Elements with NodeFactory](guide/rsx.md)
- [Custom Elements](guide/custom_elements.md)
- [Custom Renderer](guide/custom_renderer.md)
- [Server-side components](guide/server_side_components.md)
- [Bundling and Distributing](guide/bundline.md)
- [Hot Reloading Rsx](guide/hot_reloading.md)
- [Reference Guide](reference/reference.md)
- [Anti-patterns](reference/anti.md)
- [Children](reference/children.md)
- [Conditional Rendering](reference/conditional.md)
- [Controlled Inputs](reference/controlled.md)
- [Custom Elements](reference/custom.md)
- [Empty Components](reference/empty.md)
- [Error Handling](reference/error.md)
- [Fragments](reference/fragments.md)
- [Global CSS](reference/global.md)
- [Inline Styles](reference/inline.md)
- [Iterators](reference/iterators.md)
- [Listeners](reference/listeners.md)
- [Memoization](reference/memoization.md)
- [Node Refs](reference/node.md)
- [Spread Pattern](reference/spread.md)
- [State Management](reference/state.md)
- [Suspense](reference/suspense.md)
- [task](reference/task.md)
- [Testing](reference/testing.md)
- [Hot Reloading Rsx](guide/hot_reloading.md)

View file

@ -1 +0,0 @@
# Components

View file

@ -1 +0,0 @@
# Memoization

View file

@ -1 +0,0 @@
# Performance

View file

@ -1 +0,0 @@
# Props

View file

@ -1 +0,0 @@
# RSX

View file

@ -1 +0,0 @@
# Testing

View file

@ -1 +0,0 @@
# Topics in Depth

View file

@ -1 +0,0 @@
# Bundling and Distributing

View file

@ -1 +0,0 @@
# Components

View file

@ -1 +0,0 @@
# Custom Elements

View file

@ -206,7 +206,6 @@ fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
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(),

View file

@ -1 +0,0 @@
# Memoization

View file

@ -1 +0,0 @@
# Performance

View file

@ -1 +0,0 @@
# Props

View file

@ -1 +0,0 @@
# Building Elements with NodeFactory

View file

@ -1 +0,0 @@
# Server-side components

View file

@ -1 +0,0 @@
# Testing

View file

@ -1,189 +1,176 @@
# Examples
Most of these examples are run through webview so you don't need the Dioxus CLI installed to preview the functionality.
These examples are fully-fledged mini Dioxus apps.
These examples are fully-fledged micro apps. They can be ran with the `cargo run --example XYZ`
You can run them with `cargo run --example EXAMPLE_NAME`. Example:
| Example | What it does | Status |
| --------------------------------------------------- | ------------------------------------------- | ------ |
| [The basics](./basics.rs) | A few basic examples to preview Dioxus | 🛠 |
| [fine grained reactivity](./signals.rs) | Escape `diffing` by writing values directly | 🛠 |
| [Global State Management](./statemanagement.rs) | Share state between components | 🛠 |
| [Virtual Refs]() | Cross-platform imperative elements | 🛠 |
| [Inline Styles](./inline-styles.rs) | Define styles for elements inline | 🛠 |
| [Conditional Rendering](./conditional-rendering.rs) | Hide/Show elements using conditionals | ✅ |
These examples are not necessarily meant to be run, but rather serve as a reference for the given functionality.
| Example | What it does | Status |
| --------------------------------------------------- | ----------------------------------------------- | ------ |
| [The basics](./basics.rs) | A few basic examples to preview Dioxus | 🛠 |
| [fine grained reactivity](./signals.rs) | Escape `diffing` by writing values directly | 🛠 |
| [Global State Management](./statemanagement.rs) | Share state between components | 🛠 |
| [Virtual Refs]() | Cross-platform imperative elements | 🛠 |
| [Inline Styles](./inline-styles.rs) | Define styles for elements inline | 🛠 |
| [Conditional Rendering](./conditional-rendering.rs) | Hide/Show elements using conditionals | ✅ |
| [Maps/Iterators](./iterators.rs) | Use iterators in the rsx! macro | ✅ |
| [Render To string](./tostring.rs) | Render a mounted virtualdom to a string | 🛠 |
| [Component Children](./children.rs) | Pass children into child components | 🛠 |
| [Function Driven children]() | Pass functions to make VNodes | 🛠 |
| [Memoization & Borrowed Data](./memo.rs) | Suppress renders, borrow from parents | ✅ |
| [Fragments](./fragments.rs) | Support root-less element groups | ✅ |
| [Null/None Components](./empty.rs) | Return nothing! | 🛠 |
| [Spread Pattern for props](./spreadpattern.rs) | Manually specify and override props | ✅ |
| [Controlled Inputs](./controlled-inputs.rs) | this does | 🛠 |
| [Custom Elements]() | Define custom elements | 🛠 |
| [Web Components]() | Custom elements to interface with WebComponents | 🛠 |
| [Testing And debugging]() | this does | 🛠 |
| [Asynchronous Data]() | Using suspense to wait for data | 🛠 |
| [Fiber/Scheduled Rendering]() | this does | 🛠 |
| [CSS Compiled Styles]() | this does | 🛠 |
| [Anti-patterns](./antipatterns.rs) | A collection of discouraged patterns | ✅ |
| [Complete rsx reference](./rsx_usage.rs) | A complete reference for all rsx! usage | ✅ |
| [Event Listeners](./listener.rs) | Attach closures to events on elements | ✅ |
| [Inline Props](./inlineprops.rs) | Using the `#[inline_props]` macro | ✅ |
| [Eval](./eval.rs) | Evaluate dynamic JavaScript code | ✅ |
## Show me some examples!
In our collection of examples, guides, and tutorials, we have:
- The book (an introductory course)
- The guide (an in-depth analysis of everything in Dioxus)
- The reference (a collection of examples with heavy documentation)
- The general examples
- The platform-specific examples (web, ssr, desktop, mobile, server)
Here's what a few common tasks look like in Dioxus:
Nested components with children and internal state:
```rust
fn App(cx: Scope) -> Element {
cx.render(rsx!( Toggle { "Toggle me" } ))
}
#[derive(PartialEq, Props)]
struct ToggleProps { children: Element }
fn Toggle(cx: Scope<ToggleProps>) -> Element {
let mut toggled = use_state(&cx, || false);
cx.render(rsx!{
div {
&cx.props.children
button { onclick: move |_| toggled.set(true),
toggled.and_then(|| "On").or_else(|| "Off")
}
}
})
}
```shell
cargo run --example hello_world
```
Controlled inputs:
```rust
fn App(cx: Scope) -> Element {
let value = use_state(&cx, String::new);
cx.render(rsx!(
input {
"type": "text",
value: "{value}",
oninput: move |evt| value.set(evt.value.clone())
}
))
}
```
(Most of these examples are run through webview, so you don't need the Dioxus CLI installed)
Lists and Conditional rendering:
```rust
fn App(cx: Scope) -> Element {
let list = (0..10).map(|i| {
rsx!(li { key: "{i}", "Value: {i}" })
});
## Basic Features
let title = match list.len() {
0 => rsx!("Not enough"),
_ => rsx!("Plenty!"),
};
[hello_world](./hello_world.rs) - Most basic example
if should_show {
cx.render(rsx!(
title,
ul { list }
))
} else {
None
}
}
```
[readme](./readme.rs) - Counter example from the Readme
Tiny components:
```rust
static App: Component = |cx| rsx!(cx, div {"hello world!"});
```
[custom_assets](./custom_assets.rs) - Include images
Borrowed prop contents:
```rust
fn App(cx: Scope) -> Element {
let name = use_state(&cx, || String::from("example"));
rsx!(cx, Child { title: name.as_str() })
}
[custom_element](./custom_element.rs) - Render webcomponents
#[derive(Props)]
struct ChildProps<'a> { title: &'a str }
[custom_html](./custom_html.rs) - Customize wrapper HTML
fn Child(cx: Scope<ChildProps>) -> Element {
rsx!(cx, "Hello {cx.props.title}")
}
```
[eval](./eval.rs) - Evaluate JS expressions
Global State
```rust
struct GlobalState { name: String }
### RSX
fn App(cx: Scope) -> Element {
use_provide_shared_state(cx, || GlobalState { name: String::from("Toby") })
rsx!(cx, Leaf {})
}
[rsx_usage](./rsx_usage.rs) - Demo of all RSX features
fn Leaf(cx: Scope) -> Element {
let state = use_consume_shared_state::<GlobalState>(cx)?;
rsx!(cx, "Hello {state.name}")
}
```
[xss_safety](./xss_safety.rs) - You can include text without worrying about injections by default
Router (inspired by Yew-Router)
```rust
#[derive(PartialEq, Clone, Hash, Eq, Routable)]
enum Route {
#[at("/")]
Home,
#[at("/post/{id}")]
Post(id)
}
### Props
fn App(cx: Scope) -> Element {
let route = use_router(cx, Route::parse);
cx.render(rsx!(div {
match route {
Route::Home => rsx!( Home {} ),
Route::Post(id) => rsx!( Post { id: id })
}
}))
}
```
[borrowed](./borrowed.rs) - Borrowed props
Suspense
```rust
fn App(cx: Scope) -> Element {
let doggo = use_suspense(cx,
|| async { reqwest::get("https://dog.ceo/api/breeds/image/random").await.unwrap().json::<Response>().await.unwrap() },
|response| cx.render(rsx!( img { src: "{response.message}" }))
);
[inlineprops](./inlineprops.rs) - Demo of `inline_props` macro
cx.render(rsx!{
div {
"One doggo coming right up:",
doggo
}
})
}
```
[optional_props](./optional_props.rs) - Optional props
### CSS
[all_css](./all_css.rs) - You can specify any CSS attribute
[tailwind](./tailwind.rs) - You can use a library for styling
## Input Handling
[all_events](./all_events.rs) - Basic event handling demo
[filedragdrop](./filedragdrop.rs) - Handle dropped files
[form](./form.rs) - Handle form submission
[inputs](./inputs.rs) - Input values
[nested_listeners](./nested_listeners.rs) - Nested handlers and bubbling
[textarea](textarea.rs) - Text area input
### State Management
[fermi](./fermi.rs) - Fermi library for state management
[pattern_reducer](./pattern_reducer.rs) - The reducer pattern with `use_state`
[rsx_compile_fail](./rsx_compile_fail.rs)
### Async
[login_form](./login_form.rs) - Login endpoint example
[suspense](./suspense.rs) - Render placeholders while data is loading
[tasks](./tasks.rs) - Continuously run future
### SVG
[svg_basic](./svg_basic.rs)
[svg](./svg.rs)
### Performance
[framework_benchmark](./framework_benchmark.rs) - Renders a huge list
> Note: The benchmark should be run in release mode:
>
>```shell
> cargo run --example framework_benchmark --release
>```
[heavy_compute](./heavy_compute.rs) - How to deal with expensive operations
## Server-side rendering
[ssr](./ssr.rs) - Rendering RSX server-side
[hydration](./hydration.rs) - Pre-rendering with hydration
## Common Patterns
[disabled](./disabled.rs) - Disable buttons conditionally
[error_handle](./error_handle.rs) - Handle errors with early return
## Routing
[flat_router](./flat_router.rs) - Basic, flat route example
[router](./router.rs) - Router example
[link](./link.rs) - Internal, external and custom links
## Platform Features
[window_event](./window_event.rs) - Window decorations, fullscreen, minimization, etc.
[window_zoom](./window_zoom.rs) Zoom in or out
## Example Apps
[calculator](./calculator.rs) - Simple calculator
[pattern_model](./pattern_model.rs) - Simple calculator, but using a custom struct as the model
[crm](./crm.rs) - Toy multi-page customer management app
[dog_app](./dog_app.rs) - Accesses dog API
[file_explorer](./file_explorer.rs) - File browser that uses `use_ref` to interact with the model
[todomvc](./todomvc.rs) - Todo task list example
## Terminal UI
[tui_border](./tui_border.rs)
[tui_buttons](./tui_buttons.rs)
[tui_color_test](./tui_color_test.rs)
[tui_components](./tui_components.rs)
[tui_frame](./tui_frame.rs)
[tui_hover](./tui_hover.rs)
[tui_keys](./tui_keys.rs)
[tui_list](./tui_list.rs)
[tui_margin](./tui_margin.rs)
[tui_quadrants](./tui_quadrants.rs)
[tui_readme](./tui_readme.rs)
[tui_task](./tui_task.rs)
[tui_text](./tui_text.rs)
# TODO
Missing Features
- Fine-grained reactivity
- Refs - imperative handles to elements
- Function-driven children: Pass functions to make VNodes
Missing examples
- Shared state
- Root-less element groups
- Spread props
- Custom elements
- Component Children: Pass children into child components
- Render To string: Render a mounted virtualdom to a string
- Testing and Debugging

85
examples/all_events.rs Normal file
View file

@ -0,0 +1,85 @@
use dioxus::prelude::*;
use dioxus_html::on::{FocusData, KeyboardData, MouseData, WheelData};
use std::sync::Arc;
fn main() {
dioxus::desktop::launch(app);
}
#[derive(Debug)]
enum Event {
MouseMove(Arc<MouseData>),
MouseClick(Arc<MouseData>),
MouseDoubleClick(Arc<MouseData>),
MouseDown(Arc<MouseData>),
MouseUp(Arc<MouseData>),
Wheel(Arc<WheelData>),
KeyDown(Arc<KeyboardData>),
KeyUp(Arc<KeyboardData>),
KeyPress(Arc<KeyboardData>),
FocusIn(Arc<FocusData>),
FocusOut(Arc<FocusData>),
}
const MAX_EVENTS: usize = 8;
fn app(cx: Scope) -> Element {
let container_style = r#"
display: flex;
flex-direction: column;
align-items: center;
"#;
let rect_style = r#"
background: deepskyblue;
height: 50vh;
width: 50vw;
color: white;
padding: 20px;
margin: 20px;
text-aligh: center;
"#;
let events = use_ref(&cx, || Vec::new());
let events_lock = events.read();
let first_index = events_lock.len().saturating_sub(MAX_EVENTS);
let events_rendered = events_lock[first_index..]
.iter()
.map(|event| cx.render(rsx!(div {"{event:?}"})));
let log_event = move |event: Event| {
events.write().push(event);
};
cx.render(rsx! (
div {
style: "{container_style}",
div {
style: "{rect_style}",
// focusing is necessary to catch keyboard events
tabindex: "0",
onmousemove: move |event| log_event(Event::MouseMove(event.data)),
onclick: move |event| log_event(Event::MouseClick(event.data)),
ondblclick: move |event| log_event(Event::MouseDoubleClick(event.data)),
onmousedown: move |event| log_event(Event::MouseDown(event.data)),
onmouseup: move |event| log_event(Event::MouseUp(event.data)),
onwheel: move |event| log_event(Event::Wheel(event.data)),
onkeydown: move |event| log_event(Event::KeyDown(event.data)),
onkeyup: move |event| log_event(Event::KeyUp(event.data)),
onkeypress: move |event| log_event(Event::KeyPress(event.data)),
onfocusin: move |event| log_event(Event::FocusIn(event.data)),
onfocusout: move |event| log_event(Event::FocusOut(event.data)),
"Hover, click, type or scroll to see the info down below"
}
div { events_rendered },
},
))
}

View file

@ -5,6 +5,7 @@ This calculator version uses React-style state management. All state is held as
use dioxus::events::*;
use dioxus::prelude::*;
use dioxus_html::input_data::keyboard_types::Key;
fn main() {
use dioxus::desktop::tao::dpi::LogicalSize;
@ -29,33 +30,38 @@ fn app(cx: Scope) -> Element {
let input_operator = move |key: &str| val.make_mut().push_str(key);
let handle_key_down_event = move |evt: KeyboardEvent| match evt.key() {
Key::Backspace => {
if !val.len() != 0 {
val.make_mut().pop();
}
}
Key::Character(character) => match character.as_str() {
"+" => input_operator("+"),
"-" => input_operator("-"),
"/" => input_operator("/"),
"*" => input_operator("*"),
"0" => input_digit(0),
"1" => input_digit(1),
"2" => input_digit(2),
"3" => input_digit(3),
"4" => input_digit(4),
"5" => input_digit(5),
"6" => input_digit(6),
"7" => input_digit(7),
"8" => input_digit(8),
"9" => input_digit(9),
_ => {}
},
_ => {}
};
cx.render(rsx!(
style { [include_str!("./assets/calculator.css")] }
div { id: "wrapper",
div { class: "app",
div { class: "calculator",
onkeydown: move |evt| match evt.key_code {
KeyCode::Add => input_operator("+"),
KeyCode::Subtract => input_operator("-"),
KeyCode::Divide => input_operator("/"),
KeyCode::Multiply => input_operator("*"),
KeyCode::Num0 => input_digit(0),
KeyCode::Num1 => input_digit(1),
KeyCode::Num2 => input_digit(2),
KeyCode::Num3 => input_digit(3),
KeyCode::Num4 => input_digit(4),
KeyCode::Num5 => input_digit(5),
KeyCode::Num6 => input_digit(6),
KeyCode::Num7 => input_digit(7),
KeyCode::Num8 => input_digit(8),
KeyCode::Num9 => input_digit(9),
KeyCode::Backspace => {
if !val.len() != 0 {
val.make_mut().pop();
}
}
_ => {}
},
onkeydown: handle_key_down_event,
div { class: "calculator-display", [val.to_string()] }
div { class: "calculator-keypad",
div { class: "input-keys",

View file

@ -1,26 +0,0 @@
use dioxus::prelude::*;
fn main() {
dioxus::desktop::launch(app);
}
fn app(cx: Scope) -> Element {
cx.render(rsx! {
div {
button {
ondblclick: move |_| {
//
println!("double clicked!");
},
"Click me!"
}
input {
onfocusin: move |_| {
//
println!("blurred!");
},
"onblur": "console.log('blurred!')"
}
}
})
}

View file

@ -1,56 +0,0 @@
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}"},
}
))
}

View file

@ -1,28 +0,0 @@
#![allow(non_snake_case)]
//! This example proves that instantly resolving futures don't cause issues
use dioxus::prelude::*;
fn main() {
dioxus::desktop::launch(App);
}
fn App(cx: Scope) -> Element {
cx.render(rsx!(Demo {}))
}
fn Demo(cx: Scope) -> Element {
let fut1 = use_future(&cx, (), |_| async move {
std::thread::sleep(std::time::Duration::from_millis(100));
10
});
cx.render(match fut1.value() {
Some(value) => {
let content = format!("content : {:?}", value);
rsx!(div{ "{content}" })
}
None => rsx!(div{"computing!"}),
})
}

View file

@ -20,6 +20,7 @@
use dioxus::desktop::wry::application::dpi::LogicalSize;
use dioxus::events::*;
use dioxus::prelude::*;
use dioxus_html::input_data::keyboard_types::Key;
fn main() {
dioxus::desktop::launch_cfg(app, |cfg| {
@ -212,22 +213,26 @@ impl Calculator {
self.waiting_for_operand = true;
}
fn handle_keydown(&mut self, evt: KeyboardEvent) {
match evt.key_code {
KeyCode::Backspace => self.backspace(),
KeyCode::Num0 => self.input_digit(0),
KeyCode::Num1 => self.input_digit(1),
KeyCode::Num2 => self.input_digit(2),
KeyCode::Num3 => self.input_digit(3),
KeyCode::Num4 => self.input_digit(4),
KeyCode::Num5 => self.input_digit(5),
KeyCode::Num6 => self.input_digit(6),
KeyCode::Num7 => self.input_digit(7),
KeyCode::Num8 => self.input_digit(8),
KeyCode::Num9 => self.input_digit(9),
KeyCode::Add => self.operator = Some(Operator::Add),
KeyCode::Subtract => self.operator = Some(Operator::Sub),
KeyCode::Divide => self.operator = Some(Operator::Div),
KeyCode::Multiply => self.operator = Some(Operator::Mul),
match evt.key() {
Key::Backspace => self.backspace(),
Key::Character(c) => match c.as_str() {
"0" => self.input_digit(0),
"1" => self.input_digit(1),
"2" => self.input_digit(2),
"3" => self.input_digit(3),
"4" => self.input_digit(4),
"5" => self.input_digit(5),
"6" => self.input_digit(6),
"7" => self.input_digit(7),
"8" => self.input_digit(8),
"9" => self.input_digit(9),
"+" => self.operator = Some(Operator::Add),
"-" => self.operator = Some(Operator::Sub),
"/" => self.operator = Some(Operator::Div),
"*" => self.operator = Some(Operator::Mul),
_ => {}
},
_ => {}
}
}

View file

@ -1,4 +1,5 @@
use dioxus::prelude::*;
use dioxus_elements::input_data::keyboard_types::Key;
fn main() {
dioxus::desktop::launch(app);
@ -56,7 +57,7 @@ pub fn app(cx: Scope<()>) -> Element {
autofocus: "true",
oninput: move |evt| draft.set(evt.value.clone()),
onkeydown: move |evt| {
if evt.key == "Enter" && !draft.is_empty() {
if evt.key() == Key::Enter && !draft.is_empty() {
todos.make_mut().insert(
**todo_id,
TodoItem {
@ -148,8 +149,8 @@ pub fn todo_entry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element {
autofocus: "true",
onfocusout: move |_| is_editing.set(false),
onkeydown: move |evt| {
match evt.key.as_str() {
"Enter" | "Escape" | "Tab" => is_editing.set(false),
match evt.key() {
Key::Enter | Key::Escape | Key::Tab => is_editing.set(false),
_ => {}
}
},

View file

@ -0,0 +1,85 @@
use dioxus::prelude::*;
use dioxus_html::on::{FocusData, KeyboardData, MouseData, WheelData};
use std::sync::Arc;
fn main() {
dioxus::tui::launch(app);
}
#[derive(Debug)]
enum Event {
MouseMove(Arc<MouseData>),
MouseClick(Arc<MouseData>),
MouseDoubleClick(Arc<MouseData>),
MouseDown(Arc<MouseData>),
MouseUp(Arc<MouseData>),
Wheel(Arc<WheelData>),
KeyDown(Arc<KeyboardData>),
KeyUp(Arc<KeyboardData>),
KeyPress(Arc<KeyboardData>),
FocusIn(Arc<FocusData>),
FocusOut(Arc<FocusData>),
}
const MAX_EVENTS: usize = 8;
fn app(cx: Scope) -> Element {
let events = use_ref(&cx, || Vec::new());
let events_lock = events.read();
let first_index = events_lock.len().saturating_sub(MAX_EVENTS);
let events_rendered = events_lock[first_index..].iter().map(|event| {
// TUI panics if text overflows (https://github.com/DioxusLabs/dioxus/issues/371)
// temporary hack: just trim the strings (and make sure viewport is big enough)
// todo: remove
let mut trimmed = format!("{event:?}");
trimmed.truncate(200);
cx.render(rsx!(p { "{trimmed}" }))
});
let log_event = move |event: Event| {
events.write().push(event);
};
cx.render(rsx! {
div {
width: "100%",
height: "100%",
flex_direction: "column",
div {
width: "80%",
height: "50%",
border_width: "1px",
justify_content: "center",
align_items: "center",
background_color: "hsl(248, 53%, 58%)",
onmousemove: move |event| log_event(Event::MouseMove(event.data)),
onclick: move |event| log_event(Event::MouseClick(event.data)),
ondblclick: move |event| log_event(Event::MouseDoubleClick(event.data)),
onmousedown: move |event| log_event(Event::MouseDown(event.data)),
onmouseup: move |event| log_event(Event::MouseUp(event.data)),
onwheel: move |event| log_event(Event::Wheel(event.data)),
onkeydown: move |event| log_event(Event::KeyDown(event.data)),
onkeyup: move |event| log_event(Event::KeyUp(event.data)),
onkeypress: move |event| log_event(Event::KeyPress(event.data)),
onfocusin: move |event| log_event(Event::FocusIn(event.data)),
onfocusout: move |event| log_event(Event::FocusOut(event.data)),
"Hover, click, type or scroll to see the info down below"
},
div {
width: "80%",
height: "50%",
flex_direction: "column",
events_rendered,
},
},
})
}

View file

@ -14,7 +14,7 @@ fn app(cx: Scope) -> Element {
justify_content: "center",
align_items: "center",
background_color: "hsl(248, 53%, 58%)",
onwheel: move |w| radius.modify(|r| (r + w.delta_y as i8).abs()),
onwheel: move |w| radius.modify(|r| (r + w.delta().strip_units().y as i8).abs()),
border_style: "solid none solid double",
border_width: "thick",

View file

@ -1,4 +1,5 @@
use dioxus::{events::KeyCode, prelude::*};
use dioxus::prelude::*;
use dioxus_html::input_data::keyboard_types::Code;
fn main() {
dioxus::tui::launch(app);
@ -27,7 +28,7 @@ fn Button(cx: Scope<ButtonProps>) -> Element {
background_color: "{color}",
tabindex: "{cx.props.layer}",
onkeydown: |e| {
if let KeyCode::Space = e.data.key_code{
if let Code::Space = e.data.code() {
toggle.modify(|f| !f);
}
},

View file

@ -65,7 +65,7 @@ fn app(cx: Scope) -> Element {
onmouseenter: move |m| q1_color.set([get_brightness(m.data), 0, 0]),
onmousedown: move |m| q1_color.set([get_brightness(m.data), 0, 0]),
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]),
onwheel: move |w| q1_color.set([q1_color[0] + (10.0*w.delta().strip_units().y) as i32, 0, 0]),
onmouseleave: move |_| q1_color.set([200; 3]),
onmousemove: update_data,
"click me"
@ -79,7 +79,7 @@ fn app(cx: Scope) -> Element {
onmouseenter: move |m| q2_color.set([get_brightness(m.data); 3]),
onmousedown: move |m| q2_color.set([get_brightness(m.data); 3]),
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]),
onwheel: move |w| q2_color.set([q2_color[0] + (10.0*w.delta().strip_units().y) as i32;3]),
onmouseleave: move |_| q2_color.set([200; 3]),
onmousemove: update_data,
"click me"
@ -99,7 +99,7 @@ fn app(cx: Scope) -> Element {
onmouseenter: move |m| q3_color.set([0, get_brightness(m.data), 0]),
onmousedown: move |m| q3_color.set([0, get_brightness(m.data), 0]),
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]),
onwheel: move |w| q3_color.set([0, q3_color[1] + (10.0*w.delta().strip_units().y) as i32, 0]),
onmouseleave: move |_| q3_color.set([200; 3]),
onmousemove: update_data,
"click me"
@ -113,7 +113,7 @@ fn app(cx: Scope) -> Element {
onmouseenter: move |m| q4_color.set([0, 0, get_brightness(m.data)]),
onmousedown: move |m| q4_color.set([0, 0, get_brightness(m.data)]),
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]),
onwheel: move |w| q4_color.set([0, 0, q4_color[2] + (10.0*w.delta().strip_units().y) as i32]),
onmouseleave: move |_| q4_color.set([200; 3]),
onmousemove: update_data,
"click me"

View file

@ -1,9 +1,9 @@
use dioxus::events::WheelEvent;
use dioxus::prelude::*;
use dioxus_html::geometry::ScreenPoint;
use dioxus_html::input_data::keyboard_types::Code;
use dioxus_html::input_data::MouseButtonSet;
use dioxus_html::on::{KeyboardEvent, MouseEvent};
use dioxus_html::KeyCode;
fn main() {
dioxus::tui::launch(app);
@ -16,6 +16,21 @@ fn app(cx: Scope) -> Element {
let buttons = use_state(&cx, MouseButtonSet::empty);
let mouse_clicked = use_state(&cx, || false);
let key_down_handler = move |evt: KeyboardEvent| {
match evt.data.code() {
Code::ArrowLeft => count.set(count + 1),
Code::ArrowRight => count.set(count - 1),
Code::ArrowUp => count.set(count + 10),
Code::ArrowDown => count.set(count - 10),
_ => {}
}
key.set(format!(
"{:?} repeating: {:?}",
evt.key(),
evt.is_auto_repeating()
));
};
cx.render(rsx! {
div {
width: "100%",
@ -24,18 +39,9 @@ fn app(cx: Scope) -> Element {
justify_content: "center",
align_items: "center",
flex_direction: "column",
onkeydown: move |evt: KeyboardEvent| {
match evt.data.key_code {
KeyCode::LeftArrow => count.set(count + 1),
KeyCode::RightArrow => count.set(count - 1),
KeyCode::UpArrow => count.set(count + 10),
KeyCode::DownArrow => count.set(count - 10),
_ => {},
}
key.set(format!("{:?} repeating: {:?}", evt.key, evt.repeat));
},
onkeydown: key_down_handler,
onwheel: move |evt: WheelEvent| {
count.set(count + evt.data.delta_y as i64);
count.set(count + evt.data.delta().strip_units().y as i64);
},
ondrag: move |evt: MouseEvent| {
mouse.set(evt.data.screen_coordinates());

View file

@ -12,7 +12,7 @@ fn app(cx: Scope) -> Element {
width: "100%",
height: "100%",
flex_direction: "column",
onwheel: move |evt| alpha.set((**alpha + evt.data.delta_y as i64).min(100).max(0)),
onwheel: move |evt| alpha.set((**alpha + evt.data.delta().strip_units().y as i64).min(100).max(0)),
p {
background_color: "black",

View file

@ -835,10 +835,7 @@ impl ScopeState {
/// }
/// ```
#[allow(clippy::mut_from_ref)]
pub fn use_hook<'src, State: 'static>(
&'src self,
initializer: impl FnOnce(usize) -> State,
) -> &'src mut State {
pub fn use_hook<State: 'static>(&self, initializer: impl FnOnce(usize) -> State) -> &mut State {
let mut vals = self.hook_vals.borrow_mut();
let hook_len = vals.len();

View file

@ -86,6 +86,8 @@ impl DesktopController {
(serde_json::to_string(&err).unwrap() + "\n").as_bytes(),
)
.unwrap();
} else {
panic!("{}", err);
}
}
}

View file

@ -60,7 +60,7 @@ impl<T> ProvidedStateInner<T> {
///
///
///
pub fn use_context<'a, T: 'static>(cx: &'a ScopeState) -> Option<UseSharedState<'a, T>> {
pub fn use_context<T: 'static>(cx: &ScopeState) -> Option<UseSharedState<T>> {
let state = cx.use_hook(|_| {
let scope_id = cx.scope_id();
let root = cx.consume_context::<ProvidedState<T>>();

View file

@ -110,10 +110,7 @@ use std::{
/// }
/// })
/// ```
pub fn use_ref<'a, T: 'static>(
cx: &'a ScopeState,
initialize_refcell: impl FnOnce() -> T,
) -> &'a UseRef<T> {
pub fn use_ref<T: 'static>(cx: &ScopeState, initialize_refcell: impl FnOnce() -> T) -> &UseRef<T> {
let hook = cx.use_hook(|_| UseRef {
update: cx.schedule_update(),
value: Rc::new(RefCell::new(initialize_refcell())),

View file

@ -30,10 +30,10 @@ use std::{
/// ))
/// }
/// ```
pub fn use_state<'a, T: 'static>(
cx: &'a ScopeState,
pub fn use_state<T: 'static>(
cx: &ScopeState,
initial_state_fn: impl FnOnce() -> T,
) -> &'a UseState<T> {
) -> &UseState<T> {
let hook = cx.use_hook(move |_| {
let current_val = Rc::new(initial_state_fn());
let update_callback = cx.schedule_update();
@ -304,13 +304,13 @@ impl<T: 'static> Clone for UseState<T> {
}
}
impl<'a, T: 'static + Display> std::fmt::Display for UseState<T> {
impl<T: 'static + Display> std::fmt::Display for UseState<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.current_val)
}
}
impl<'a, T: std::fmt::Binary> std::fmt::Binary for UseState<T> {
impl<T: std::fmt::Binary> std::fmt::Binary for UseState<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:b}", self.current_val.as_ref())
}
@ -341,7 +341,7 @@ impl<T: Debug> Debug for UseState<T> {
}
}
impl<'a, T> std::ops::Deref for UseState<T> {
impl<T> std::ops::Deref for UseState<T> {
type Target = T;
fn deref(&self) -> &Self::Target {

View file

@ -40,5 +40,5 @@ features = [
[features]
default = []
serialize = ["serde", "serde_repr"]
serialize = ["serde", "serde_repr", "euclid/serde", "keyboard-types/serde"]
wasm-bind = ["web-sys", "wasm-bindgen"]

View file

@ -5,12 +5,20 @@ 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 crate::geometry::{
ClientPoint, Coordinates, ElementPoint, LinesVector, PagePoint, PagesVector, PixelsVector,
ScreenPoint, WheelDelta,
};
use keyboard_types::Modifiers;
use crate::input_data::{
decode_key_location, decode_mouse_button_set, encode_key_location, encode_mouse_button_set,
MouseButton, MouseButtonSet,
};
use euclid::UnknownUnit;
use keyboard_types::{Code, Key, Location, Modifiers};
use std::collections::HashMap;
use std::convert::TryInto;
use std::fmt::{Debug, Formatter};
use std::str::FromStr;
use super::*;
macro_rules! event_directory {
@ -419,71 +427,142 @@ pub mod on {
pub type KeyboardEvent = UiEvent<KeyboardData>;
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone)]
#[derive(Clone)]
pub struct KeyboardData {
#[deprecated(
since = "0.3.0",
note = "This may not work in all environments. Use key() instead."
)]
pub char_code: u32,
/// Identify which "key" was entered.
///
/// This is the best method to use for all languages. They key gets mapped to a String sequence which you can match on.
/// The key isn't an enum because there are just so many context-dependent keys.
///
/// A full list on which keys to use is available at:
/// <https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values>
///
/// # Example
///
/// ```rust, ignore
/// match event.key().as_str() {
/// "Esc" | "Escape" => {}
/// "ArrowDown" => {}
/// "ArrowLeft" => {}
/// _ => {}
/// }
/// ```
///
#[deprecated(since = "0.3.0", note = "use key() instead")]
pub key: String,
/// Get the key code as an enum Variant.
///
/// This is intended for things like arrow keys, escape keys, function keys, and other non-international keys.
/// To match on unicode sequences, use the [`KeyboardData::key`] method - this will return a string identifier instead of a limited enum.
///
///
/// ## Example
///
/// ```rust, ignore
/// use dioxus::KeyCode;
/// match event.key_code() {
/// KeyCode::Escape => {}
/// KeyCode::LeftArrow => {}
/// KeyCode::RightArrow => {}
/// _ => {}
/// }
/// ```
///
#[deprecated(
since = "0.3.0",
note = "This may not work in all environments. Use code() instead."
)]
pub key_code: KeyCode,
/// the physical key on the keyboard
code: Code,
/// Indicate if the `alt` modifier key was pressed during this keyboard event
#[deprecated(since = "0.3.0", note = "use modifiers() instead")]
pub alt_key: bool,
/// Indicate if the `ctrl` modifier key was pressed during this keyboard event
#[deprecated(since = "0.3.0", note = "use modifiers() instead")]
pub ctrl_key: bool,
/// Indicate if the `meta` modifier key was pressed during this keyboard event
#[deprecated(since = "0.3.0", note = "use modifiers() instead")]
pub meta_key: bool,
/// Indicate if the `shift` modifier key was pressed during this keyboard event
#[deprecated(since = "0.3.0", note = "use modifiers() instead")]
pub shift_key: bool,
pub locale: String,
#[deprecated(since = "0.3.0", note = "use location() instead")]
pub location: usize,
#[deprecated(since = "0.3.0", note = "use is_auto_repeating() instead")]
pub repeat: bool,
#[deprecated(since = "0.3.0", note = "use code() or key() instead")]
pub which: usize,
// get_modifier_state: bool,
}
impl KeyboardData {
pub fn new(
key: Key,
code: Code,
location: Location,
is_auto_repeating: bool,
modifiers: Modifiers,
) -> Self {
#[allow(deprecated)]
KeyboardData {
char_code: key.legacy_charcode(),
key: key.to_string(),
key_code: KeyCode::from_raw_code(
key.legacy_keycode()
.try_into()
.expect("could not convert keycode to u8"),
),
code,
alt_key: modifiers.contains(Modifiers::ALT),
ctrl_key: modifiers.contains(Modifiers::CONTROL),
meta_key: modifiers.contains(Modifiers::META),
shift_key: modifiers.contains(Modifiers::SHIFT),
location: encode_key_location(location),
repeat: is_auto_repeating,
which: key
.legacy_charcode()
.try_into()
.expect("could not convert charcode to usize"),
}
}
/// The value of the key pressed by the user, taking into consideration the state of modifier keys such as Shift as well as the keyboard locale and layout.
pub fn key(&self) -> Key {
#[allow(deprecated)]
FromStr::from_str(&self.key).expect("could not parse")
}
/// A physical key on the keyboard (as opposed to the character generated by pressing the key). In other words, this property returns a value that isn't altered by keyboard layout or the state of the modifier keys.
pub fn code(&self) -> Code {
self.code
}
/// 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 location of the key on the keyboard or other input device.
pub fn location(&self) -> Location {
#[allow(deprecated)]
decode_key_location(self.location)
}
/// `true` iff the key is being held down such that it is automatically repeating.
pub fn is_auto_repeating(&self) -> bool {
#[allow(deprecated)]
self.repeat
}
}
impl Debug for KeyboardData {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("KeyboardData")
.field("key", &self.key())
.field("code", &self.code())
.field("modifiers", &self.modifiers())
.field("location", &self.location())
.field("is_auto_repeating", &self.is_auto_repeating())
.finish()
}
}
pub type FocusEvent = UiEvent<FocusData>;
@ -502,7 +581,7 @@ pub mod on {
pub type MouseEvent = UiEvent<MouseData>;
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone)]
#[derive(Clone)]
/// Data associated with a mouse event
///
/// Do not use the deprecated fields; they may change or become private in the future.
@ -687,6 +766,17 @@ pub mod on {
}
}
impl Debug for MouseData {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MouseData")
.field("coordinates", &self.coordinates())
.field("modifiers", &self.modifiers())
.field("held_buttons", &self.held_buttons())
.field("trigger_button", &self.trigger_button())
.finish()
}
}
pub type PointerEvent = UiEvent<PointerData>;
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone)]
@ -738,14 +828,75 @@ pub mod on {
pub type WheelEvent = UiEvent<WheelData>;
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone)]
#[derive(Clone)]
pub struct WheelData {
#[deprecated(since = "0.3.0", note = "use delta() instead")]
pub delta_mode: u32,
#[deprecated(since = "0.3.0", note = "use delta() instead")]
pub delta_x: f64,
#[deprecated(since = "0.3.0", note = "use delta() instead")]
pub delta_y: f64,
#[deprecated(since = "0.3.0", note = "use delta() instead")]
pub delta_z: f64,
}
impl WheelData {
/// Construct a new WheelData with the specified wheel movement delta
pub fn new(delta: WheelDelta) -> Self {
let (delta_mode, vector) = match delta {
WheelDelta::Pixels(v) => (0, v.cast_unit::<UnknownUnit>()),
WheelDelta::Lines(v) => (1, v.cast_unit::<UnknownUnit>()),
WheelDelta::Pages(v) => (2, v.cast_unit::<UnknownUnit>()),
};
#[allow(deprecated)]
WheelData {
delta_mode,
delta_x: vector.x,
delta_y: vector.y,
delta_z: vector.z,
}
}
/// Construct from the attributes of the web wheel event
pub fn from_web_attributes(
delta_mode: u32,
delta_x: f64,
delta_y: f64,
delta_z: f64,
) -> Self {
#[allow(deprecated)]
Self {
delta_mode,
delta_x,
delta_y,
delta_z,
}
}
/// The amount of wheel movement
#[allow(deprecated)]
pub fn delta(&self) -> WheelDelta {
let x = self.delta_x;
let y = self.delta_y;
let z = self.delta_z;
match self.delta_mode {
0 => WheelDelta::Pixels(PixelsVector::new(x, y, z)),
1 => WheelDelta::Lines(LinesVector::new(x, y, z)),
2 => WheelDelta::Pages(PagesVector::new(x, y, z)),
_ => panic!("Invalid delta mode, {:?}", self.delta_mode),
}
}
}
impl Debug for WheelData {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WheelData")
.field("delta", &self.delta())
.finish()
}
}
pub type MediaEvent = UiEvent<MediaData>;
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone)]

View file

@ -25,7 +25,78 @@ pub struct PageSpace;
/// A point in PageSpace
pub type PagePoint = Point2D<f64, PageSpace>;
/// A pixel unit: one unit corresponds to 1 pixel
pub struct Pixels;
/// A vector expressed in Pixels
pub type PixelsVector = Vector3D<f64, Pixels>;
/// A unit in terms of Lines
///
/// One unit is relative to the size of one line
pub struct Lines;
/// A vector expressed in Lines
pub type LinesVector = Vector3D<f64, Lines>;
/// A unit in terms of Screens:
///
/// One unit is relative to the size of a page
pub struct Pages;
/// A vector expressed in Pages
pub type PagesVector = Vector3D<f64, Pages>;
/// A vector representing the amount the mouse wheel was moved
///
/// This may be expressed in Pixels, Lines or Pages
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
pub enum WheelDelta {
/// Movement in Pixels
Pixels(PixelsVector),
/// Movement in Lines
Lines(LinesVector),
/// Movement in Pages
Pages(PagesVector),
}
impl WheelDelta {
/// Convenience function for constructing a WheelDelta with pixel units
pub fn pixels(x: f64, y: f64, z: f64) -> Self {
WheelDelta::Pixels(PixelsVector::new(x, y, z))
}
/// Convenience function for constructing a WheelDelta with line units
pub fn lines(x: f64, y: f64, z: f64) -> Self {
WheelDelta::Lines(LinesVector::new(x, y, z))
}
/// Convenience function for constructing a WheelDelta with page units
pub fn pages(x: f64, y: f64, z: f64) -> Self {
WheelDelta::Pages(PagesVector::new(x, y, z))
}
/// Returns true iff there is no wheel movement
///
/// i.e. the x, y and z delta is zero (disregards units)
pub fn is_zero(&self) -> bool {
self.strip_units() == Vector3D::new(0., 0., 0.)
}
/// A Vector3D proportional to the amount scrolled
///
/// Note that this disregards the 3 possible units: this could be expressed in terms of pixels, lines, or pages.
///
/// In most cases, to properly handle scrolling, you should handle all 3 possible enum variants instead of stripping units. Otherwise, if you assume that the units will always be pixels, the user may experience some unexpectedly slow scrolling if their mouse/OS sends values expressed in lines or pages.
pub fn strip_units(&self) -> Vector3D<f64, UnknownUnit> {
match self {
WheelDelta::Pixels(v) => v.cast_unit(),
WheelDelta::Lines(v) => v.cast_unit(),
WheelDelta::Pages(v) => v.cast_unit(),
}
}
}
/// Coordinates of a point in the app's interface
#[derive(Debug)]
pub struct Coordinates {
screen: ScreenPoint,
client: ClientPoint,

View file

@ -3,6 +3,7 @@ use enumset::{EnumSet, EnumSetType};
/// A re-export of keyboard_types
pub use keyboard_types;
use keyboard_types::Location;
/// A mouse button type (such as Primary/Secondary)
// note: EnumSetType also derives Copy and Clone for some reason
@ -118,3 +119,25 @@ pub fn encode_mouse_button_set(set: MouseButtonSet) -> u16 {
code
}
pub fn decode_key_location(code: usize) -> Location {
match code {
0 => Location::Standard,
1 => Location::Left,
2 => Location::Right,
3 => Location::Numpad,
// keyboard_types doesn't yet support mobile/joystick locations
4 | 5 => Location::Standard,
// unknown location; Standard seems better than panicking
_ => Location::Standard,
}
}
pub fn encode_key_location(location: Location) -> usize {
match location {
Location::Standard => 0,
Location::Left => 1,
Location::Right => 2,
Location::Numpad => 3,
}
}

View file

@ -1,11 +1,12 @@
use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint};
use crate::input_data::{decode_mouse_button_set, MouseButton};
use crate::input_data::{decode_key_location, decode_mouse_button_set, MouseButton};
use crate::on::{
AnimationData, CompositionData, KeyboardData, MouseData, PointerData, TouchData,
TransitionData, WheelData,
};
use crate::KeyCode;
use keyboard_types::Modifiers;
use keyboard_types::{Code, Key, Modifiers};
use std::convert::TryInto;
use std::str::FromStr;
use wasm_bindgen::JsCast;
use web_sys::{
AnimationEvent, CompositionEvent, Event, KeyboardEvent, MouseEvent, PointerEvent, TouchEvent,
@ -56,19 +57,32 @@ impl From<&CompositionEvent> for CompositionData {
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,
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);
}
Self::new(
Key::from_str(&e.key()).expect("could not parse key"),
Code::from_str(&e.code()).expect("could not parse code"),
decode_key_location(
e.location()
.try_into()
.expect("could not convert location to u32"),
),
e.repeat(),
modifiers,
)
}
}
@ -146,12 +160,7 @@ impl From<&PointerEvent> for PointerData {
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(),
}
WheelData::from_web_attributes(e.delta_mode(), e.delta_x(), e.delta_y(), e.delta_z())
}
}

View file

@ -15,43 +15,45 @@ class ListenerMap {
this.root = root;
}
createBubbling(event_name, handler) {
if (this.global[event_name] === undefined) {
this.global[event_name] = {};
this.global[event_name].active = 1;
this.global[event_name].callback = handler;
this.root.addEventListener(event_name, handler);
} else {
this.global[event_name].active++;
create(event_name, element, handler, bubbles) {
if (bubbles) {
if (this.global[event_name] === undefined) {
this.global[event_name] = {};
this.global[event_name].active = 1;
this.global[event_name].callback = handler;
this.root.addEventListener(event_name, handler);
} else {
this.global[event_name].active++;
}
}
else {
const id = element.getAttribute("data-dioxus-id");
if (!this.local[id]) {
this.local[id] = {};
}
this.local[id][event_name] = handler;
element.addEventListener(event_name, handler);
}
}
createNonBubbling(event_name, element, handler) {
const id = element.getAttribute("data-dioxus-id");
if (!this.local[id]) {
this.local[id] = {};
remove(element, event_name, bubbles) {
if (bubbles) {
this.global[event_name].active--;
if (this.global[event_name].active === 0) {
this.root.removeEventListener(event_name, this.global[event_name].callback);
delete this.global[event_name];
}
}
this.local[id][event_name] = handler;
element.addEventListener(event_name, handler);
}
removeBubbling(event_name) {
this.global[event_name].active--;
if (this.global[event_name].active === 0) {
this.root.removeEventListener(event_name, this.global[event_name].callback);
delete this.global[event_name];
else {
const id = element.getAttribute("data-dioxus-id");
delete this.local[id][event_name];
if (this.local[id].length === 0) {
delete this.local[id];
}
element.removeEventListener(event_name, handler);
}
}
removeNonBubbling(element, event_name) {
const id = element.getAttribute("data-dioxus-id");
delete this.local[id][event_name];
if (this.local[id].length === 0) {
delete this.local[id];
}
element.removeEventListener(event_name, handler);
}
removeAllNonBubbling(element) {
const id = element.getAttribute("data-dioxus-id");
delete this.local[id];
@ -93,7 +95,9 @@ export class Interpreter {
ReplaceWith(root_id, m) {
let root = this.nodes[root_id];
let els = this.stack.splice(this.stack.length - m);
this.listeners.removeAllNonBubbling(root);
if (is_element_node(root.nodeType)) {
this.listeners.removeAllNonBubbling(root);
}
root.replaceWith(...els);
}
InsertAfter(root, n) {
@ -108,8 +112,10 @@ export class Interpreter {
}
Remove(root) {
let node = this.nodes[root];
this.listeners.removeAllNonBubbling(node);
if (node !== undefined) {
if (is_element_node(node)) {
this.listeners.removeAllNonBubbling(node);
}
node.remove();
}
}
@ -137,22 +143,12 @@ export class Interpreter {
NewEventListener(event_name, root, handler, bubbles) {
const element = this.nodes[root];
element.setAttribute("data-dioxus-id", `${root}`);
if (bubbles) {
this.listeners.createBubbling(event_name, handler);
}
else {
this.listeners.createNonBubbling(event_name, element, handler);
}
this.listeners.create(event_name, element, handler, bubbles);
}
RemoveEventListener(root, event_name, bubbles) {
const element = this.nodes[root];
element.removeAttribute(`data-dioxus-id`);
if (bubbles) {
this.listeners.removeBubbling(event_name)
}
else {
this.listeners.removeNonBubbling(element, event_name);
}
this.listeners.remove(element, event_name, bubbles);
}
SetText(root, text) {
this.nodes[root].textContent = text;
@ -388,6 +384,7 @@ export function serialize_event(event) {
location,
repeat,
which,
code,
} = event;
return {
char_code: charCode,
@ -400,7 +397,7 @@ export function serialize_event(event) {
location: location,
repeat: repeat,
which: which,
locale: "locale",
code,
};
}
case "focus":
@ -660,6 +657,10 @@ const bool_attrs = {
truespeed: true,
};
function is_element_node(node) {
return node.nodeType == 1;
}
function event_bubbles(event) {
switch (event) {
case "copy":

View file

@ -358,7 +358,6 @@ function serialize_event(event) {
location: location,
repeat: repeat,
which: which,
locale: "locale",
};
}
case "focus":

View file

@ -1,9 +1,9 @@
#![cfg(target_arch = "wasm32")]
#![allow(non_snake_case)]
use dioxus::prelude::*;
use dioxus_router::*;
use gloo_utils::document;
use serde::{Deserialize, Serialize};
use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser);
@ -47,7 +47,7 @@ fn simple_test() {
}
fn BlogPost(cx: Scope) -> Element {
let id = use_route(&cx).parse_segment::<usize>("id")?;
let _id = use_route(&cx).parse_segment::<usize>("id")?;
cx.render(rsx! {
div {
@ -58,5 +58,5 @@ fn simple_test() {
main();
let element = gloo_utils::document();
let _ = document();
}

View file

@ -113,9 +113,9 @@ impl ToTokens for CapturedContextBuilder {
let expr = segment.to_token_stream();
let as_string = expr.to_string();
let format_expr = if format_args.is_empty() {
"{".to_string() + format_args + "}"
"{".to_string() + &format_args + "}"
} else {
"{".to_string() + ":" + format_args + "}"
"{".to_string() + ":" + &format_args + "}"
};
Some(quote! {
FormattedArg{

View file

@ -1,3 +1,5 @@
use std::fmt::Display;
use serde::{Deserialize, Serialize};
use crate::CodeLocation;
@ -37,3 +39,18 @@ impl ParseError {
ParseError { message, location }
}
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::ParseError(error) => write!(
f,
"parse error:\n--> at {}:{}:{}\n\t{:?}\n",
error.location.file_path, error.location.line, error.location.column, error.message
),
Error::RecompileRequiredError(reason) => {
write!(f, "recompile required: {:?}\n", reason)
}
}
}
}

View file

@ -39,7 +39,7 @@ fn resolve_ifmt(ifmt: &IfmtInput, captured: &IfmtArgs) -> Result<String, Error>
}
}
}
Segment::Literal(lit) => result.push_str(lit),
Segment::Literal(lit) => result.push_str(&lit),
}
}
Ok(result)
@ -126,12 +126,12 @@ fn build_node<'a>(
ElementAttr::AttrExpression { .. }
| ElementAttr::CustomAttrExpression { .. } => {
let (name, value) = match &attr.attr {
let (name, value, span) = match &attr.attr {
ElementAttr::AttrExpression { name, value } => {
(name.to_string(), value)
(name.to_string(), value, name.span())
}
ElementAttr::CustomAttrExpression { name, value } => {
(name.value(), value)
(name.value(), value, name.span())
}
_ => unreachable!(),
};
@ -151,6 +151,14 @@ fn build_node<'a>(
is_volatile: false,
namespace,
});
} else {
return Err(Error::ParseError(ParseError::new(
syn::Error::new(
span,
format!("unknown attribute: {}", name),
),
ctx.location.clone(),
)));
}
} else {
return Err(Error::RecompileRequiredError(

View file

@ -150,6 +150,8 @@ impl RsxContext {
fn report_error(&self, error: Error) {
if let Some(handler) = &self.data.write().unwrap().error_handler {
handler.handle_error(error)
} else {
panic!("no error handler set for this platform...\n{}", error);
}
}

View file

@ -5,11 +5,13 @@ 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::geometry::{
ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint, WheelDelta,
};
use dioxus_html::input_data::keyboard_types::{Code, Key, Location, Modifiers};
use dioxus_html::input_data::MouseButtonSet as DioxusMouseButtons;
use dioxus_html::input_data::{MouseButton as DioxusMouseButton, MouseButtonSet};
use dioxus_html::{event_bubbles, on::*, KeyCode};
use dioxus_html::{event_bubbles, on::*};
use std::{
any::Any,
cell::{RefCell, RefMut},
@ -140,14 +142,20 @@ impl InnerInputState {
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
let is_repeating = self
.last_key_pressed
.as_ref()
.filter(|k2| k2.0.key == k.key && k2.1.elapsed() < MAX_REPEAT_TIME)
// heuristic for guessing which presses are auto-repeating. not necessarily accurate
.filter(|(last_data, last_instant)| {
last_data.key() == k.key() && last_instant.elapsed() < MAX_REPEAT_TIME
})
.is_some();
k.repeat = repeat;
let new = k.clone();
self.last_key_pressed = Some((new, Instant::now()));
if is_repeating {
*k = KeyboardData::new(k.key(), k.code(), k.location(), true, k.modifiers());
}
self.last_key_pressed = Some((k.clone(), Instant::now()));
}
}
}
@ -166,8 +174,10 @@ impl InnerInputState {
let old_focus = self.focus_state.last_focused_id;
evts.retain(|e| match &e.1 {
EventData::Keyboard(k) => match k.key_code {
KeyCode::Tab => !self.focus_state.progress(dom, !k.shift_key),
EventData::Keyboard(k) => match k.code() {
Code::Tab => !self
.focus_state
.progress(dom, !k.modifiers().contains(Modifiers::SHIFT)),
_ => true,
},
_ => true,
@ -293,7 +303,10 @@ impl InnerInputState {
// 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 was_scrolled = self
.wheel
.as_ref()
.map_or(false, |data| !data.delta().is_zero());
let wheel_data = &self.wheel;
{
@ -457,7 +470,7 @@ impl InnerInputState {
{
// wheel
if let Some(w) = wheel_data {
if wheel_delta != 0.0 {
if was_scrolled {
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("wheel") {
let node_layout = get_abs_layout(node, dom, layout);
@ -723,13 +736,8 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> {
};
let get_wheel_data = |up| {
// from https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent
EventData::Wheel(WheelData {
delta_mode: 0x01,
delta_x: 0.0,
delta_y: if up { -1.0 } else { 1.0 },
delta_z: 0.0,
})
let y = if up { -1.0 } else { 1.0 };
EventData::Wheel(WheelData::new(WheelDelta::lines(0., y, 0.)))
};
match m.kind {
@ -748,147 +756,222 @@ 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() {
'A' => KeyCode::A,
'B' => KeyCode::B,
'C' => KeyCode::C,
'D' => KeyCode::D,
'E' => KeyCode::E,
'F' => KeyCode::F,
'G' => KeyCode::G,
'H' => KeyCode::H,
'I' => KeyCode::I,
'J' => KeyCode::J,
'K' => KeyCode::K,
'L' => KeyCode::L,
'M' => KeyCode::M,
'N' => KeyCode::N,
'O' => KeyCode::O,
'P' => KeyCode::P,
'Q' => KeyCode::Q,
'R' => KeyCode::R,
'S' => KeyCode::S,
'T' => KeyCode::T,
'U' => KeyCode::U,
'V' => KeyCode::V,
'W' => KeyCode::W,
'X' => KeyCode::X,
'Y' => KeyCode::Y,
'Z' => KeyCode::Z,
_ => return None,
},
' ' => KeyCode::Space,
'[' => KeyCode::OpenBracket,
'{' => KeyCode::OpenBracket,
']' => KeyCode::CloseBraket,
'}' => KeyCode::CloseBraket,
';' => KeyCode::Semicolon,
':' => KeyCode::Semicolon,
',' => KeyCode::Comma,
'<' => KeyCode::Comma,
'.' => KeyCode::Period,
'>' => KeyCode::Period,
'1' => KeyCode::Num1,
'2' => KeyCode::Num2,
'3' => KeyCode::Num3,
'4' => KeyCode::Num4,
'5' => KeyCode::Num5,
'6' => KeyCode::Num6,
'7' => KeyCode::Num7,
'8' => KeyCode::Num8,
'9' => KeyCode::Num9,
'0' => KeyCode::Num0,
'!' => KeyCode::Num1,
'@' => KeyCode::Num2,
'#' => KeyCode::Num3,
'$' => KeyCode::Num4,
'%' => KeyCode::Num5,
'^' => KeyCode::Num6,
'&' => KeyCode::Num7,
'*' => KeyCode::Num8,
'(' => KeyCode::Num9,
')' => KeyCode::Num0,
// numpad charicter are ambiguous to tui
// '*' => KeyCode::Multiply,
// '/' => KeyCode::Divide,
// '-' => KeyCode::Subtract,
// '+' => KeyCode::Add,
'+' => KeyCode::EqualSign,
'-' => KeyCode::Dash,
'_' => KeyCode::Dash,
'\'' => KeyCode::SingleQuote,
'"' => KeyCode::SingleQuote,
'\\' => KeyCode::BackSlash,
'|' => KeyCode::BackSlash,
'/' => KeyCode::ForwardSlash,
'?' => KeyCode::ForwardSlash,
'=' => KeyCode::EqualSign,
'`' => KeyCode::GraveAccent,
'~' => KeyCode::GraveAccent,
_ => return None,
};
key_str = c.to_string();
} else {
code = match event.code {
TermKeyCode::Esc => KeyCode::Escape,
TermKeyCode::Backspace => KeyCode::Backspace,
TermKeyCode::Enter => KeyCode::Enter,
TermKeyCode::Left => KeyCode::LeftArrow,
TermKeyCode::Right => KeyCode::RightArrow,
TermKeyCode::Up => KeyCode::UpArrow,
TermKeyCode::Down => KeyCode::DownArrow,
TermKeyCode::Home => KeyCode::Home,
TermKeyCode::End => KeyCode::End,
TermKeyCode::PageUp => KeyCode::PageUp,
TermKeyCode::PageDown => KeyCode::PageDown,
TermKeyCode::Tab => KeyCode::Tab,
TermKeyCode::Delete => KeyCode::Delete,
TermKeyCode::Insert => KeyCode::Insert,
TermKeyCode::F(fn_num) => match fn_num {
1 => KeyCode::F1,
2 => KeyCode::F2,
3 => KeyCode::F3,
4 => KeyCode::F4,
5 => KeyCode::F5,
6 => KeyCode::F6,
7 => KeyCode::F7,
8 => KeyCode::F8,
9 => KeyCode::F9,
10 => KeyCode::F10,
11 => KeyCode::F11,
12 => KeyCode::F12,
_ => return None,
},
// backtab is Shift + Tab
TermKeyCode::BackTab => {
shift_key = true;
KeyCode::Tab
}
TermKeyCode::Null => return None,
_ => return None,
};
key_str = if let KeyCode::BackSlash = code {
"\\".to_string()
} else {
format!("{code:?}")
}
};
// from https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
Some(EventData::Keyboard(KeyboardData {
char_code: code.raw_code(),
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,
locale: Default::default(),
location: 0x00,
repeat: Default::default(),
which: Default::default(),
}))
let key = key_from_crossterm_key_code(event.code);
// crossterm does not provide code. we make a guess as to which key might have been pressed
// this is probably garbage if the user has a custom keyboard layout
let code = guess_code_from_crossterm_key_code(event.code)?;
let modifiers = modifiers_from_crossterm_modifiers(event.modifiers);
Some(EventData::Keyboard(KeyboardData::new(
key,
code,
Location::Standard,
false,
modifiers,
)))
}
/// The crossterm key_code nicely represents the meaning of the key and we can mostly convert it without any issues
///
/// Exceptions:
/// BackTab is converted to Key::Tab, and Null is converted to Key::Unidentified
fn key_from_crossterm_key_code(key_code: TermKeyCode) -> Key {
match key_code {
TermKeyCode::Backspace => Key::Backspace,
TermKeyCode::Enter => Key::Enter,
TermKeyCode::Left => Key::ArrowLeft,
TermKeyCode::Right => Key::ArrowRight,
TermKeyCode::Up => Key::ArrowUp,
TermKeyCode::Down => Key::ArrowDown,
TermKeyCode::Home => Key::Home,
TermKeyCode::End => Key::End,
TermKeyCode::PageUp => Key::PageUp,
TermKeyCode::PageDown => Key::PageDown,
TermKeyCode::Tab => Key::Tab,
// ? no corresponding Key
TermKeyCode::BackTab => Key::Tab,
TermKeyCode::Delete => Key::Delete,
TermKeyCode::Insert => Key::Insert,
TermKeyCode::F(1) => Key::F1,
TermKeyCode::F(2) => Key::F2,
TermKeyCode::F(3) => Key::F3,
TermKeyCode::F(4) => Key::F4,
TermKeyCode::F(5) => Key::F5,
TermKeyCode::F(6) => Key::F6,
TermKeyCode::F(7) => Key::F7,
TermKeyCode::F(8) => Key::F8,
TermKeyCode::F(9) => Key::F9,
TermKeyCode::F(10) => Key::F10,
TermKeyCode::F(11) => Key::F11,
TermKeyCode::F(12) => Key::F12,
TermKeyCode::F(13) => Key::F13,
TermKeyCode::F(14) => Key::F14,
TermKeyCode::F(15) => Key::F15,
TermKeyCode::F(16) => Key::F16,
TermKeyCode::F(17) => Key::F17,
TermKeyCode::F(18) => Key::F18,
TermKeyCode::F(19) => Key::F19,
TermKeyCode::F(20) => Key::F20,
TermKeyCode::F(21) => Key::F21,
TermKeyCode::F(22) => Key::F22,
TermKeyCode::F(23) => Key::F23,
TermKeyCode::F(24) => Key::F24,
TermKeyCode::F(other) => {
panic!("Unexpected function key: {other:?}")
}
TermKeyCode::Char(c) => Key::Character(c.to_string()),
TermKeyCode::Null => Key::Unidentified,
TermKeyCode::Esc => Key::Escape,
}
}
// Crossterm does not provide a way to get the `code` (physical key on keyboard)
// So we make a guess based on their `key_code`, but this is probably going to break on anything other than a very standard european keyboard
// It may look fine, but it's a horrible hack. But there's nothing better we can do.
fn guess_code_from_crossterm_key_code(key_code: TermKeyCode) -> Option<Code> {
let code = match key_code {
TermKeyCode::Backspace => Code::Backspace,
TermKeyCode::Enter => Code::Enter,
TermKeyCode::Left => Code::ArrowLeft,
TermKeyCode::Right => Code::ArrowRight,
TermKeyCode::Up => Code::ArrowUp,
TermKeyCode::Down => Code::ArrowDown,
TermKeyCode::Home => Code::Home,
TermKeyCode::End => Code::End,
TermKeyCode::PageUp => Code::PageUp,
TermKeyCode::PageDown => Code::PageDown,
TermKeyCode::Tab => Code::Tab,
// ? Apparently you get BackTab by pressing Tab
TermKeyCode::BackTab => Code::Tab,
TermKeyCode::Delete => Code::Delete,
TermKeyCode::Insert => Code::Insert,
TermKeyCode::F(1) => Code::F1,
TermKeyCode::F(2) => Code::F2,
TermKeyCode::F(3) => Code::F3,
TermKeyCode::F(4) => Code::F4,
TermKeyCode::F(5) => Code::F5,
TermKeyCode::F(6) => Code::F6,
TermKeyCode::F(7) => Code::F7,
TermKeyCode::F(8) => Code::F8,
TermKeyCode::F(9) => Code::F9,
TermKeyCode::F(10) => Code::F10,
TermKeyCode::F(11) => Code::F11,
TermKeyCode::F(12) => Code::F12,
TermKeyCode::F(13) => Code::F13,
TermKeyCode::F(14) => Code::F14,
TermKeyCode::F(15) => Code::F15,
TermKeyCode::F(16) => Code::F16,
TermKeyCode::F(17) => Code::F17,
TermKeyCode::F(18) => Code::F18,
TermKeyCode::F(19) => Code::F19,
TermKeyCode::F(20) => Code::F20,
TermKeyCode::F(21) => Code::F21,
TermKeyCode::F(22) => Code::F22,
TermKeyCode::F(23) => Code::F23,
TermKeyCode::F(24) => Code::F24,
TermKeyCode::F(other) => {
panic!("Unexpected function key: {other:?}")
}
// this is a horrible way for crossterm to represent keys but we have to deal with it
TermKeyCode::Char(c) => match c {
'A'..='Z' | 'a'..='z' => match c.to_ascii_uppercase() {
'A' => Code::KeyA,
'B' => Code::KeyB,
'C' => Code::KeyC,
'D' => Code::KeyD,
'E' => Code::KeyE,
'F' => Code::KeyF,
'G' => Code::KeyG,
'H' => Code::KeyH,
'I' => Code::KeyI,
'J' => Code::KeyJ,
'K' => Code::KeyK,
'L' => Code::KeyL,
'M' => Code::KeyM,
'N' => Code::KeyN,
'O' => Code::KeyO,
'P' => Code::KeyP,
'Q' => Code::KeyQ,
'R' => Code::KeyR,
'S' => Code::KeyS,
'T' => Code::KeyT,
'U' => Code::KeyU,
'V' => Code::KeyV,
'W' => Code::KeyW,
'X' => Code::KeyX,
'Y' => Code::KeyY,
'Z' => Code::KeyZ,
_ => unreachable!("Exhaustively checked all characters in range A..Z"),
},
' ' => Code::Space,
'[' | '{' => Code::BracketLeft,
']' | '}' => Code::BracketRight,
';' => Code::Semicolon,
':' => Code::Semicolon,
',' => Code::Comma,
'<' => Code::Comma,
'.' => Code::Period,
'>' => Code::Period,
'1' => Code::Digit1,
'2' => Code::Digit2,
'3' => Code::Digit3,
'4' => Code::Digit4,
'5' => Code::Digit5,
'6' => Code::Digit6,
'7' => Code::Digit7,
'8' => Code::Digit8,
'9' => Code::Digit9,
'0' => Code::Digit0,
'!' => Code::Digit1,
'@' => Code::Digit2,
'#' => Code::Digit3,
'$' => Code::Digit4,
'%' => Code::Digit5,
'^' => Code::Digit6,
'&' => Code::Digit7,
'*' => Code::Digit8,
'(' => Code::Digit9,
')' => Code::Digit0,
// numpad characters are ambiguous; we don't know which key was really pressed
// it could be also:
// '*' => Code::Multiply,
// '/' => Code::Divide,
// '-' => Code::Subtract,
// '+' => Code::Add,
'+' => Code::Equal,
'-' | '_' => Code::Minus,
'\'' => Code::Quote,
'"' => Code::Quote,
'\\' => Code::Backslash,
'|' => Code::Backslash,
'/' => Code::Slash,
'?' => Code::Slash,
'=' => Code::Equal,
'`' => Code::Backquote,
'~' => Code::Backquote,
_ => return None,
},
TermKeyCode::Null => return None,
TermKeyCode::Esc => Code::Escape,
};
Some(code)
}
fn modifiers_from_crossterm_modifiers(src: KeyModifiers) -> Modifiers {
let mut modifiers = Modifiers::empty();
if src.contains(KeyModifiers::SHIFT) {
modifiers.insert(Modifiers::SHIFT);
}
if src.contains(KeyModifiers::ALT) {
modifiers.insert(Modifiers::ALT);
}
if src.contains(KeyModifiers::CONTROL) {
modifiers.insert(Modifiers::CONTROL);
}
modifiers
}

View file

@ -1,5 +1,6 @@
use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent};
use dioxus::prelude::*;
use dioxus_html::input_data::keyboard_types::Code;
use dioxus_tui::TuiContext;
use std::future::Future;
use std::pin::Pin;
@ -56,7 +57,7 @@ fn key_down() {
width: "100%",
height: "100%",
onkeydown: move |evt| {
assert_eq!(evt.data.key_code, dioxus_html::KeyCode::A);
assert_eq!(evt.data.code(), Code::KeyA);
tui_ctx.quit();
},
}
@ -286,7 +287,7 @@ fn wheel() {
width: "100%",
height: "100%",
onwheel: move |evt| {
assert!(evt.data.delta_y > 0.0);
assert!(evt.data.delta().strip_units().y > 0.0);
tui_ctx.quit();
},
}

View file

@ -62,6 +62,7 @@ use dioxus_core::prelude::Component;
use dioxus_core::SchedulerMsg;
use dioxus_core::VirtualDom;
use futures_util::FutureExt;
use web_sys::console;
mod cache;
mod cfg;
@ -172,50 +173,6 @@ pub fn launch_with_props<T>(
pub async fn run_with_props<T: 'static + Send>(root: Component<T>, root_props: T, cfg: WebConfig) {
let mut dom = VirtualDom::new_with_props(root, root_props);
for s in crate::cache::BUILTIN_INTERNED_STRINGS {
wasm_bindgen::intern(s);
}
for s in &cfg.cached_strings {
wasm_bindgen::intern(s);
}
let tasks = dom.get_scheduler_channel();
let sender_callback: Rc<dyn Fn(SchedulerMsg)> =
Rc::new(move |event| tasks.unbounded_send(event).unwrap());
let should_hydrate = cfg.hydrate;
let mut websys_dom = dom::WebsysDom::new(cfg, sender_callback);
log::trace!("rebuilding app");
if should_hydrate {
// todo: we need to split rebuild and initialize into two phases
// it's a waste to produce edits just to get the vdom loaded
let _ = dom.rebuild();
if let Err(err) = websys_dom.rehydrate(&dom) {
log::error!(
"Rehydration failed {:?}. Rebuild DOM into element from scratch",
&err
);
websys_dom.root.set_text_content(None);
// errrrr we should split rebuild into two phases
// one that initializes things and one that produces edits
let edits = dom.rebuild();
websys_dom.apply_edits(edits.edits);
}
} else {
let edits = dom.rebuild();
websys_dom.apply_edits(edits.edits);
}
let mut work_loop = ric_raf::RafLoop::new();
#[cfg(feature = "hot-reload")]
{
use dioxus_rsx_interpreter::error::Error;
@ -274,12 +231,61 @@ pub async fn run_with_props<T: 'static + Send>(root: Component<T>, root_props: T
// forward stream to the websocket
dom.base_scope().spawn_forever(async move {
while let Some(err) = error_channel_receiver.next().await {
ws.send_with_str(serde_json::to_string(&err).unwrap().as_str())
.unwrap();
if ws.ready_state() == WebSocket::OPEN {
ws.send_with_str(serde_json::to_string(&err).unwrap().as_str())
.unwrap();
} else {
console::warn_1(&"WebSocket is not open, cannot send error. Run with dioxus serve --hot-reload to enable hot reloading.".into());
panic!("{}", err);
}
}
});
}
for s in crate::cache::BUILTIN_INTERNED_STRINGS {
wasm_bindgen::intern(s);
}
for s in &cfg.cached_strings {
wasm_bindgen::intern(s);
}
let tasks = dom.get_scheduler_channel();
let sender_callback: Rc<dyn Fn(SchedulerMsg)> =
Rc::new(move |event| tasks.unbounded_send(event).unwrap());
let should_hydrate = cfg.hydrate;
let mut websys_dom = dom::WebsysDom::new(cfg, sender_callback);
log::trace!("rebuilding app");
if should_hydrate {
// todo: we need to split rebuild and initialize into two phases
// it's a waste to produce edits just to get the vdom loaded
let _ = dom.rebuild();
if let Err(err) = websys_dom.rehydrate(&dom) {
log::error!(
"Rehydration failed {:?}. Rebuild DOM into element from scratch",
&err
);
websys_dom.root.set_text_content(None);
// errrrr we should split rebuild into two phases
// one that initializes things and one that produces edits
let edits = dom.rebuild();
websys_dom.apply_edits(edits.edits);
}
} else {
let edits = dom.rebuild();
websys_dom.apply_edits(edits.edits);
}
let mut work_loop = ric_raf::RafLoop::new();
loop {
log::trace!("waiting for work");
// if virtualdom has nothing, wait for it to have something before requesting idle time

View file

@ -521,7 +521,6 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc<dyn Any + Send
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(),