mirror of
https://github.com/DioxusLabs/dioxus
synced 2025-02-17 06:08:26 +00:00
Merge remote-tracking branch 'upstream/master' into pr/Demonthos/473
This commit is contained in:
commit
55e262b2c6
64 changed files with 1123 additions and 765 deletions
|
@ -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>
|
||||
|
||||
|
|
|
@ -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) -->
|
||||
|
||||
|
||||
|
|
|
@ -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() }
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
# Fetching
|
||||
|
|
@ -1 +0,0 @@
|
|||
# WebSockets
|
|
@ -1 +0,0 @@
|
|||
# Channels
|
|
@ -1 +0,0 @@
|
|||
# Advanced Guides
|
|
@ -1,3 +0,0 @@
|
|||
# Defining Components
|
||||
|
||||
This section is currently under construction! 🏗
|
|
@ -1,3 +0,0 @@
|
|||
# Defining State
|
||||
|
||||
This section is currently under construction! 🏗
|
|
@ -1,4 +0,0 @@
|
|||
# Structuring our app
|
||||
|
||||
|
||||
This section is currently under construction! 🏗
|
|
@ -1,4 +0,0 @@
|
|||
# Styling
|
||||
|
||||
|
||||
This section is currently under construction! 🏗
|
|
@ -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)
|
|
@ -1 +0,0 @@
|
|||
# Components
|
|
@ -1 +0,0 @@
|
|||
# Memoization
|
|
@ -1 +0,0 @@
|
|||
# Performance
|
|
@ -1 +0,0 @@
|
|||
# Props
|
|
@ -1 +0,0 @@
|
|||
# RSX
|
|
@ -1 +0,0 @@
|
|||
# Testing
|
|
@ -1 +0,0 @@
|
|||
# Topics in Depth
|
|
@ -1 +0,0 @@
|
|||
# Bundling and Distributing
|
|
@ -1 +0,0 @@
|
|||
# Components
|
|
@ -1 +0,0 @@
|
|||
# Custom Elements
|
|
@ -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(),
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
# Memoization
|
|
@ -1 +0,0 @@
|
|||
# Performance
|
|
@ -1 +0,0 @@
|
|||
# Props
|
|
@ -1 +0,0 @@
|
|||
# Building Elements with NodeFactory
|
|
@ -1 +0,0 @@
|
|||
# Server-side components
|
|
@ -1 +0,0 @@
|
|||
# Testing
|
|
@ -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
85
examples/all_events.rs
Normal 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 },
|
||||
},
|
||||
))
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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!')"
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
|
@ -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}"},
|
||||
}
|
||||
))
|
||||
}
|
|
@ -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!"}),
|
||||
})
|
||||
}
|
|
@ -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),
|
||||
_ => {}
|
||||
},
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
_ => {}
|
||||
}
|
||||
},
|
||||
|
|
85
examples/tui_all_events.rs
Normal file
85
examples/tui_all_events.rs
Normal 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,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -86,6 +86,8 @@ impl DesktopController {
|
|||
(serde_json::to_string(&err).unwrap() + "\n").as_bytes(),
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
panic!("{}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>>();
|
||||
|
|
|
@ -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())),
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -358,7 +358,6 @@ function serialize_event(event) {
|
|||
location: location,
|
||||
repeat: repeat,
|
||||
which: which,
|
||||
locale: "locale",
|
||||
};
|
||||
}
|
||||
case "focus":
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
|
|
Loading…
Add table
Reference in a new issue