mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-23 12:43:08 +00:00
Merge pull request #1939 from DioxusLabs/jk/examples-overhaul
Add more examples, document examples, add css to most examples
This commit is contained in:
commit
77c80ea715
76 changed files with 1414 additions and 916 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2558,6 +2558,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"warp",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -125,6 +125,7 @@ publish = false
|
|||
manganis = { workspace = true, optional = true}
|
||||
reqwest = { version = "0.11.9", features = ["json"], optional = true}
|
||||
http-range = {version = "0.1.5", optional = true }
|
||||
warp = { version = "0.3.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
dioxus = { workspace = true, features = ["router"] }
|
||||
|
@ -154,7 +155,7 @@ server = ["dioxus/axum"]
|
|||
default = ["dioxus/desktop"]
|
||||
web = ["dioxus/web"]
|
||||
collect-assets = ["manganis"]
|
||||
http = ["reqwest", "http-range"]
|
||||
http = ["reqwest", "http-range", "warp"]
|
||||
|
||||
[[example]]
|
||||
name = "login_form"
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
//! This example shows how to listen to all events on a div and log them to the console.
|
||||
//!
|
||||
//! The primary demonstration here is the properties on the events themselves, hoping to give you some inspiration
|
||||
//! on adding interactivity to your own application.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use std::{collections::VecDeque, fmt::Debug, rc::Rc};
|
||||
|
||||
|
@ -5,41 +10,24 @@ fn main() {
|
|||
launch(app);
|
||||
}
|
||||
|
||||
const MAX_EVENTS: usize = 8;
|
||||
|
||||
const CONTAINER_STYLE: &str = r#"
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
"#;
|
||||
|
||||
const RECT_STYLE: &str = r#"
|
||||
background: deepskyblue;
|
||||
height: 50vh;
|
||||
width: 50vw;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
margin: 20px;
|
||||
text-aligh: center;
|
||||
"#;
|
||||
|
||||
fn app() -> Element {
|
||||
let mut events = use_signal(|| VecDeque::new() as VecDeque<Rc<dyn Debug>>);
|
||||
// Using a VecDeque so its cheap to pop old events off the front
|
||||
let mut events = use_signal(VecDeque::new);
|
||||
|
||||
// All events and their data implement Debug, so we can re-cast them as Rc<dyn Debug> instead of their specific type
|
||||
let mut log_event = move |event: Rc<dyn Debug>| {
|
||||
let mut events = events.write();
|
||||
|
||||
if events.len() >= MAX_EVENTS {
|
||||
events.pop_front();
|
||||
// Only store the last 20 events
|
||||
if events.read().len() >= 20 {
|
||||
events.write().pop_front();
|
||||
}
|
||||
|
||||
events.push_back(event);
|
||||
events.write().push_back(event);
|
||||
};
|
||||
|
||||
rsx! {
|
||||
div { style: "{CONTAINER_STYLE}",
|
||||
style { {include_str!("./assets/events.css")} }
|
||||
div { id: "container",
|
||||
// focusing is necessary to catch keyboard events
|
||||
div { style: "{RECT_STYLE}", tabindex: 0,
|
||||
div { id: "receiver", tabindex: 0,
|
||||
onmousemove: move |event| log_event(event.data()),
|
||||
onclick: move |event| log_event(event.data()),
|
||||
ondoubleclick: move |event| log_event(event.data()),
|
||||
|
@ -57,7 +45,7 @@ fn app() -> Element {
|
|||
|
||||
"Hover, click, type or scroll to see the info down below"
|
||||
}
|
||||
div {
|
||||
div { id: "log",
|
||||
for event in events.read().iter() {
|
||||
div { "{event:?}" }
|
||||
}
|
||||
|
|
23
examples/assets/clock.css
Normal file
23
examples/assets/clock.css
Normal file
|
@ -0,0 +1,23 @@
|
|||
html body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100vh;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
}
|
||||
|
||||
#app {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background-color: plum;
|
||||
font-size: 6em;
|
||||
color: aliceblue;
|
||||
}
|
||||
|
||||
#title {
|
||||
font-size: 0.5em;
|
||||
color: black;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
26
examples/assets/counter.css
Normal file
26
examples/assets/counter.css
Normal file
|
@ -0,0 +1,26 @@
|
|||
html body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 20px;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
font-size: 2rem;
|
||||
height: 100vh;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
#controls {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 5px 10px;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 50px;
|
||||
}
|
16
examples/assets/crm.css
Normal file
16
examples/assets/crm.css
Normal file
|
@ -0,0 +1,16 @@
|
|||
body {
|
||||
background-color: #f4f4f4;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.42857143;
|
||||
color: #333;
|
||||
margin: 20px;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.red {
|
||||
background-color: rgb(202, 60, 60) !important;
|
||||
}
|
12
examples/assets/custom_assets.css
Normal file
12
examples/assets/custom_assets.css
Normal file
|
@ -0,0 +1,12 @@
|
|||
body {
|
||||
background-color: #f0f0f0;
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
0
examples/assets/dog_app.css
Normal file
0
examples/assets/dog_app.css
Normal file
24
examples/assets/events.css
Normal file
24
examples/assets/events.css
Normal file
|
@ -0,0 +1,24 @@
|
|||
#container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#receiver {
|
||||
background: deepskyblue;
|
||||
height: 30vh;
|
||||
width: 80vw;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
margin: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#log {
|
||||
background: lightgray;
|
||||
padding: 20px;
|
||||
margin: 20px;
|
||||
overflow-y: scroll;
|
||||
align-items: start;
|
||||
text-align: left;
|
||||
}
|
22
examples/assets/file_upload.css
Normal file
22
examples/assets/file_upload.css
Normal file
|
@ -0,0 +1,22 @@
|
|||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #f4f4f4;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
#drop-zone {
|
||||
border: 2px dashed #ccc;
|
||||
border-radius: 3px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
margin: 20px;
|
||||
background-color: rgba(225, 124, 225, 0);
|
||||
}
|
40
examples/assets/flat_router.css
Normal file
40
examples/assets/flat_router.css
Normal file
|
@ -0,0 +1,40 @@
|
|||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 20px;
|
||||
padding: 20px;
|
||||
background-color: #f4f4f4;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.nav-btn {
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
|
||||
a {
|
||||
padding: 10px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* button hover effect */
|
||||
a:hover {
|
||||
background-color: #dd6a6a;
|
||||
}
|
||||
|
||||
#content {
|
||||
border: 2px dashed #ccc;
|
||||
padding-top: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
12
examples/assets/links.css
Normal file
12
examples/assets/links.css
Normal file
|
@ -0,0 +1,12 @@
|
|||
#external-links {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#nav {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
background-color: #f4f4f4;
|
||||
}
|
43
examples/assets/radio.css
Normal file
43
examples/assets/radio.css
Normal file
|
@ -0,0 +1,43 @@
|
|||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button {
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
background-color: #f0f0f0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#pause {
|
||||
background-color: #ff0000;
|
||||
}
|
||||
|
||||
#play {
|
||||
background-color: #00ff00;
|
||||
}
|
||||
|
||||
|
||||
.bounce {
|
||||
animation: boomBox 0.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes boomBox {
|
||||
0% {
|
||||
transform: scale(1.0);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(2);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1.0);
|
||||
}
|
||||
}
|
29
examples/assets/roulette.css
Normal file
29
examples/assets/roulette.css
Normal file
|
@ -0,0 +1,29 @@
|
|||
#main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#roulette-grid {
|
||||
margin: 10px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(9, 1fr);
|
||||
grid-gap: 10px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
#roulette-grid>input {
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
#roulette-grid>input:nth-child(odd) {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
#roulette-grid>input:nth-child(even) {
|
||||
background-color: black;
|
||||
}
|
13
examples/assets/router.css
Normal file
13
examples/assets/router.css
Normal file
|
@ -0,0 +1,13 @@
|
|||
#navbar {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#blog-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
gap: 1rem;
|
||||
}
|
|
@ -1,3 +1,12 @@
|
|||
//! Backgrounded futures example
|
||||
//!
|
||||
//! This showcases how use_future, use_memo, and use_effect will stop running if the component returns early.
|
||||
//! Generally you should avoid using early returns around hooks since most hooks are not properly designed to
|
||||
//! handle early returns. However, use_future *does* pause the future when the component returns early, and so
|
||||
//! hooks that build on top of it like use_memo and use_effect will also pause.
|
||||
//!
|
||||
//! This example is more of a demonstration of the behavior than a practical use case, but it's still interesting to see.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
|
@ -10,17 +19,17 @@ fn app() -> Element {
|
|||
|
||||
let child = use_memo(move || {
|
||||
rsx! {
|
||||
Child {
|
||||
count
|
||||
}
|
||||
Child { count }
|
||||
}
|
||||
});
|
||||
|
||||
rsx! {
|
||||
// Some toggle/controls to show the child or increment the count
|
||||
button { onclick: move |_| show_child.toggle(), "Toggle child" }
|
||||
button { onclick: move |_| count += 1, "Increment count" }
|
||||
|
||||
if show_child() {
|
||||
{child.cloned()}
|
||||
{child()}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,12 +53,12 @@ fn Child(count: Signal<i32>) -> Element {
|
|||
}
|
||||
});
|
||||
|
||||
use_effect(move || {
|
||||
println!("Child count: {}", count());
|
||||
});
|
||||
use_effect(move || println!("Child count: {}", count()));
|
||||
|
||||
rsx! {
|
||||
"hellO!"
|
||||
{early}
|
||||
div {
|
||||
"Child component"
|
||||
{early}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
/*
|
||||
This example is a simple iOS-style calculator. This particular example can run any platform - Web, Mobile, Desktop.
|
||||
This calculator version uses React-style state management. All state is held as individual use_states.
|
||||
*/
|
||||
//! Calculator
|
||||
//!
|
||||
//! This example is a simple iOS-style calculator. Instead of wrapping the state in a single struct like the
|
||||
//! `calculate_mutable` example, this example uses several closures to manage actions with the state. Most
|
||||
//! components will start like this since it's the quickest way to start adding state to your app. The `Signal` type
|
||||
//! in Dioxus is `Copy` - meaning you don't need to clone it to use it in a closure.
|
||||
//!
|
||||
//! Notice how our logic is consolidated into just a few callbacks instead of a single struct. This is a rather organic
|
||||
//! way to start building state management in Dioxus, and it's a great way to start.
|
||||
|
||||
use dioxus::events::*;
|
||||
use dioxus::html::input_data::keyboard_types::Key;
|
||||
|
|
|
@ -1,38 +1,28 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
//! Example: Calculator
|
||||
//! -------------------
|
||||
//! This example showcases a simple calculator using an approach to state management where the state is composed of only
|
||||
//! a single signal. Since Dioxus implements traditional React diffing, state can be consolidated into a typical Rust struct
|
||||
//! with methods that take `&mut self`. For many use cases, this is a simple way to manage complex state without wrapping
|
||||
//! everything in a signal.
|
||||
//!
|
||||
//! Some components benefit through the use of "Models". Models are a single block of encapsulated state that allow mutative
|
||||
//! methods to be performed on them. Dioxus exposes the ability to use the model pattern through the "use_model" hook.
|
||||
//!
|
||||
//! Models are commonly used in the "Model-View-Component" approach for building UI state.
|
||||
//!
|
||||
//! `use_model` is basically just a fancy wrapper around set_state, but saves a "working copy" of the new state behind a
|
||||
//! RefCell. To modify the working copy, you need to call "get_mut" which returns the RefMut. This makes it easy to write
|
||||
//! fully encapsulated apps that retain a certain feel of native Rusty-ness. A calculator app is a good example of when this
|
||||
//! is useful.
|
||||
//!
|
||||
//! Do note that "get_mut" returns a `RefMut` (a lock over a RefCell). If two `RefMut`s are held at the same time (ie in a loop)
|
||||
//! the RefCell will panic and crash. You can use `try_get_mut` or `.modify` to avoid this problem, or just not hold two
|
||||
//! RefMuts at the same time.
|
||||
//! Generally, you'll want to split your state into several signals if you have a large application, but for small
|
||||
//! applications, or focused components, this is a great way to manage state.
|
||||
|
||||
use dioxus::desktop::tao::dpi::LogicalSize;
|
||||
use dioxus::desktop::{Config, WindowBuilder};
|
||||
use dioxus::events::*;
|
||||
use dioxus::html::input_data::keyboard_types::Key;
|
||||
use dioxus::html::MouseEvent;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let cfg = Config::new().with_window(
|
||||
WindowBuilder::new()
|
||||
.with_title("Calculator Demo")
|
||||
.with_resizable(false)
|
||||
.with_inner_size(LogicalSize::new(320.0, 530.0)),
|
||||
);
|
||||
|
||||
LaunchBuilder::desktop().with_cfg(cfg).launch(app);
|
||||
LaunchBuilder::desktop()
|
||||
.with_cfg(
|
||||
Config::new().with_window(
|
||||
WindowBuilder::new()
|
||||
.with_title("Calculator Demo")
|
||||
.with_resizable(false)
|
||||
.with_inner_size(LogicalSize::new(320.0, 530.0)),
|
||||
),
|
||||
)
|
||||
.launch(app);
|
||||
}
|
||||
|
||||
const STYLE: &str = include_str!("./assets/calculator.css");
|
||||
|
@ -109,6 +99,7 @@ struct Calculator {
|
|||
waiting_for_operand: bool,
|
||||
cur_val: f64,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Operator {
|
||||
Add,
|
||||
|
@ -116,6 +107,7 @@ enum Operator {
|
|||
Mul,
|
||||
Div,
|
||||
}
|
||||
|
||||
impl Calculator {
|
||||
fn new() -> Self {
|
||||
Calculator {
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
//! A simple little clock that updates the time every few milliseconds.
|
||||
//!
|
||||
//! Neither Rust nor Tokio have an interval function, so we just sleep until the next update.
|
||||
//! Tokio timer's don't work on WASM though, so you'll need to use a slightly different approach if you're targeting the web.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
|
@ -5,20 +10,36 @@ fn main() {
|
|||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut count = use_signal(|| 0);
|
||||
let mut millis = use_signal(|| 0);
|
||||
|
||||
use_future(move || async move {
|
||||
// Save our initial timea
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
loop {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
count += 1;
|
||||
// In lieu of an interval, we just sleep until the next update
|
||||
let now = tokio::time::Instant::now();
|
||||
tokio::time::sleep_until(now + std::time::Duration::from_millis(27)).await;
|
||||
|
||||
// Update the time, using a more precise approach of getting the duration since we started the timer
|
||||
millis.set(start.elapsed().as_millis() as i64);
|
||||
}
|
||||
});
|
||||
|
||||
use_effect(move || {
|
||||
println!("High-Five counter: {}", count());
|
||||
});
|
||||
// Format the time as a string
|
||||
// This is rather cheap so it's fine to leave it in the render function
|
||||
let time = format!(
|
||||
"{:02}:{:02}:{:03}",
|
||||
millis() / 1000 / 60 % 60,
|
||||
millis() / 1000 % 60,
|
||||
millis() % 1000
|
||||
);
|
||||
|
||||
rsx! {
|
||||
div { "High-Five counter: {count}" }
|
||||
style { {include_str!("./assets/clock.css")} }
|
||||
div { id: "app",
|
||||
div { id: "title", "Carpe diem 🎉" }
|
||||
div { id: "clock-display", "{time}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
//! Managing focus
|
||||
//!
|
||||
//! This example shows how to manage focus in a Dioxus application. We implement a "roulette" that focuses on each input
|
||||
//! in the grid every few milliseconds until the user interacts with the inputs.
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
@ -7,6 +12,7 @@ fn main() {
|
|||
}
|
||||
|
||||
fn app() -> Element {
|
||||
// Element data is stored as Rc<MountedData> so we can clone it and pass it around
|
||||
let mut elements = use_signal(Vec::<Rc<MountedData>>::new);
|
||||
let mut running = use_signal(|| true);
|
||||
|
||||
|
@ -14,7 +20,7 @@ fn app() -> Element {
|
|||
let mut focused = 0;
|
||||
|
||||
loop {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
||||
|
||||
if !running() {
|
||||
continue;
|
||||
|
@ -31,17 +37,24 @@ fn app() -> Element {
|
|||
});
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
h1 { "Input Roulette" }
|
||||
style { {include_str!("./assets/roulette.css")} }
|
||||
h1 { "Input Roulette" }
|
||||
button { onclick: move |_| running.toggle(), "Toggle roulette" }
|
||||
div { id: "roulette-grid",
|
||||
// Restart the roulette if the user presses escape
|
||||
onkeydown: move |event| {
|
||||
if event.code().to_string() == "Escape" {
|
||||
running.set(true);
|
||||
}
|
||||
},
|
||||
|
||||
// Draw the grid of inputs
|
||||
for i in 0..100 {
|
||||
input {
|
||||
r#type: "number",
|
||||
value: "{i}",
|
||||
onmounted: move |cx| {
|
||||
elements.write().push(cx.data());
|
||||
},
|
||||
oninput: move |_| {
|
||||
running.set(false);
|
||||
}
|
||||
onmounted: move |cx| elements.write().push(cx.data()),
|
||||
oninput: move |_| running.set(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
//! Comparison example with leptos' counter example
|
||||
//! https://github.com/leptos-rs/leptos/blob/main/examples/counters/src/lib.rs
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut counters = use_signal(|| vec![0, 0, 0]);
|
||||
let sum = use_memo(move || counters.read().iter().copied().sum::<i32>());
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
button { onclick: move |_| counters.write().push(0), "Add counter" }
|
||||
button {
|
||||
onclick: move |_| {
|
||||
counters.write().pop();
|
||||
},
|
||||
"Remove counter"
|
||||
}
|
||||
p { "Total: {sum}" }
|
||||
for i in 0..counters.len() {
|
||||
Child { i, counters }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Child(counters: Signal<Vec<i32>>, i: usize) -> Element {
|
||||
rsx! {
|
||||
li {
|
||||
button { onclick: move |_| counters.write()[i] -= 1, "-1" }
|
||||
input {
|
||||
value: "{counters.read()[i]}",
|
||||
oninput: move |e| {
|
||||
if let Ok(value) = e.value().parse::<i32>() {
|
||||
counters.write()[i] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
button { onclick: move |_| counters.write()[i] += 1, "+1" }
|
||||
button {
|
||||
onclick: move |_| {
|
||||
counters.write().remove(i);
|
||||
},
|
||||
"x"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
52
examples/counters.rs
Normal file
52
examples/counters.rs
Normal file
|
@ -0,0 +1,52 @@
|
|||
//! A simple counters example that stores a list of items in a vec and then iterates over them.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
// Store the counters in a signal
|
||||
let mut counters = use_signal(|| vec![0, 0, 0]);
|
||||
|
||||
// Whenver the counters change, sum them up
|
||||
let sum = use_memo(move || counters.read().iter().copied().sum::<i32>());
|
||||
|
||||
rsx! {
|
||||
style { {include_str!("./assets/counter.css")} }
|
||||
|
||||
div { id: "controls",
|
||||
button { onclick: move |_| counters.write().push(0), "Add counter" }
|
||||
button { onclick: move |_| { counters.write().pop(); }, "Remove counter" }
|
||||
}
|
||||
|
||||
h3 { "Total: {sum}" }
|
||||
|
||||
// Calling `iter` on a Signal<Vec<>> gives you a GenerationalRef to each entry in the vec
|
||||
// We enumerate to get the idx of each counter, which we use later to modify the vec
|
||||
for (i, counter) in counters.iter().enumerate() {
|
||||
// We need a key to uniquely identify each counter. You really shouldn't be using the index, so we're using
|
||||
// the counter value itself.
|
||||
//
|
||||
// If we used the index, and a counter is removed, dioxus would need to re-write the contents of all following
|
||||
// counters instead of simply removing the one that was removed
|
||||
//
|
||||
// You should use a stable identifier for the key, like a unique id or the value of the counter itself
|
||||
li { key: "{i}",
|
||||
button { onclick: move |_| counters.write()[i] -= 1, "-1" }
|
||||
input {
|
||||
r#type: "number",
|
||||
value: "{counter}",
|
||||
oninput: move |e| {
|
||||
if let Ok(value) = e.parsed() {
|
||||
counters.write()[i] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
button { onclick: move |_| counters.write()[i] += 1, "+1" }
|
||||
button { onclick: move |_| { counters.write().remove(i); }, "x" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,14 @@
|
|||
//! Tiny CRM: A port of the Yew CRM example to Dioxus.
|
||||
//! Tiny CRM - A simple CRM app using the Router component and global signals
|
||||
//!
|
||||
//! This shows how to use the `Router` component to manage different views in your app. It also shows how to use global
|
||||
//! signals to manage state across the entire app.
|
||||
//!
|
||||
//! We could simply pass the state as a prop to each component, but this is a good example of how to use global state
|
||||
//! in a way that works across pages.
|
||||
//!
|
||||
//! We implement a number of important details here too, like focusing inputs, handling form submits, navigating the router,
|
||||
//! platform-specific configuration, and importing 3rd party CSS libaries.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
|
@ -16,7 +26,7 @@ fn main() {
|
|||
integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5",
|
||||
crossorigin: "anonymous"
|
||||
}
|
||||
style { {r#" .red { background-color: rgb(202, 60, 60) !important; } "#} }
|
||||
style { {include_str!("./assets/crm.css")} }
|
||||
h1 { "Dioxus CRM Example" }
|
||||
Router::<Route> {}
|
||||
}
|
||||
|
@ -32,23 +42,24 @@ struct Client {
|
|||
description: String,
|
||||
}
|
||||
|
||||
/// The pages of the app, each with a route
|
||||
#[derive(Routable, Clone)]
|
||||
enum Route {
|
||||
#[route("/")]
|
||||
ClientList,
|
||||
List,
|
||||
|
||||
#[route("/new")]
|
||||
ClientAdd,
|
||||
New,
|
||||
|
||||
#[route("/settings")]
|
||||
Settings,
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn ClientList() -> Element {
|
||||
fn List() -> Element {
|
||||
rsx! {
|
||||
h2 { "List of Clients" }
|
||||
Link { to: Route::ClientAdd, class: "pure-button pure-button-primary", "Add Client" }
|
||||
Link { to: Route::New, class: "pure-button pure-button-primary", "Add Client" }
|
||||
Link { to: Route::Settings, class: "pure-button", "Settings" }
|
||||
for client in CLIENTS.read().iter() {
|
||||
div { class: "client", style: "margin-bottom: 50px",
|
||||
|
@ -60,12 +71,12 @@ fn ClientList() -> Element {
|
|||
}
|
||||
|
||||
#[component]
|
||||
fn ClientAdd() -> Element {
|
||||
fn New() -> Element {
|
||||
let mut first_name = use_signal(String::new);
|
||||
let mut last_name = use_signal(String::new);
|
||||
let mut description = use_signal(String::new);
|
||||
|
||||
let submit_client = move |_: FormEvent| {
|
||||
let submit_client = move |_| {
|
||||
// Write the client
|
||||
CLIENTS.write().push(Client {
|
||||
first_name: first_name(),
|
||||
|
@ -74,7 +85,7 @@ fn ClientAdd() -> Element {
|
|||
});
|
||||
|
||||
// And then navigate back to the client list
|
||||
dioxus::router::router().push(Route::ClientList);
|
||||
router().push(Route::List);
|
||||
};
|
||||
|
||||
rsx! {
|
||||
|
@ -87,7 +98,7 @@ fn ClientAdd() -> Element {
|
|||
id: "first_name",
|
||||
r#type: "text",
|
||||
placeholder: "First Name…",
|
||||
required: "",
|
||||
required: true,
|
||||
value: "{first_name}",
|
||||
oninput: move |e| first_name.set(e.value()),
|
||||
|
||||
|
@ -99,19 +110,19 @@ fn ClientAdd() -> Element {
|
|||
}
|
||||
|
||||
div { class: "pure-control-group",
|
||||
label { "for": "last_name", "Last Name" }
|
||||
label { r#for: "last_name", "Last Name" }
|
||||
input {
|
||||
id: "last_name",
|
||||
r#type: "text",
|
||||
placeholder: "Last Name…",
|
||||
required: "",
|
||||
required: true,
|
||||
value: "{last_name}",
|
||||
oninput: move |e| last_name.set(e.value())
|
||||
}
|
||||
}
|
||||
|
||||
div { class: "pure-control-group",
|
||||
label { "for": "description", "Description" }
|
||||
label { r#for: "description", "Description" }
|
||||
textarea {
|
||||
id: "description",
|
||||
placeholder: "Description…",
|
||||
|
@ -122,7 +133,7 @@ fn ClientAdd() -> Element {
|
|||
|
||||
div { class: "pure-controls",
|
||||
button { r#type: "submit", class: "pure-button pure-button-primary", "Save" }
|
||||
Link { to: Route::ClientList, class: "pure-button pure-button-primary red", "Cancel" }
|
||||
Link { to: Route::List, class: "pure-button pure-button-primary red", "Cancel" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -137,10 +148,10 @@ fn Settings() -> Element {
|
|||
class: "pure-button pure-button-primary red",
|
||||
onclick: move |_| {
|
||||
CLIENTS.write().clear();
|
||||
dioxus::router::router().push(Route::ClientList);
|
||||
dioxus::router::router().push(Route::List);
|
||||
},
|
||||
"Remove all Clients"
|
||||
}
|
||||
Link { to: Route::ClientList, class: "pure-button", "Go back" }
|
||||
Link { to: Route::List, class: "pure-button", "Go back" }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
//! A simple example on how to use assets loading from the filesystem.
|
||||
//!
|
||||
//! If the feature "collect-assets" is enabled, the assets will be collected via the dioxus CLI and embedded into the
|
||||
//! final bundle. This lets you do various useful things like minify, compress, and optimize your assets.
|
||||
//!
|
||||
//! We can still use assets without the CLI middleware, but generally larger apps will benefit from it.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[cfg(not(feature = "collect-assets"))]
|
||||
|
@ -14,7 +21,7 @@ fn main() {
|
|||
fn app() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
p { "This should show an image:" }
|
||||
h1 { "This should show an image:" }
|
||||
img { src: ASSET_PATH.to_string() }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,28 +1,22 @@
|
|||
//! This example shows how to use a custom index.html and custom <HEAD> extensions
|
||||
//! to add things like stylesheets, scripts, and third-party JS libraries.
|
||||
|
||||
use dioxus::desktop::Config;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
LaunchBuilder::desktop()
|
||||
.with_cfg(
|
||||
Config::new().with_custom_head("<style>body { background-color: red; }</style>".into()),
|
||||
)
|
||||
.launch(app);
|
||||
|
||||
LaunchBuilder::desktop()
|
||||
.with_cfg(
|
||||
Config::new().with_custom_index(
|
||||
dioxus::desktop::Config::new().with_custom_index(
|
||||
r#"
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Dioxus app</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<style>body { background-color: blue; }</style>
|
||||
<style>body { background-color: olive; }</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>External HTML</h1>
|
||||
<div id="main"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -35,6 +29,6 @@ fn main() {
|
|||
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
div { h1 { "hello world!" } }
|
||||
h1 { "Custom HTML!" }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
//! A simple demonstration of how to set attributes on buttons to disable them.
|
||||
//!
|
||||
//! This example also showcases the shorthand syntax for attributes, and how signals themselves implement IntoAttribute
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
|
@ -8,13 +12,12 @@ fn app() -> Element {
|
|||
let mut disabled = use_signal(|| false);
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
div { style: "text-align: center; margin: 20px; display: flex; flex-direction: column; align-items: center;",
|
||||
button { onclick: move |_| disabled.toggle(),
|
||||
"click to "
|
||||
if disabled() { "enable" } else { "disable" }
|
||||
" the lower button"
|
||||
}
|
||||
|
||||
button { disabled, "lower button" }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
//! This example demonstrates a simple app that fetches a list of dog breeds and displays a random dog.
|
||||
//!
|
||||
//! The app uses the `use_signal` and `use_resource` hooks to manage state and fetch data from the Dog API.
|
||||
//! `use_resource` is basically an async version of use_memo - it will track dependencies between .await points
|
||||
//! and then restart the future if any of the dependencies change.
|
||||
//!
|
||||
//! You should generally throttle requests to an API - either client side or server side. This example doesn't do that
|
||||
//! since it's unlikely the user will rapidly cause new fetches, but it's something to keep in mind.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
|
@ -6,8 +15,18 @@ fn main() {
|
|||
}
|
||||
|
||||
fn app() -> Element {
|
||||
// Breed is a signal that will be updated when the user clicks a breed in the list
|
||||
// `deerhound` is just a default that we know will exist. We could also use a `None` instead
|
||||
let mut breed = use_signal(|| "deerhound".to_string());
|
||||
|
||||
// Fetch the list of breeds from the Dog API
|
||||
// Since there are no dependencies, this will never restart
|
||||
let breed_list = use_resource(move || async move {
|
||||
#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
|
||||
struct ListBreeds {
|
||||
message: HashMap<String, Vec<String>>,
|
||||
}
|
||||
|
||||
let list = reqwest::get("https://dog.ceo/api/breeds/list/all")
|
||||
.await
|
||||
.unwrap()
|
||||
|
@ -19,7 +38,7 @@ fn app() -> Element {
|
|||
};
|
||||
|
||||
rsx! {
|
||||
for cur_breed in breeds.message.keys().take(10).cloned() {
|
||||
for cur_breed in breeds.message.keys().take(20).cloned() {
|
||||
li { key: "{cur_breed}",
|
||||
button { onclick: move |_| breed.set(cur_breed.clone()),
|
||||
"{cur_breed}"
|
||||
|
@ -29,22 +48,31 @@ fn app() -> Element {
|
|||
}
|
||||
});
|
||||
|
||||
// We can use early returns in dioxus!
|
||||
// Traditional signal-based libraries can't do this since the scope is by default non-reactive
|
||||
let Some(breed_list) = breed_list() else {
|
||||
return rsx! { "loading breeds..." };
|
||||
};
|
||||
|
||||
rsx! {
|
||||
style { {include_str!("./assets/dog_app.css")} }
|
||||
h1 { "Select a dog breed!" }
|
||||
div { height: "500px", display: "flex",
|
||||
ul { flex: "50%", {breed_list} }
|
||||
div { flex: "50%", BreedPic { breed } }
|
||||
ul { width: "100px", {breed_list} }
|
||||
div { flex: 1, BreedPic { breed } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn BreedPic(breed: Signal<String>) -> Element {
|
||||
// This resource will restart whenever the breed changes
|
||||
let mut fut = use_resource(move || async move {
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct DogApi {
|
||||
message: String,
|
||||
}
|
||||
|
||||
reqwest::get(format!("https://dog.ceo/api/breed/{breed}/images/random"))
|
||||
.await
|
||||
.unwrap()
|
||||
|
@ -61,13 +89,3 @@ fn BreedPic(breed: Signal<String>) -> Element {
|
|||
None => rsx! { "loading image..." },
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
|
||||
struct ListBreeds {
|
||||
message: HashMap<String, Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct DogApi {
|
||||
message: String,
|
||||
}
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
//! This example shows how to load in custom assets with the use_asset_handler hook.
|
||||
//!
|
||||
//! This hook is currently only available on desktop and allows you to intercept any request made by the webview
|
||||
//! and respond with your own data. You could use this to load in custom videos, streams, stylesheets, images,
|
||||
//! or any asset that isn't known at compile time.
|
||||
|
||||
use dioxus::desktop::{use_asset_handler, wry::http::Response};
|
||||
use dioxus::prelude::*;
|
||||
|
||||
|
@ -16,8 +22,8 @@ fn app() -> Element {
|
|||
});
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
img { src: "/logos/logo.png" }
|
||||
}
|
||||
style { {include_str!("./assets/custom_assets.css")} }
|
||||
h1 { "Dynamic Assets" }
|
||||
img { src: "/logos/logo.png" }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
//! This example showcases how to use the ErrorBoundary component to handle errors in your app.
|
||||
//!
|
||||
//! The ErrorBoundary component is a special component that can be used to catch panics and other errors that occur.
|
||||
//! By default, Dioxus will catch panics during rendering, async, and handlers, and bubble them up to the nearest
|
||||
//! error boundary. If no error boundary is present, it will be caught by the root error boundary and the app will
|
||||
//! render the error message as just a string.
|
||||
|
||||
use dioxus::{dioxus_core::CapturedError, prelude::*};
|
||||
|
||||
fn main() {
|
||||
|
@ -7,7 +14,10 @@ fn main() {
|
|||
fn app() -> Element {
|
||||
rsx! {
|
||||
ErrorBoundary {
|
||||
handle_error: |error: CapturedError| rsx! {"Found error {error}"},
|
||||
handle_error: |error: CapturedError| rsx! {
|
||||
h1 { "An error occurred" }
|
||||
pre { "{error:#?}" }
|
||||
},
|
||||
DemoC { x: 1 }
|
||||
}
|
||||
}
|
||||
|
@ -15,11 +25,18 @@ fn app() -> Element {
|
|||
|
||||
#[component]
|
||||
fn DemoC(x: i32) -> Element {
|
||||
let result = Err("Error");
|
||||
|
||||
result.throw()?;
|
||||
|
||||
rsx! {
|
||||
h1 { "{x}" }
|
||||
h1 { "Error handler demo" }
|
||||
button {
|
||||
onclick: move |_| {
|
||||
// Create an error
|
||||
let result: Result<Element, &str> = Err("Error");
|
||||
|
||||
// And then call `throw` on it. The `throw` method is given by the `Throw` trait which is automatically
|
||||
// imported via the prelude.
|
||||
_ = result.throw();
|
||||
},
|
||||
"Click to throw an error"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
//! This example shows how to use the `eval` function to run JavaScript code in the webview.
|
||||
//!
|
||||
//! Eval will only work with renderers that support javascript - so currently only the web and desktop/mobile renderers
|
||||
//! that use a webview. Native renderers will throw "unsupported" errors when calling `eval`.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
|
@ -5,20 +10,33 @@ fn main() {
|
|||
}
|
||||
|
||||
fn app() -> Element {
|
||||
// Create a future that will resolve once the javascript has been succesffully executed.
|
||||
let future = use_resource(move || async move {
|
||||
// Wait a little bit just to give the appearance of a loading screen
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
||||
|
||||
// The `eval` is available in the prelude - and simply takes a block of JS.
|
||||
// Dioxus' eval is interesting since it allows sending messages to and from the JS code using the `await dioxus.recv()`
|
||||
// builtin function. This allows you to create a two-way communication channel between Rust and JS.
|
||||
let mut eval = eval(
|
||||
r#"
|
||||
dioxus.send("Hi from JS!");
|
||||
let msg = await dioxus.recv();
|
||||
console.log(msg);
|
||||
return "hello world";
|
||||
return "hi from JS!";
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Send a message to the JS code.
|
||||
eval.send("Hi from Rust!".into()).unwrap();
|
||||
|
||||
// Our line on the JS side will log the message and then return "hello world".
|
||||
let res = eval.recv().await.unwrap();
|
||||
|
||||
// This will print "Hi from JS!" and "Hi from Rust!".
|
||||
println!("{:?}", eval.await);
|
||||
|
||||
res
|
||||
});
|
||||
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
//! Example: File Explorer
|
||||
//! -------------------------
|
||||
//!
|
||||
//! This is a fun little desktop application that lets you explore the file system.
|
||||
//!
|
||||
//! This example is interesting because it's mixing filesystem operations and GUI, which is typically hard for UI to do.
|
||||
//!
|
||||
//! It also uses `use_ref` to maintain a model, rather than `use_state`. That way,
|
||||
//! we dont need to clutter our code with `read` commands.
|
||||
//! We store the state entirely in a single signal, making the explorer logic fairly easy to reason about.
|
||||
|
||||
use dioxus::desktop::{Config, WindowBuilder};
|
||||
use dioxus::prelude::*;
|
||||
|
@ -37,25 +34,25 @@ fn app() -> Element {
|
|||
}
|
||||
style { "{_STYLE}" }
|
||||
main {
|
||||
{files.read().path_names.iter().enumerate().map(|(dir_id, path)| {
|
||||
let path_end = path.split('/').last().unwrap_or(path.as_str());
|
||||
rsx! (
|
||||
div {
|
||||
class: "folder",
|
||||
key: "{path}",
|
||||
i { class: "material-icons",
|
||||
onclick: move |_| files.write().enter_dir(dir_id),
|
||||
if path_end.contains('.') {
|
||||
"description"
|
||||
} else {
|
||||
"folder"
|
||||
for (dir_id, path) in files.read().path_names.iter().enumerate() {
|
||||
{
|
||||
let path_end = path.split('/').last().unwrap_or(path.as_str());
|
||||
rsx! {
|
||||
div { class: "folder", key: "{path}",
|
||||
i { class: "material-icons",
|
||||
onclick: move |_| files.write().enter_dir(dir_id),
|
||||
if path_end.contains('.') {
|
||||
"description"
|
||||
} else {
|
||||
"folder"
|
||||
}
|
||||
p { class: "cooltip", "0 folders / 0 files" }
|
||||
}
|
||||
p { class: "cooltip", "0 folders / 0 files" }
|
||||
h1 { "{path_end}" }
|
||||
}
|
||||
h1 { "{path_end}" }
|
||||
}
|
||||
)
|
||||
})},
|
||||
}
|
||||
}
|
||||
if let Some(err) = files.read().err.as_ref() {
|
||||
div {
|
||||
code { "{err}" }
|
||||
|
@ -67,6 +64,10 @@ fn app() -> Element {
|
|||
}
|
||||
}
|
||||
|
||||
/// A simple little struct to hold the file explorer state
|
||||
///
|
||||
/// We don't use any fancy signals or memoization here - Dioxus is so fast that even a file explorer can be done with a
|
||||
/// single signal.
|
||||
struct Files {
|
||||
path_stack: Vec<String>,
|
||||
path_names: Vec<String>,
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
#![allow(non_snake_case)]
|
||||
//! This example shows how to use the `file` methods on FormEvent and DragEvent to handle file uploads and drops.
|
||||
//!
|
||||
//! Dioxus intercepts these events and provides a Rusty interface to the file data. Since we want this interface to
|
||||
//! be crossplatform,
|
||||
|
||||
use dioxus::html::HasFileData;
|
||||
use dioxus::prelude::*;
|
||||
use tokio::time::sleep;
|
||||
|
||||
fn main() {
|
||||
launch(App);
|
||||
launch(app);
|
||||
}
|
||||
|
||||
fn App() -> Element {
|
||||
fn app() -> Element {
|
||||
let mut enable_directory_upload = use_signal(|| false);
|
||||
let mut files_uploaded = use_signal(|| Vec::new() as Vec<String>);
|
||||
|
||||
|
@ -31,12 +35,16 @@ fn App() -> Element {
|
|||
};
|
||||
|
||||
rsx! {
|
||||
style { {include_str!("./assets/file_upload.css")} }
|
||||
|
||||
input {
|
||||
r#type: "checkbox",
|
||||
id: "directory-upload",
|
||||
checked: enable_directory_upload,
|
||||
oninput: move |evt| enable_directory_upload.set(evt.checked()),
|
||||
},
|
||||
label {
|
||||
input {
|
||||
r#type: "checkbox",
|
||||
checked: enable_directory_upload,
|
||||
oninput: move |evt| enable_directory_upload.set(evt.checked()),
|
||||
},
|
||||
r#for: "directory-upload",
|
||||
"Enable directory upload"
|
||||
}
|
||||
|
||||
|
@ -47,16 +55,18 @@ fn App() -> Element {
|
|||
directory: enable_directory_upload,
|
||||
onchange: upload_files,
|
||||
}
|
||||
|
||||
div {
|
||||
width: "100px",
|
||||
height: "100px",
|
||||
border: "1px solid black",
|
||||
// cheating with a little bit of JS...
|
||||
"ondragover": "this.style.backgroundColor='#88FF88';",
|
||||
"ondragleave": "this.style.backgroundColor='#FFFFFF';",
|
||||
|
||||
id: "drop-zone",
|
||||
prevent_default: "ondrop dragover dragenter",
|
||||
ondrop: handle_file_drop,
|
||||
ondragover: move |event| event.stop_propagation(),
|
||||
"Drop files here"
|
||||
}
|
||||
|
||||
ul {
|
||||
for file in files_uploaded.read().iter() {
|
||||
li { "{file}" }
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
use dioxus::desktop::Config;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
LaunchBuilder::desktop()
|
||||
.with_cfg(Config::new().with_file_drop_handler(|_w, e| {
|
||||
println!("{e:?}");
|
||||
true
|
||||
}))
|
||||
.launch(app)
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
rsx!(
|
||||
div { h1 { "drag a file here and check your console" } }
|
||||
)
|
||||
}
|
|
@ -1,8 +1,18 @@
|
|||
//! This example shows how to use the `Router` component to create a simple navigation system.
|
||||
//! The more complex router example uses all of the router features, while this simple exmaple showcases
|
||||
//! just the `Layout` and `Route` features.
|
||||
//!
|
||||
//! Layouts let you wrap chunks of your app with a component. This is useful for things like a footers, heeaders, etc.
|
||||
//! Routes are enum variants with that match the name of a component in scope. This way you can create a new route
|
||||
//! in your app simply by adding the variant to the enum and creating a new component with the same name. You can
|
||||
//! override this of course.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
launch(|| {
|
||||
rsx! {
|
||||
style { {include_str!("./assets/flat_router.css")} }
|
||||
Router::<Route> {}
|
||||
}
|
||||
})
|
||||
|
@ -11,7 +21,7 @@ fn main() {
|
|||
#[derive(Routable, Clone)]
|
||||
#[rustfmt::skip]
|
||||
enum Route {
|
||||
#[layout(Footer)]
|
||||
#[layout(Footer)] // wrap the entire app in a footer
|
||||
#[route("/")]
|
||||
Home {},
|
||||
|
||||
|
@ -28,45 +38,47 @@ enum Route {
|
|||
#[component]
|
||||
fn Footer() -> Element {
|
||||
rsx! {
|
||||
Outlet::<Route> {}
|
||||
p { "----" }
|
||||
nav {
|
||||
style { {STYLE} }
|
||||
Link { to: Route::Home {}, class: "nav-btn", "Home" }
|
||||
Link { to: Route::Games {}, class: "nav-btn", "Games" }
|
||||
Link { to: Route::Play {}, class: "nav-btn", "Play" }
|
||||
Link { to: Route::Settings {}, class: "nav-btn", "Settings" }
|
||||
}
|
||||
div { id: "content",
|
||||
Outlet::<Route> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Home() -> Element {
|
||||
rsx!("Home")
|
||||
rsx!(
|
||||
h1 { "Home" }
|
||||
p { "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." }
|
||||
)
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Games() -> Element {
|
||||
rsx!("Games")
|
||||
rsx!(
|
||||
h1 { "Games" }
|
||||
// Dummy text that talks about video games
|
||||
p { "Lorem games are sit amet Sed do eiusmod tempor et dolore magna aliqua." }
|
||||
)
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Play() -> Element {
|
||||
rsx!("Play")
|
||||
rsx!(
|
||||
h1 { "Play" }
|
||||
p { "Always play with your full heart adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." }
|
||||
)
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Settings() -> Element {
|
||||
rsx!("Settings")
|
||||
rsx!(
|
||||
h1 { "Settings" }
|
||||
p { "Settings are consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." }
|
||||
)
|
||||
}
|
||||
|
||||
const STYLE: &str = r#"
|
||||
nav {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
.nav-btn {
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
"#;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! Forms
|
||||
//!
|
||||
//! Dioxus forms deviate slightly from html, automatically returning all named inputs
|
||||
//! in the "values" field
|
||||
//! in the "values" field.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
|
|
38
examples/future.rs
Normal file
38
examples/future.rs
Normal file
|
@ -0,0 +1,38 @@
|
|||
//! A simple example that shows how to use the use_future hook to run a background task.
|
||||
//!
|
||||
//! use_future assumes your future will never complete - it won't return a value.
|
||||
//! If you want to return a value, use use_resource instead.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use std::time::Duration;
|
||||
|
||||
fn main() {
|
||||
launch_desktop(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut count = use_signal(|| 0);
|
||||
|
||||
// use_future will run the future
|
||||
use_future(move || async move {
|
||||
loop {
|
||||
tokio::time::sleep(Duration::from_millis(200)).await;
|
||||
count += 1;
|
||||
}
|
||||
});
|
||||
|
||||
// We can also spawn futures from effects, handlers, or other futures
|
||||
use_effect(move || {
|
||||
spawn(async move {
|
||||
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||
count.set(100);
|
||||
});
|
||||
});
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
h1 { "Current count: {count}" }
|
||||
button { onclick: move |_| count.set(0), "Reset the count" }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,10 @@
|
|||
use std::fmt::Display;
|
||||
//! This example demonstrates how to create a generic component in Dioxus.
|
||||
//!
|
||||
//! Generic components can be useful when you want to create a component that renders differently depending on the type
|
||||
//! of data it receives. In this particular example, we're just using a type that implements `Display` and `PartialEq`,
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use std::fmt::Display;
|
||||
|
||||
fn main() {
|
||||
launch_desktop(app);
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
//! Example: README.md showcase
|
||||
//! Example: Global signals and memos
|
||||
//!
|
||||
//! The example from the README.md.
|
||||
//! This example demonstrates how to use global signals and memos to share state across your app.
|
||||
//! Global signals are simply signals that live on the root of your app and are accessible from anywhere. To access a
|
||||
//! global signal, simply use its methods like a regular signal. Calls to `read` and `write` will be forwarded to the
|
||||
//! signal at the root of your app using the `static`'s address.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
|
@ -13,8 +16,43 @@ static DOUBLED_COUNT: GlobalMemo<i32> = Signal::global_memo(|| COUNT() * 2);
|
|||
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
h1 { "{COUNT} x 2 = {DOUBLED_COUNT}" }
|
||||
style { {include_str!("./assets/counter.css")} }
|
||||
Increment {}
|
||||
Decrement {}
|
||||
Reset {}
|
||||
Display {}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Increment() -> Element {
|
||||
rsx! {
|
||||
button { onclick: move |_| *COUNT.write() += 1, "Up high!" }
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Decrement() -> Element {
|
||||
rsx! {
|
||||
button { onclick: move |_| *COUNT.write() -= 1, "Down low!" }
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Display() -> Element {
|
||||
rsx! {
|
||||
p { "Count: ", "{COUNT}" }
|
||||
p { "Doubled: ", "{DOUBLED_COUNT}" }
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Reset() -> Element {
|
||||
// Not all write methods are availale on global signals since `write` requires a mutable reference. In these cases,
|
||||
// We can simply pull out the actual signal using the signal() method.
|
||||
let mut as_signal = use_hook(|| COUNT.signal());
|
||||
|
||||
rsx! {
|
||||
button { onclick: move |_| as_signal.set(0), "Reset" }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,14 @@
|
|||
//! The simplest example of a Dioxus app.
|
||||
//!
|
||||
//! In this example we:
|
||||
//! - import a number of important items from the prelude (launch, Element, rsx, div, etc.)
|
||||
//! - define a main function that calls the launch function with our app function
|
||||
//! - define an app function that returns a div element with the text "Hello, world!"
|
||||
//!
|
||||
//! The `launch` function is the entry point for all Dioxus apps. It takes a function that returns an Element. This function
|
||||
//! calls "launch" on the currently-configured renderer you have. So if the `web` feature is enabled, it will launch a web
|
||||
//! app, and if the `desktop` feature is enabled, it will launch a desktop app.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
|
|
|
@ -1,162 +0,0 @@
|
|||
//! This example roughly shows how events are serialized into Rust from JavaScript.
|
||||
//!
|
||||
//! There is some conversion happening when input types are checkbox/radio/select/textarea etc.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
launch_desktop(app);
|
||||
}
|
||||
|
||||
const FIELDS: &[(&str, &str)] = &[
|
||||
("button", "Click me!"),
|
||||
("checkbox", "CHECKBOX"),
|
||||
("color", ""),
|
||||
("date", ""),
|
||||
("datetime-local", ""),
|
||||
("email", ""),
|
||||
("file", ""),
|
||||
("image", ""),
|
||||
("number", ""),
|
||||
("password", ""),
|
||||
("radio", ""),
|
||||
("range", ""),
|
||||
("reset", ""),
|
||||
("search", ""),
|
||||
("submit", ""),
|
||||
("tel", ""),
|
||||
("text", ""),
|
||||
("time", ""),
|
||||
("url", ""),
|
||||
// less supported things
|
||||
("hidden", ""),
|
||||
("month", ""), // degrades to text most of the time, but works properly as "value'"
|
||||
("week", ""), // degrades to text most of the time
|
||||
];
|
||||
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
div { margin_left: "30px",
|
||||
{select_example()},
|
||||
div {
|
||||
// handling inputs on divs will catch all input events below
|
||||
// so the value of our input event will be either huey, dewey, louie, or true/false (because of the checkboxe)
|
||||
// be mindful in grouping inputs together, as they will all be handled by the same event handler
|
||||
oninput: move |evt| println!("{evt:?}"),
|
||||
div {
|
||||
input {
|
||||
id: "huey",
|
||||
r#type: "radio",
|
||||
value: "huey",
|
||||
checked: true,
|
||||
name: "drone",
|
||||
}
|
||||
label {
|
||||
r#for: "huey",
|
||||
"Huey"
|
||||
}
|
||||
}
|
||||
div {
|
||||
input {
|
||||
id: "dewey",
|
||||
r#type: "radio",
|
||||
value: "dewey",
|
||||
name: "drone",
|
||||
}
|
||||
label { r#for: "dewey", "Dewey" }
|
||||
}
|
||||
div {
|
||||
input {
|
||||
id: "louie",
|
||||
value: "louie",
|
||||
r#type: "radio",
|
||||
name: "drone",
|
||||
}
|
||||
label {
|
||||
r#for: "louie",
|
||||
"Louie"
|
||||
}
|
||||
}
|
||||
div {
|
||||
input {
|
||||
id: "groovy",
|
||||
value: "groovy",
|
||||
r#type: "checkbox",
|
||||
name: "drone",
|
||||
}
|
||||
label {
|
||||
r#for: "groovy",
|
||||
"groovy"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// elements with driven values will preventdefault automatically.
|
||||
// you can disable this override with preventdefault: false
|
||||
div {
|
||||
input {
|
||||
id: "pdf",
|
||||
value: "pdf",
|
||||
name: "pdf",
|
||||
r#type: "checkbox",
|
||||
oninput: move |evt| {
|
||||
println!("{evt:?}");
|
||||
},
|
||||
}
|
||||
label {
|
||||
r#for: "pdf",
|
||||
"pdf"
|
||||
}
|
||||
}
|
||||
|
||||
for (field, value) in FIELDS.iter() {
|
||||
div {
|
||||
input {
|
||||
id: "{field}",
|
||||
name: "{field}",
|
||||
r#type: "{field}",
|
||||
value: "{value}",
|
||||
oninput: move |evt: FormEvent| {
|
||||
println!("{evt:?}");
|
||||
},
|
||||
}
|
||||
label {
|
||||
r#for: "{field}",
|
||||
"{field} element"
|
||||
}
|
||||
br {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn select_example() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
select {
|
||||
id: "selection",
|
||||
name: "selection",
|
||||
multiple: true,
|
||||
oninput: move |evt| println!("{evt:?}"),
|
||||
option {
|
||||
value: "Option 1",
|
||||
label: "Option 1",
|
||||
}
|
||||
option {
|
||||
value: "Option 2",
|
||||
label: "Option 2",
|
||||
selected: true,
|
||||
},
|
||||
option {
|
||||
value: "Option 3",
|
||||
label: "Option 3",
|
||||
}
|
||||
}
|
||||
label {
|
||||
r#for: "selection",
|
||||
"select element"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +1,21 @@
|
|||
//! How to use links in Dioxus
|
||||
//!
|
||||
//! The `router` crate gives us a `Link` component which is a much more powerful version of the standard HTML link.
|
||||
//! However, you can use the traditional `<a>` tag if you want to build your own `Link` component.
|
||||
//!
|
||||
//! The `Link` component integrates with the Router and is smart enough to detect if the link is internal or external.
|
||||
//! It also allows taking any `Route` as a target, making your links typesafe
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
launch_desktop(App);
|
||||
launch_desktop(app);
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn App() -> Element {
|
||||
fn app() -> Element {
|
||||
rsx! (
|
||||
div {
|
||||
p { a { href: "http://dioxuslabs.com/", "Default link - links outside of your app" } }
|
||||
p {
|
||||
a {
|
||||
href: "http://dioxuslabs.com/",
|
||||
prevent_default: "onclick",
|
||||
onclick: |_| println!("Hello Dioxus"),
|
||||
"Custom event link - links inside of your app"
|
||||
}
|
||||
}
|
||||
}
|
||||
div { Router::<Route> {} }
|
||||
style { {include_str!("./assets/links.css")} }
|
||||
Router::<Route> {}
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -28,6 +25,10 @@ enum Route {
|
|||
#[layout(Header)]
|
||||
#[route("/")]
|
||||
Home {},
|
||||
|
||||
#[route("/default-links")]
|
||||
DefaultLinks {},
|
||||
|
||||
#[route("/settings")]
|
||||
Settings {},
|
||||
}
|
||||
|
@ -36,13 +37,10 @@ enum Route {
|
|||
fn Header() -> Element {
|
||||
rsx! {
|
||||
h1 { "Your app here" }
|
||||
ul {
|
||||
li {
|
||||
Link { to: Route::Home {}, "home" }
|
||||
}
|
||||
li {
|
||||
Link { to: Route::Settings {}, "settings" }
|
||||
}
|
||||
nav { id: "nav",
|
||||
Link { to: Route::Home {}, "home" }
|
||||
Link { to: Route::DefaultLinks {}, "default links" }
|
||||
Link { to: Route::Settings {}, "settings" }
|
||||
}
|
||||
Outlet::<Route> {}
|
||||
}
|
||||
|
@ -57,3 +55,23 @@ fn Home() -> Element {
|
|||
fn Settings() -> Element {
|
||||
rsx!( h1 { "Settings" } )
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn DefaultLinks() -> Element {
|
||||
rsx! {
|
||||
// Just some default links
|
||||
div { id: "external-links",
|
||||
// This link will open in a webbrowser
|
||||
a { href: "http://dioxuslabs.com/", "Default link - links outside of your app" }
|
||||
|
||||
// This link will do nothing - we're preventing the default behavior
|
||||
// It will just log "Hello Dioxus" to the console
|
||||
a {
|
||||
href: "http://dioxuslabs.com/",
|
||||
prevent_default: "onclick",
|
||||
onclick: |_| println!("Hello Dioxus"),
|
||||
"Custom event link - links inside of your app"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
//! This example demonstrates the following:
|
||||
//! Futures in a callback, Router, and Forms
|
||||
//! Implementing a login form
|
||||
//!
|
||||
//! This example demonstrates how to implement a login form using Dioxus desktop. Since forms typically navigate the
|
||||
//! page on submit, we need to intercept the onsubmit event and send a request to a server. On the web, we could
|
||||
//! just leave the submit action` as is, but on desktop, we need to handle the form submission ourselves.
|
||||
//!
|
||||
//! Todo: actually spin up a server and run the login flow. Login is way more complex than a form override :)
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
//! This example shows how you can chain memos together to create a tree of memoized values.
|
||||
//!
|
||||
//! Memos will also pause when their parent component pauses, so if you have a memo that depends on a signal, and the
|
||||
//! signal pauses, the memo will pause too.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
//! Multiwindow example
|
||||
//!
|
||||
//! This exmaple shows how to implement a simple multiwindow application using dioxus.
|
||||
//! This works by spawning a new window when the user clicks a button. We have to build a new virtualdom which has its
|
||||
//! own context, root elements, etc.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
//! Example: README.md showcase
|
||||
//! Optional props
|
||||
//!
|
||||
//! The example from the README.md.
|
||||
//! This example demonstrates how to use optional props in your components. The `Button` component has several props,
|
||||
//! and we use a variety of attributes to set them.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
|
@ -12,19 +11,27 @@ fn main() {
|
|||
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
// We can set some of the props, and the rest will be filled with their default values
|
||||
// By default `c` can take a `None` value, but `d` is required to wrap a `Some` value
|
||||
Button {
|
||||
a: "asd".to_string(),
|
||||
// b can be omitted, and it will be filled with its default value
|
||||
c: "asd".to_string(),
|
||||
d: Some("asd".to_string()),
|
||||
e: Some("asd".to_string()),
|
||||
}
|
||||
|
||||
Button {
|
||||
a: "asd".to_string(),
|
||||
b: "asd".to_string(),
|
||||
|
||||
// We can omit the `Some` on `c` since Dioxus automatically transforms Option<T> into optional
|
||||
c: "asd".to_string(),
|
||||
d: Some("asd".to_string()),
|
||||
e: "asd".to_string(),
|
||||
}
|
||||
|
||||
// `b` and `e` are ommitted
|
||||
Button {
|
||||
a: "asd".to_string(),
|
||||
c: "asd".to_string(),
|
||||
|
@ -51,6 +58,7 @@ struct ButtonProps {
|
|||
|
||||
type SthElse<T> = Option<T>;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn Button(props: ButtonProps) -> Element {
|
||||
rsx! {
|
||||
button {
|
||||
|
|
|
@ -1,4 +1,13 @@
|
|||
use dioxus::desktop::{tao::dpi::PhysicalPosition, LogicalSize, WindowBuilder};
|
||||
//! This example demonstrates how to create an overlay window with dioxus.
|
||||
//!
|
||||
//! Basically, we just create a new window with a transparent background and no decorations, size it to the screen, and
|
||||
//! then we can draw whatever we want on it. In this case, we're drawing a simple overlay with a draggable header.
|
||||
//!
|
||||
//! We also add a global shortcut to toggle the overlay on and off, so you could build a raycast-type app with this.
|
||||
|
||||
use dioxus::desktop::{
|
||||
tao::dpi::PhysicalPosition, use_global_shortcut, LogicalSize, WindowBuilder,
|
||||
};
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
|
@ -6,21 +15,27 @@ fn main() {
|
|||
}
|
||||
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
background_color: "red",
|
||||
border: "1px solid black",
|
||||
let mut show_overlay = use_signal(|| true);
|
||||
|
||||
_ = use_global_shortcut("cmd+g", move || show_overlay.toggle());
|
||||
|
||||
rsx! {
|
||||
if show_overlay() {
|
||||
div {
|
||||
width: "100%",
|
||||
height: "10px",
|
||||
background_color: "black",
|
||||
onmousedown: move |_| dioxus::desktop::window().drag(),
|
||||
}
|
||||
height: "100%",
|
||||
background_color: "red",
|
||||
border: "1px solid black",
|
||||
|
||||
"This is an overlay!"
|
||||
div {
|
||||
width: "100%",
|
||||
height: "10px",
|
||||
background_color: "black",
|
||||
onmousedown: move |_| dioxus::desktop::window().drag(),
|
||||
}
|
||||
|
||||
"This is an overlay!"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
//! This example shows how to create a popup window and send data back to the parent window.
|
||||
|
||||
use std::rc::Rc;
|
||||
//! Currently Dioxus doesn't support nested renderers, hence the need to create popups as separate windows.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use futures_util::StreamExt;
|
||||
use std::rc::Rc;
|
||||
|
||||
fn main() {
|
||||
launch_desktop(app);
|
||||
|
@ -14,6 +13,7 @@ fn app() -> Element {
|
|||
|
||||
// Wait for responses to the compose channel, and then push them to the emails_sent signal.
|
||||
let handle = use_coroutine(|mut rx: UnboundedReceiver<String>| async move {
|
||||
use futures_util::StreamExt;
|
||||
while let Some(message) = rx.next().await {
|
||||
emails_sent.write().push(message);
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ fn app() -> Element {
|
|||
let open_compose_window = move |_evt: MouseEvent| {
|
||||
let tx = handle.tx();
|
||||
dioxus::desktop::window().new_window(
|
||||
VirtualDom::new_with_props(compose, Rc::new(move |s| tx.unbounded_send(s).unwrap())),
|
||||
VirtualDom::new_with_props(popup, Rc::new(move |s| tx.unbounded_send(s).unwrap())),
|
||||
Default::default(),
|
||||
);
|
||||
};
|
||||
|
@ -41,21 +41,19 @@ fn app() -> Element {
|
|||
}
|
||||
}
|
||||
|
||||
fn compose(send: Rc<dyn Fn(String)>) -> Element {
|
||||
fn popup(send: Rc<dyn Fn(String)>) -> Element {
|
||||
let mut user_input = use_signal(String::new);
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
h1 { "Compose a new email" }
|
||||
|
||||
button {
|
||||
onclick: move |_| {
|
||||
send(user_input.cloned());
|
||||
dioxus::desktop::window().close();
|
||||
},
|
||||
"Click to send"
|
||||
"Send"
|
||||
}
|
||||
|
||||
input { oninput: move |e| user_input.set(e.value()), value: "{user_input}" }
|
||||
}
|
||||
}
|
|
@ -1,4 +1,9 @@
|
|||
#![allow(clippy::await_holding_refcell_ref)]
|
||||
//! Read the size of elements using the MountedData struct.
|
||||
//!
|
||||
//! Whenever an Element is finally mounted to the Dom, its data is avaiable to be read.
|
||||
//! These fields can typically only be read asynchronously, since various renderers need to release the main thread to
|
||||
//! perform layout and painting.
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use dioxus::{html::geometry::euclid::Rect, prelude::*};
|
||||
|
@ -33,6 +38,7 @@ fn app() -> Element {
|
|||
let read_dims = move |_| async move {
|
||||
let read = div_element.read();
|
||||
let client_rect = read.as_ref().map(|el| el.get_client_rect());
|
||||
|
||||
if let Some(client_rect) = client_rect {
|
||||
if let Ok(rect) = client_rect.await {
|
||||
dimensions.set(rect);
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
//! The example from the readme!
|
||||
//!
|
||||
//! This example demonstrates how to create a simple counter app with dioxus. The `Signal` type wraps inner values,
|
||||
//! making them `Copy`, allowing them to be freely used in closures and and async functions. `Signal` also provides
|
||||
//! helper methods like AddAssign, SubAssign, toggle, etc, to make it easy to update the value without running
|
||||
//! into lock issues.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
|
|
|
@ -15,12 +15,16 @@ fn app() -> Element {
|
|||
let mut state = use_signal(|| PlayerState { is_playing: false });
|
||||
|
||||
rsx!(
|
||||
div {
|
||||
h1 {"Select an option"}
|
||||
h3 { "The radio is... ", {state.read().is_playing()}, "!" }
|
||||
button { onclick: move |_| state.write().reduce(PlayerAction::Pause), "Pause" }
|
||||
button { onclick: move |_| state.write().reduce(PlayerAction::Play), "Play" }
|
||||
style { {include_str!("./assets/radio.css")} }
|
||||
h1 {"Select an option"}
|
||||
|
||||
// Add some cute animations if the radio is playing!
|
||||
div { class: if state.read().is_playing { "bounce" },
|
||||
"The radio is... ", {state.read().is_playing()}, "!"
|
||||
}
|
||||
|
||||
button { id: "play", onclick: move |_| state.write().reduce(PlayerAction::Pause), "Pause" }
|
||||
button { id: "pause", onclick: move |_| state.write().reduce(PlayerAction::Play), "Play" }
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,32 +1,59 @@
|
|||
//! An advanced usage of the router with nested routes and redirects.
|
||||
//!
|
||||
//! Dioxus implements an enum-based router, which allows you to define your routes in a type-safe way.
|
||||
//! However, since we need to bake quite a bit of logic into the enum, we have to add some extra syntax.
|
||||
//!
|
||||
//! Note that you don't need to use advanced features like nest, redirect, etc, since these can all be implemented
|
||||
//! manually, but they are provided as a convenience.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
launch_desktop(|| {
|
||||
rsx! {
|
||||
style { {include_str!("./assets/router.css")} }
|
||||
Router::<Route> {}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Turn off rustfmt since we're doing layouts and routes in the same enum
|
||||
#[derive(Routable, Clone, Debug, PartialEq)]
|
||||
#[rustfmt::skip]
|
||||
enum Route {
|
||||
// Wrap Home in a Navbar Layout
|
||||
#[layout(NavBar)]
|
||||
// The default route is always "/" unless otherwise specified
|
||||
#[route("/")]
|
||||
Home {},
|
||||
|
||||
// Wrap the next routes in a layout and a nest
|
||||
#[nest("/blog")]
|
||||
#[layout(Blog)]
|
||||
#[route("/")]
|
||||
BlogList {},
|
||||
#[route("/:name")]
|
||||
BlogPost { name: String },
|
||||
#[end_layout]
|
||||
#[layout(Blog)]
|
||||
// At "/blog", we want to show a list of blog posts
|
||||
#[route("/")]
|
||||
BlogList {},
|
||||
|
||||
// At "/blog/:name", we want to show a specific blog post, using the name slug
|
||||
#[route("/:name")]
|
||||
BlogPost { name: String },
|
||||
|
||||
// We need to end the blog layout and nest
|
||||
// Note we don't need either - we could've just done `/blog/` and `/blog/:name` without nesting,
|
||||
// but it's a bit cleaner this way
|
||||
#[end_layout]
|
||||
#[end_nest]
|
||||
|
||||
// And the regular page layout
|
||||
#[end_layout]
|
||||
|
||||
// Add some redirects for the `/myblog` route
|
||||
#[nest("/myblog")]
|
||||
#[redirect("/", || Route::BlogList {})]
|
||||
#[redirect("/:name", |name: String| Route::BlogPost { name })]
|
||||
#[end_nest]
|
||||
|
||||
// Finally, we need to handle the 404 page
|
||||
#[route("/:..route")]
|
||||
PageNotFound {
|
||||
route: Vec<String>,
|
||||
|
@ -36,15 +63,9 @@ enum Route {
|
|||
#[component]
|
||||
fn NavBar() -> Element {
|
||||
rsx! {
|
||||
nav {
|
||||
ul {
|
||||
li {
|
||||
Link { to: Route::Home {}, "Home" }
|
||||
}
|
||||
li {
|
||||
Link { to: Route::BlogList {}, "Blog" }
|
||||
}
|
||||
}
|
||||
nav { id: "navbar",
|
||||
Link { to: Route::Home {}, "Home" }
|
||||
Link { to: Route::BlogList {}, "Blog" }
|
||||
}
|
||||
Outlet::<Route> {}
|
||||
}
|
||||
|
@ -67,30 +88,31 @@ fn Blog() -> Element {
|
|||
fn BlogList() -> Element {
|
||||
rsx! {
|
||||
h2 { "Choose a post" }
|
||||
ul {
|
||||
li {
|
||||
Link {
|
||||
to: Route::BlogPost {
|
||||
name: "Blog post 1".into(),
|
||||
},
|
||||
"Read the first blog post"
|
||||
}
|
||||
div { id: "blog-list",
|
||||
Link { to: Route::BlogPost { name: "Blog post 1".into() },
|
||||
"Read the first blog post"
|
||||
}
|
||||
li {
|
||||
Link {
|
||||
to: Route::BlogPost {
|
||||
name: "Blog post 2".into(),
|
||||
},
|
||||
"Read the second blog post"
|
||||
}
|
||||
Link { to: Route::BlogPost { name: "Blog post 2".into() },
|
||||
"Read the second blog post"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We can use the `name` slug to show a specific blog post
|
||||
// In theory we could read from the filesystem or a database here
|
||||
#[component]
|
||||
fn BlogPost(name: String) -> Element {
|
||||
rsx! { h2 { "Blog Post: {name}" } }
|
||||
let contents = match name.as_str() {
|
||||
"Blog post 1" => "This is the first blog post. It's not very interesting.",
|
||||
"Blog post 2" => "This is the second blog post. It's not very interesting either.",
|
||||
_ => "This blog post doesn't exist.",
|
||||
};
|
||||
|
||||
rsx! {
|
||||
h2 { "{name}" }
|
||||
p { "{contents}" }
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
|
|
|
@ -39,263 +39,266 @@
|
|||
//! - Allow top-level fragments
|
||||
|
||||
fn main() {
|
||||
todo!()
|
||||
//launch_desktop(App);
|
||||
launch(app)
|
||||
}
|
||||
|
||||
// use core::{fmt, str::FromStr};
|
||||
// use std::fmt::Display;
|
||||
use core::{fmt, str::FromStr};
|
||||
use std::fmt::Display;
|
||||
|
||||
// use baller::Baller;
|
||||
// use dioxus::prelude::*;
|
||||
use baller::Baller;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
// #[component]
|
||||
// fn App() -> Element {
|
||||
// let formatting = "formatting!";
|
||||
// let formatting_tuple = ("a", "b");
|
||||
// let lazy_fmt = format_args!("lazily formatted text");
|
||||
// let asd = 123;
|
||||
// rsx! {
|
||||
// div {
|
||||
// // Elements
|
||||
// div {}
|
||||
// h1 {"Some text"}
|
||||
// h1 {"Some text with {formatting}"}
|
||||
// h1 {"Formatting basic expressions {formatting_tuple.0} and {formatting_tuple.1}"}
|
||||
// h1 {"Formatting without interpolation " {formatting_tuple.0} "and" {formatting_tuple.1} }
|
||||
// h2 {
|
||||
// "Multiple"
|
||||
// "Text"
|
||||
// "Blocks"
|
||||
// "Use comments as separators in html"
|
||||
// }
|
||||
// div {
|
||||
// h1 {"multiple"}
|
||||
// h2 {"nested"}
|
||||
// h3 {"elements"}
|
||||
// }
|
||||
// div {
|
||||
// class: "my special div",
|
||||
// h1 {"Headers and attributes!"}
|
||||
// }
|
||||
// div {
|
||||
// // pass simple rust expressions in
|
||||
// class: lazy_fmt,
|
||||
// id: format_args!("attributes can be passed lazily with std::fmt::Arguments"),
|
||||
// class: "asd",
|
||||
// class: "{asd}",
|
||||
// // if statements can be used to conditionally render attributes
|
||||
// class: if formatting.contains("form") { "{asd}" },
|
||||
// div {
|
||||
// class: {
|
||||
// const WORD: &str = "expressions";
|
||||
// format_args!("Arguments can be passed in through curly braces for complex {WORD}")
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
fn app() -> Element {
|
||||
let formatting = "formatting!";
|
||||
let formatting_tuple = ("a", "b");
|
||||
let lazy_fmt = format_args!("lazily formatted text");
|
||||
let asd = 123;
|
||||
|
||||
// // Expressions can be used in element position too:
|
||||
// {rsx!(p { "More templating!" })},
|
||||
rsx! {
|
||||
div {
|
||||
// Elements
|
||||
div {}
|
||||
h1 {"Some text"}
|
||||
h1 {"Some text with {formatting}"}
|
||||
h1 {"Formatting basic expressions {formatting_tuple.0} and {formatting_tuple.1}"}
|
||||
h1 {"Formatting without interpolation " {formatting_tuple.0} "and" {formatting_tuple.1} }
|
||||
h2 {
|
||||
"Multiple"
|
||||
"Text"
|
||||
"Blocks"
|
||||
"Use comments as separators in html"
|
||||
}
|
||||
div {
|
||||
h1 {"multiple"}
|
||||
h2 {"nested"}
|
||||
h3 {"elements"}
|
||||
}
|
||||
div {
|
||||
class: "my special div",
|
||||
h1 {"Headers and attributes!"}
|
||||
}
|
||||
div {
|
||||
// pass simple rust expressions in
|
||||
class: lazy_fmt,
|
||||
id: format_args!("attributes can be passed lazily with std::fmt::Arguments"),
|
||||
class: "asd",
|
||||
class: "{asd}",
|
||||
// if statements can be used to conditionally render attributes
|
||||
class: if formatting.contains("form") { "{asd}" },
|
||||
div {
|
||||
class: {
|
||||
const WORD: &str = "expressions";
|
||||
format_args!("Arguments can be passed in through curly braces for complex {WORD}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// // Iterators
|
||||
// {(0..10).map(|i| rsx!(li { "{i}" }))},
|
||||
// Expressions can be used in element position too:
|
||||
{rsx!(p { "More templating!" })},
|
||||
|
||||
// // Iterators within expressions
|
||||
// {
|
||||
// let data = std::collections::HashMap::<&'static str, &'static str>::new();
|
||||
// // Iterators *should* have keys when you can provide them.
|
||||
// // Keys make your app run faster. Make sure your keys are stable, unique, and predictable.
|
||||
// // Using an "ID" associated with your data is a good idea.
|
||||
// data.into_iter().map(|(k, v)| rsx!(li { key: "{k}", "{v}" }))
|
||||
// }
|
||||
// Iterators
|
||||
{(0..10).map(|i| rsx!(li { "{i}" }))},
|
||||
|
||||
// // Matching
|
||||
// match true {
|
||||
// true => rsx!( h1 {"Top text"}),
|
||||
// false => rsx!( h1 {"Bottom text"})
|
||||
// }
|
||||
// Iterators within expressions
|
||||
{
|
||||
let data = std::collections::HashMap::<&'static str, &'static str>::new();
|
||||
// Iterators *should* have keys when you can provide them.
|
||||
// Keys make your app run faster. Make sure your keys are stable, unique, and predictable.
|
||||
// Using an "ID" associated with your data is a good idea.
|
||||
data.into_iter().map(|(k, v)| rsx!(li { key: "{k}", "{v}" }))
|
||||
}
|
||||
|
||||
// // Conditional rendering
|
||||
// // Dioxus conditional rendering is based around None/Some. We have no special syntax for conditionals.
|
||||
// // You can convert a bool condition to rsx! with .then and .or
|
||||
// {true.then(|| rsx!(div {}))},
|
||||
// Matching
|
||||
match true {
|
||||
true => rsx!( h1 {"Top text"}),
|
||||
false => rsx!( h1 {"Bottom text"})
|
||||
}
|
||||
|
||||
// // Alternatively, you can use the "if" syntax - but both branches must be resolve to Element
|
||||
// if false {
|
||||
// h1 {"Top text"}
|
||||
// } else {
|
||||
// h1 {"Bottom text"}
|
||||
// }
|
||||
// Conditional rendering
|
||||
// Dioxus conditional rendering is based around None/Some. We have no special syntax for conditionals.
|
||||
// You can convert a bool condition to rsx! with .then and .or
|
||||
{true.then(|| rsx!(div {}))},
|
||||
|
||||
// // Using optionals for diverging branches
|
||||
// // Note that since this is wrapped in curlies, it's interpreted as an expression
|
||||
// {if true {
|
||||
// Some(rsx!(h1 {"Top text"}))
|
||||
// } else {
|
||||
// None
|
||||
// }}
|
||||
// Alternatively, you can use the "if" syntax - but both branches must be resolve to Element
|
||||
if false {
|
||||
h1 {"Top text"}
|
||||
} else {
|
||||
h1 {"Bottom text"}
|
||||
}
|
||||
|
||||
// // returning "None" without a diverging branch is a bit noisy... but rare in practice
|
||||
// {None as Option<()>},
|
||||
// Using optionals for diverging branches
|
||||
// Note that since this is wrapped in curlies, it's interpreted as an expression
|
||||
{if true {
|
||||
Some(rsx!(h1 {"Top text"}))
|
||||
} else {
|
||||
None
|
||||
}}
|
||||
|
||||
// // can also just use empty fragments
|
||||
// Fragment {}
|
||||
// returning "None" without a diverging branch is a bit noisy... but rare in practice
|
||||
{None as Option<()>},
|
||||
|
||||
// // Fragments let you insert groups of nodes without a parent.
|
||||
// // This lets you make components that insert elements as siblings without a container.
|
||||
// div {"A"}
|
||||
// Fragment {
|
||||
// div {"B"}
|
||||
// div {"C"}
|
||||
// Fragment {
|
||||
// "D"
|
||||
// Fragment {
|
||||
// "E"
|
||||
// "F"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// can also just use empty fragments
|
||||
Fragment {}
|
||||
|
||||
// // Components
|
||||
// // Can accept any paths
|
||||
// // Notice how you still get syntax highlighting and IDE support :)
|
||||
// Baller {}
|
||||
// baller::Baller {}
|
||||
// crate::baller::Baller {}
|
||||
// Fragments let you insert groups of nodes without a parent.
|
||||
// This lets you make components that insert elements as siblings without a container.
|
||||
div {"A"}
|
||||
Fragment {
|
||||
div {"B"}
|
||||
div {"C"}
|
||||
Fragment {
|
||||
"D"
|
||||
Fragment {
|
||||
"E"
|
||||
"F"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// // Can take properties
|
||||
// Taller { a: "asd" }
|
||||
// Components
|
||||
// Can accept any paths
|
||||
// Notice how you still get syntax highlighting and IDE support :)
|
||||
Baller {}
|
||||
baller::Baller {}
|
||||
crate::baller::Baller {}
|
||||
|
||||
// // Can take optional properties
|
||||
// Taller { a: "asd" }
|
||||
// Can take properties
|
||||
Taller { a: "asd" }
|
||||
|
||||
// // Can pass in props directly as an expression
|
||||
// {
|
||||
// let props = TallerProps {a: "hello", children: None };
|
||||
// rsx!(Taller { ..props })
|
||||
// }
|
||||
// Can take optional properties
|
||||
Taller { a: "asd" }
|
||||
|
||||
// // Spreading can also be overridden manually
|
||||
// Taller {
|
||||
// ..TallerProps { a: "ballin!", children: None },
|
||||
// a: "not ballin!"
|
||||
// }
|
||||
// Can pass in props directly as an expression
|
||||
{
|
||||
let props = TallerProps {a: "hello", children: None };
|
||||
rsx!(Taller { ..props })
|
||||
}
|
||||
|
||||
// // Can take children too!
|
||||
// Taller { a: "asd", div {"hello world!"} }
|
||||
// Spreading can also be overridden manually
|
||||
Taller {
|
||||
..TallerProps { a: "ballin!", children: None },
|
||||
a: "not ballin!"
|
||||
}
|
||||
|
||||
// // This component's props are defined *inline* with the `inline_props` macro
|
||||
// WithInline { text: "using functionc all syntax" }
|
||||
// Can take children too!
|
||||
Taller { a: "asd", div {"hello world!"} }
|
||||
|
||||
// // Components can be generic too
|
||||
// // This component takes i32 type to give you typed input
|
||||
// TypedInput::<i32> {}
|
||||
// This component's props are defined *inline* with the `inline_props` macro
|
||||
WithInline { text: "using functionc all syntax" }
|
||||
|
||||
// // Type inference can be used too
|
||||
// TypedInput { initial: 10.0 }
|
||||
// Components can be generic too
|
||||
// This component takes i32 type to give you typed input
|
||||
TypedInput::<i32> {}
|
||||
|
||||
// // geneircs with the `inline_props` macro
|
||||
// Label { text: "hello geneirc world!" }
|
||||
// Label { text: 99.9 }
|
||||
// Type inference can be used too
|
||||
TypedInput { initial: 10.0 }
|
||||
|
||||
// // Lowercase components work too, as long as they are access using a path
|
||||
// baller::lowercase_component {}
|
||||
// geneircs with the `inline_props` macro
|
||||
Label { text: "hello geneirc world!" }
|
||||
Label { text: 99.9 }
|
||||
|
||||
// // For in-scope lowercase components, use the `self` keyword
|
||||
// self::lowercase_helper {}
|
||||
// Lowercase components work too, as long as they are access using a path
|
||||
baller::lowercase_component {}
|
||||
|
||||
// // helper functions
|
||||
// // Anything that implements IntoVnode can be dropped directly into Rsx
|
||||
// {helper("hello world!")}
|
||||
// For in-scope lowercase components, use the `self` keyword
|
||||
self::lowercase_helper {}
|
||||
|
||||
// // Strings can be supplied directly
|
||||
// {String::from("Hello world!")}
|
||||
// helper functions
|
||||
// Anything that implements IntoVnode can be dropped directly into Rsx
|
||||
{helper("hello world!")}
|
||||
|
||||
// // So can format_args
|
||||
// {format_args!("Hello {}!", "world")}
|
||||
// Strings can be supplied directly
|
||||
{String::from("Hello world!")}
|
||||
|
||||
// // Or we can shell out to a helper function
|
||||
// {format_dollars(10, 50)}
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// So can format_args
|
||||
{format_args!("Hello {}!", "world")}
|
||||
|
||||
// fn format_dollars(dollars: u32, cents: u32) -> String {
|
||||
// format!("${dollars}.{cents:02}")
|
||||
// }
|
||||
// Or we can shell out to a helper function
|
||||
{format_dollars(10, 50)}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fn helper<'a>(cx: &'a ScopeState, text: &'a str) -> Element {
|
||||
// rsx! {
|
||||
// p { "{text}" }
|
||||
// }
|
||||
// }
|
||||
fn format_dollars(dollars: u32, cents: u32) -> String {
|
||||
format!("${dollars}.{cents:02}")
|
||||
}
|
||||
|
||||
// // no_case_check disables PascalCase checking if you *really* want a snake_case component.
|
||||
// // This will likely be deprecated/removed in a future update that will introduce a more polished linting system,
|
||||
// // something like Clippy.
|
||||
// #[component(no_case_check)]
|
||||
// fn lowercase_helper() -> Element {
|
||||
// rsx! {
|
||||
// "asd"
|
||||
// }
|
||||
// }
|
||||
fn helper(text: &str) -> Element {
|
||||
rsx! {
|
||||
p { "{text}" }
|
||||
}
|
||||
}
|
||||
|
||||
// mod baller {
|
||||
// use super::*;
|
||||
// no_case_check disables PascalCase checking if you *really* want a snake_case component.
|
||||
// This will likely be deprecated/removed in a future update that will introduce a more polished linting system,
|
||||
// something like Clippy.
|
||||
#[component(no_case_check)]
|
||||
fn lowercase_helper() -> Element {
|
||||
rsx! {
|
||||
"asd"
|
||||
}
|
||||
}
|
||||
|
||||
// #[component]
|
||||
// /// This component totally balls
|
||||
// pub fn Baller() -> Element {
|
||||
// todo!()
|
||||
// }
|
||||
mod baller {
|
||||
use super::*;
|
||||
|
||||
// // no_case_check disables PascalCase checking if you *really* want a snake_case component.
|
||||
// // This will likely be deprecated/removed in a future update that will introduce a more polished linting system,
|
||||
// // something like Clippy.
|
||||
// #[component(no_case_check)]
|
||||
// pub fn lowercase_component() -> Element {
|
||||
// rsx! { "look ma, no uppercase" }
|
||||
// }
|
||||
// }
|
||||
#[component]
|
||||
/// This component totally balls
|
||||
pub fn Baller() -> Element {
|
||||
rsx! { "ballin'" }
|
||||
}
|
||||
|
||||
// /// Documention for this component is visible within the rsx macro
|
||||
// #[component]
|
||||
// pub fn Taller(
|
||||
// /// Fields are documented and accessible in rsx!
|
||||
// a: &'static str,
|
||||
// children: Element,
|
||||
// ) -> Element {
|
||||
// rsx! { {&children} }
|
||||
// }
|
||||
// no_case_check disables PascalCase checking if you *really* want a snake_case component.
|
||||
// This will likely be deprecated/removed in a future update that will introduce a more polished linting system,
|
||||
// something like Clippy.
|
||||
#[component(no_case_check)]
|
||||
pub fn lowercase_component() -> Element {
|
||||
rsx! { "look ma, no uppercase" }
|
||||
}
|
||||
}
|
||||
|
||||
// #[derive(Props, PartialEq, Eq)]
|
||||
// pub struct TypedInputProps<T> {
|
||||
// #[props(optional, default)]
|
||||
// initial: Option<T>,
|
||||
// }
|
||||
/// Documention for this component is visible within the rsx macro
|
||||
#[component]
|
||||
pub fn Taller(
|
||||
/// Fields are documented and accessible in rsx!
|
||||
a: &'static str,
|
||||
children: Element,
|
||||
) -> Element {
|
||||
rsx! { {&children} }
|
||||
}
|
||||
|
||||
// #[allow(non_snake_case)]
|
||||
// pub fn TypedInput<T>(_: Scope<TypedInputProps<T>>) -> Element
|
||||
// where
|
||||
// T: FromStr + fmt::Display,
|
||||
// <T as FromStr>::Err: std::fmt::Display,
|
||||
// {
|
||||
// todo!()
|
||||
// }
|
||||
#[derive(Props, Clone, PartialEq, Eq)]
|
||||
pub struct TypedInputProps<T: 'static + Clone + PartialEq> {
|
||||
#[props(optional, default)]
|
||||
initial: Option<T>,
|
||||
}
|
||||
|
||||
// #[component]
|
||||
// fn WithInline(cx: Scope<'a>, text: &'a str) -> Element {
|
||||
// rsx! {
|
||||
// p { "{text}" }
|
||||
// }
|
||||
// }
|
||||
#[allow(non_snake_case)]
|
||||
pub fn TypedInput<T>(props: TypedInputProps<T>) -> Element
|
||||
where
|
||||
T: FromStr + fmt::Display + PartialEq + Clone + 'static,
|
||||
<T as FromStr>::Err: std::fmt::Display,
|
||||
{
|
||||
if let Some(props) = props.initial {
|
||||
return rsx! { "{props}" };
|
||||
}
|
||||
|
||||
// #[component]
|
||||
// fn Label<T: Clone + PartialEq>(text: T) -> Element
|
||||
// where
|
||||
// T: Display,
|
||||
// {
|
||||
// rsx! {
|
||||
// p { "{text}" }
|
||||
// }
|
||||
// }
|
||||
None
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn WithInline(text: String) -> Element {
|
||||
rsx! {
|
||||
p { "{text}" }
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Label<T: Clone + PartialEq + 'static>(text: T) -> Element
|
||||
where
|
||||
T: Display,
|
||||
{
|
||||
rsx! {
|
||||
p { "{text}" }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
//! Scroll elements using their MountedData
|
||||
//!
|
||||
//! Dioxus exposes a few helpful APIs around elements (mimicking the DOM APIs) to allow you to interact with elements
|
||||
//! across the renderers. This includes scrolling, reading dimensions, and more.
|
||||
//!
|
||||
//! In this example we demonstrate how to scroll to the top of the page using the `scroll_to` method on the `MountedData`
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
//! Add global shortcuts to your app while a component is active
|
||||
//!
|
||||
//! This demo shows how to add a global shortcut to your app that toggles a signal. You could use this to implement
|
||||
//! a raycast-type app, or to add a global shortcut to your app that toggles a component on and off.
|
||||
//!
|
||||
//! These are *global* shortcuts, so they will work even if your app is not in focus.
|
||||
|
||||
use dioxus::desktop::use_global_shortcut;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//! Dioxus supports shorthand syntax for creating elements and components.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
//! A simple example demonstrating how to use signals to modify state from several different places.
|
||||
//!
|
||||
//! This simlpe example implements a counter that can be incremented, decremented, and paused. It also demonstrates
|
||||
//! that background tasks in use_futures can modify the value as well.
|
||||
//!
|
||||
//! Most signals implement Into<ReadOnlySignal<T>>, making ReadOnlySignal a good default type when building new
|
||||
//! library components that don't need to modify their values.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use std::time::Duration;
|
||||
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
//! A few ways of mapping elements into rsx! syntax
|
||||
//!
|
||||
//! Rsx allows anything that's an iterator where the output type implements Into<Element>, so you can use any of the following:
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
|
|
|
@ -1,20 +1,32 @@
|
|||
#![allow(non_snake_case)]
|
||||
//! A simple example of a router with a few routes and a nav bar.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
// Launch the router, using our `Route` component as the generic type
|
||||
// This will automatically boot the app to "/" unless otherwise specified
|
||||
launch(|| rsx! { Router::<Route> {} });
|
||||
}
|
||||
|
||||
/// By default, the Routable derive will use the name of the variant as the route
|
||||
/// You can also specify a specific component by adding the Component name to the `#[route]` attribute
|
||||
#[rustfmt::skip]
|
||||
#[derive(Routable, Clone, PartialEq)]
|
||||
enum Route {
|
||||
// Wrap the app in a Nav layout
|
||||
#[layout(Nav)]
|
||||
#[route("/")]
|
||||
Homepage {},
|
||||
#[route("/")]
|
||||
Homepage {},
|
||||
|
||||
#[route("/blog/:id")]
|
||||
Blog { id: String },
|
||||
#[route("/blog/:id")]
|
||||
Blog { id: String },
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Homepage() -> Element {
|
||||
rsx! { h1 { "Welcome home" } }
|
||||
rsx! {
|
||||
h1 { "Welcome home" }
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
|
@ -25,6 +37,9 @@ fn Blog(id: String) -> Element {
|
|||
}
|
||||
}
|
||||
|
||||
/// A simple nav bar that links to the homepage and blog pages
|
||||
///
|
||||
/// The `Route` enum gives up typesafe routes, allowing us to rename routes and serialize them automatically
|
||||
#[component]
|
||||
fn Nav() -> Element {
|
||||
rsx! {
|
||||
|
@ -52,7 +67,3 @@ fn Nav() -> Element {
|
|||
div { Outlet::<Route> {} }
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
launch_desktop(|| rsx! { Router::<Route> {} });
|
||||
}
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
//! This example demonstrates how to use the spread operator to pass attributes to child components.
|
||||
//!
|
||||
//! This lets components like the `Link` allow the user to extend the attributes of the underlying `a` tag.
|
||||
//! These attributes are bundled into a `Vec<Attribute>` which can be spread into the child component using the `..` operator.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
//! Example: SSR
|
||||
//!
|
||||
//! This example shows how we can render the Dioxus Virtualdom using SSR.
|
||||
//! Dioxus' SSR is quite comprehensive and can generate a number of utility markers for things like hydration.
|
||||
//!
|
||||
//! You can also render without any markers to get a clean HTML output.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
launch_desktop(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut state = use_signal(|| 0);
|
||||
let mut depth = use_signal(|| 1_usize);
|
||||
|
||||
if depth() == 5 {
|
||||
return rsx! {
|
||||
div { "Max depth reached" }
|
||||
button { onclick: move |_| depth -= 1, "Remove depth" }
|
||||
};
|
||||
}
|
||||
|
||||
let items = use_memo(move || (0..depth()).map(|f| f as _).collect::<Vec<isize>>());
|
||||
|
||||
rsx! {
|
||||
button { onclick: move |_| state += 1, "Increment" }
|
||||
button { onclick: move |_| depth += 1, "Add depth" }
|
||||
button {
|
||||
onclick: move |_| async move {
|
||||
depth += 1;
|
||||
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||
dbg!(items.read());
|
||||
// if depth() is 5, this will be the old since the memo hasn't been re-computed
|
||||
// use_memos are only re-computed when the signals they capture change
|
||||
// *and* they are used in the current render
|
||||
// If the use_memo isn't used, it can't be re-computed!
|
||||
},
|
||||
"Add depth with sleep"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
//! Handle async streams using use_future and awaiting the next value.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use futures_util::{future, stream, Stream, StreamExt};
|
||||
use std::time::Duration;
|
||||
|
@ -10,8 +12,11 @@ fn app() -> Element {
|
|||
let mut count = use_signal(|| 10);
|
||||
|
||||
use_future(move || async move {
|
||||
// Create the stream.
|
||||
// This could be a network request, a file read, or any other async operation.
|
||||
let mut stream = some_stream();
|
||||
|
||||
// Await the next value from the stream.
|
||||
while let Some(second) = stream.next().await {
|
||||
count.set(second);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
//! Suspense in Dioxus
|
||||
//!
|
||||
//! Currently, `rsx!` does not accept futures as values. To achieve the functionality
|
||||
|
@ -49,6 +47,7 @@ fn app() -> Element {
|
|||
/// This component will re-render when the future has finished
|
||||
/// Suspense is achieved my moving the future into only the component that
|
||||
/// actually renders the data.
|
||||
#[component]
|
||||
fn Doggo() -> Element {
|
||||
let mut fut = use_resource(move || async move {
|
||||
#[derive(serde::Deserialize)]
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
// Thanks to @japsu and their project https://github.com/japsu/jatsi for the example!
|
||||
//! Thanks to @japsu and their project https://github.com/japsu/jatsi for the example!
|
||||
//!
|
||||
//! This example shows how to create a simple dice rolling app using SVG and Dioxus.
|
||||
//! The `svg` element and its children have a custom namespace, and are attached using different methods than regular
|
||||
//! HTML elements. Any element can specify a custom namespace by using the `namespace` meta attribute.
|
||||
//!
|
||||
//! If you `go-to-definition` on the `svg` element, you'll see its custom namespace.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use rand::{thread_rng, Rng};
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
//! Example: README.md showcase
|
||||
//!
|
||||
//! The example from the README.md.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use std::time::Duration;
|
||||
|
||||
fn main() {
|
||||
launch_desktop(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut count = use_signal(|| 0);
|
||||
|
||||
use_future(move || async move {
|
||||
loop {
|
||||
tokio::time::sleep(Duration::from_millis(1000)).await;
|
||||
count += 1;
|
||||
}
|
||||
});
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
h1 { "Current count: {count}" }
|
||||
button { onclick: move |_| count.set(0), "Reset the count" }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
// How to use textareas
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
launch_desktop(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut model = use_signal(|| String::from("asd"));
|
||||
|
||||
rsx! {
|
||||
textarea {
|
||||
class: "border",
|
||||
rows: "10",
|
||||
cols: "80",
|
||||
value: "{model}",
|
||||
oninput: move |e| model.set(e.value().clone()),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
#![allow(non_snake_case)]
|
||||
//! The typical TodoMVC app, implemented in Dioxus.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_elements::input_data::keyboard_types::Key;
|
||||
use std::collections::HashMap;
|
||||
|
@ -21,15 +22,21 @@ struct TodoItem {
|
|||
contents: String,
|
||||
}
|
||||
|
||||
const STYLE: &str = include_str!("./assets/todomvc.css");
|
||||
|
||||
fn app() -> Element {
|
||||
// We store the todos in a HashMap in a Signal.
|
||||
// Each key is the id of the todo, and the value is the todo itself.
|
||||
let mut todos = use_signal(HashMap::<u32, TodoItem>::new);
|
||||
|
||||
let filter = use_signal(|| FilterState::All);
|
||||
|
||||
// We use a simple memoized signal to calculate the number of active todos.
|
||||
// Whenever the todos change, the active_todo_count will be recalculated.
|
||||
let active_todo_count =
|
||||
use_memo(move || todos.read().values().filter(|item| !item.checked).count());
|
||||
|
||||
// We use a memoized signal to filter the todos based on the current filter state.
|
||||
// Whenever the todos or filter change, the filtered_todos will be recalculated.
|
||||
// Note that we're only storing the IDs of the todos, not the todos themselves.
|
||||
let filtered_todos = use_memo(move || {
|
||||
let mut filtered_todos = todos
|
||||
.read()
|
||||
|
@ -47,6 +54,8 @@ fn app() -> Element {
|
|||
filtered_todos
|
||||
});
|
||||
|
||||
// Toggle all the todos to the opposite of the current state.
|
||||
// If all todos are checked, uncheck them all. If any are unchecked, check them all.
|
||||
let toggle_all = move |_| {
|
||||
let check = active_todo_count() != 0;
|
||||
for (_, item) in todos.write().iter_mut() {
|
||||
|
@ -55,8 +64,8 @@ fn app() -> Element {
|
|||
};
|
||||
|
||||
rsx! {
|
||||
style { {include_str!("./assets/todomvc.css")} }
|
||||
section { class: "todoapp",
|
||||
style { {STYLE} }
|
||||
TodoHeader { todos }
|
||||
section { class: "main",
|
||||
if !todos.read().is_empty() {
|
||||
|
@ -69,17 +78,29 @@ fn app() -> Element {
|
|||
}
|
||||
label { r#for: "toggle-all" }
|
||||
}
|
||||
|
||||
// Render the todos using the filtered_todos signal
|
||||
// We pass the ID into the TodoEntry component so it can access the todo from the todos signal.
|
||||
// Since we store the todos in a signal too, we also need to send down the todo list
|
||||
ul { class: "todo-list",
|
||||
for id in filtered_todos() {
|
||||
TodoEntry { key: "{id}", id, todos }
|
||||
}
|
||||
}
|
||||
|
||||
// We only show the footer if there are todos.
|
||||
if !todos.read().is_empty() {
|
||||
ListFooter { active_todo_count, todos, filter }
|
||||
}
|
||||
}
|
||||
}
|
||||
PageFooter {}
|
||||
|
||||
// A simple info footer
|
||||
footer { class: "info",
|
||||
p { "Double-click to edit a todo" }
|
||||
p { "Created by " a { href: "http://github.com/jkelleyrtp/", "jkelleyrtp" } }
|
||||
p { "Part of " a { href: "http://todomvc.com", "TodoMVC" } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,21 +138,34 @@ fn TodoHeader(mut todos: Signal<HashMap<u32, TodoItem>>) -> Element {
|
|||
}
|
||||
}
|
||||
|
||||
/// A single todo entry
|
||||
/// This takes the ID of the todo and the todos signal as props
|
||||
/// We can use these together to memoize the todo contents and checked state
|
||||
#[component]
|
||||
fn TodoEntry(mut todos: Signal<HashMap<u32, TodoItem>>, id: u32) -> Element {
|
||||
let mut is_editing = use_signal(|| false);
|
||||
|
||||
// To avoid re-rendering this component when the todo list changes, we isolate our reads to memos
|
||||
// This way, the component will only re-render when the contents of the todo change, or when the editing state changes.
|
||||
// This does involve taking a local clone of the todo contents, but it allows us to prevent this component from re-rendering
|
||||
let checked = use_memo(move || todos.read().get(&id).unwrap().checked);
|
||||
let contents = use_memo(move || todos.read().get(&id).unwrap().contents.clone());
|
||||
|
||||
rsx! {
|
||||
li { class: if checked() { "completed" }, class: if is_editing() { "editing" },
|
||||
li {
|
||||
// Dioxus lets you use if statements in rsx to conditionally render attributes
|
||||
// These will get merged into a single class attribute
|
||||
class: if checked() { "completed" },
|
||||
class: if is_editing() { "editing" },
|
||||
|
||||
// Some basic controls for the todo
|
||||
div { class: "view",
|
||||
input {
|
||||
class: "toggle",
|
||||
r#type: "checkbox",
|
||||
id: "cbg-{id}",
|
||||
checked: "{checked}",
|
||||
oninput: move |evt| todos.write().get_mut(&id).unwrap().checked = evt.value().parse().unwrap(),
|
||||
oninput: move |evt| todos.write().get_mut(&id).unwrap().checked = evt.checked(),
|
||||
}
|
||||
label {
|
||||
r#for: "cbg-{id}",
|
||||
|
@ -145,6 +179,8 @@ fn TodoEntry(mut todos: Signal<HashMap<u32, TodoItem>>, id: u32) -> Element {
|
|||
prevent_default: "onclick"
|
||||
}
|
||||
}
|
||||
|
||||
// Only render the actual input if we're editing
|
||||
if is_editing() {
|
||||
input {
|
||||
class: "edit",
|
||||
|
@ -170,6 +206,8 @@ fn ListFooter(
|
|||
active_todo_count: ReadOnlySignal<usize>,
|
||||
mut filter: Signal<FilterState>,
|
||||
) -> Element {
|
||||
// We use a memoized signal to calculate whether we should show the "Clear completed" button.
|
||||
// This will recompute whenever the todos change, and if the value is true, the button will be shown.
|
||||
let show_clear_completed = use_memo(move || todos.read().values().any(|todo| todo.checked));
|
||||
|
||||
rsx! {
|
||||
|
@ -211,19 +249,3 @@ fn ListFooter(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn PageFooter() -> Element {
|
||||
rsx! {
|
||||
footer { class: "info",
|
||||
p { "Double-click to edit a todo" }
|
||||
p {
|
||||
"Created by "
|
||||
a { href: "http://github.com/jkelleyrtp/", "jkelleyrtp" }
|
||||
}
|
||||
p {
|
||||
"Part of "
|
||||
a { href: "http://todomvc.com", "TodoMVC" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,35 +1,26 @@
|
|||
//! Using `wry`'s http module, we can stream a video file from the local file system.
|
||||
//!
|
||||
//! You could load in any file type, but this example uses a video file.
|
||||
|
||||
use dioxus::desktop::wry::http;
|
||||
use dioxus::desktop::wry::http::Response;
|
||||
use dioxus::desktop::{use_asset_handler, AssetRequest};
|
||||
use dioxus::prelude::*;
|
||||
use http::{header::*, response::Builder as ResponseBuilder, status::StatusCode};
|
||||
use std::{io::SeekFrom, path::PathBuf};
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio::io::AsyncSeekExt;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
|
||||
|
||||
const VIDEO_PATH: &str = "./examples/assets/test_video.mp4";
|
||||
|
||||
fn main() {
|
||||
let video_file = PathBuf::from(VIDEO_PATH);
|
||||
if !video_file.exists() {
|
||||
tokio::runtime::Runtime::new()
|
||||
.unwrap()
|
||||
.block_on(async move {
|
||||
println!("Downloading video file...");
|
||||
let video_url =
|
||||
"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4";
|
||||
let mut response = reqwest::get(video_url).await.unwrap();
|
||||
let mut file = tokio::fs::File::create(&video_file).await.unwrap();
|
||||
while let Some(chunk) = response.chunk().await.unwrap() {
|
||||
file.write_all(&chunk).await.unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
// For the sake of this example, we will download the video file if it doesn't exist
|
||||
ensure_video_is_loaded();
|
||||
|
||||
launch_desktop(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
// Any request to /videos will be handled by this handler
|
||||
use_asset_handler("videos", move |request, responder| {
|
||||
// Using dioxus::spawn works, but is slower than a dedicated thread
|
||||
tokio::task::spawn(async move {
|
||||
|
@ -186,3 +177,21 @@ async fn get_stream_response(
|
|||
|
||||
http_response.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn ensure_video_is_loaded() {
|
||||
let video_file = PathBuf::from(VIDEO_PATH);
|
||||
if !video_file.exists() {
|
||||
tokio::runtime::Runtime::new()
|
||||
.unwrap()
|
||||
.block_on(async move {
|
||||
println!("Downloading video file...");
|
||||
let video_url =
|
||||
"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4";
|
||||
let mut response = reqwest::get(video_url).await.unwrap();
|
||||
let mut file = tokio::fs::File::create(&video_file).await.unwrap();
|
||||
while let Some(chunk) = response.chunk().await.unwrap() {
|
||||
file.write_all(&chunk).await.unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
//! Dioxus allows webcomponents to be created with a simple syntax.
|
||||
//!
|
||||
//! Read more about webcomponents [here](https://developer.mozilla.org/en-US/docs/Web/Web_Components)
|
||||
//!
|
||||
//! We typically suggest wrapping webcomponents in a strongly typed interface using a component.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
|
@ -6,8 +12,21 @@ fn main() {
|
|||
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
web-component {
|
||||
"my-prop": "5%",
|
||||
div {
|
||||
h1 { "Web Components" }
|
||||
CoolWebComponet { my_prop: "Hello, world!".to_string() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A web-component wrapped with a strongly typed interface using a component
|
||||
#[component]
|
||||
fn CoolWebComponet(my_prop: String) -> Element {
|
||||
rsx! {
|
||||
// rsx! takes a webcomponent as long as its tag name is separated with dashes
|
||||
web-component {
|
||||
// Since web-components don't have built-in attributes, the attribute names must be passed as a string
|
||||
"my-prop": my_prop,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,14 @@
|
|||
//! This example demonstrates how to handle window events and change window properties.
|
||||
//!
|
||||
//! We're able to do things like:
|
||||
//! - implement window dragging
|
||||
//! - toggle fullscreen
|
||||
//! - toggle always on top
|
||||
//! - toggle window decorations
|
||||
//! - change the window title
|
||||
//!
|
||||
//! The entire featuresuite of wry and tao is available to you
|
||||
|
||||
use dioxus::desktop::{window, Config, WindowBuilder};
|
||||
use dioxus::prelude::*;
|
||||
|
||||
|
@ -14,29 +25,40 @@ fn main() {
|
|||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut fullscreen = use_signal(|| false);
|
||||
let mut always_on_top = use_signal(|| false);
|
||||
let mut decorations = use_signal(|| false);
|
||||
|
||||
rsx!(
|
||||
link {
|
||||
href: "https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css",
|
||||
rel: "stylesheet"
|
||||
link { href: "https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", rel: "stylesheet" }
|
||||
Header {}
|
||||
div { class: "container mx-auto",
|
||||
div { class: "grid grid-cols-5",
|
||||
SetOnTop {}
|
||||
SetDecorations {}
|
||||
SetTitle {}
|
||||
}
|
||||
}
|
||||
header {
|
||||
class: "text-gray-400 bg-gray-900 body-font",
|
||||
onmousedown: move |_| window().drag(),
|
||||
)
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Header() -> Element {
|
||||
let mut fullscreen = use_signal(|| false);
|
||||
|
||||
rsx! {
|
||||
header { class: "text-gray-400 bg-gray-900 body-font", onmousedown: move |_| window().drag(),
|
||||
div { class: "container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center",
|
||||
a { class: "flex title-font font-medium items-center text-white mb-4 md:mb-0",
|
||||
span { class: "ml-3 text-xl", "Dioxus" }
|
||||
}
|
||||
nav { class: "md:ml-auto flex flex-wrap items-center text-base justify-center" }
|
||||
|
||||
// Set the window to minimized
|
||||
button {
|
||||
class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
|
||||
onmousedown: |evt| evt.stop_propagation(),
|
||||
onclick: move |_| window().set_minimized(true),
|
||||
"Minimize"
|
||||
}
|
||||
|
||||
// Toggle fullscreen
|
||||
button {
|
||||
class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
|
||||
onmousedown: |evt| evt.stop_propagation(),
|
||||
|
@ -47,6 +69,9 @@ fn app() -> Element {
|
|||
},
|
||||
"Fullscreen"
|
||||
}
|
||||
|
||||
// Close the window
|
||||
// If the window is the last window open, the app will close, if you configured the close behavior to do so
|
||||
button {
|
||||
class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
|
||||
onmousedown: |evt| evt.stop_propagation(),
|
||||
|
@ -55,40 +80,57 @@ fn app() -> Element {
|
|||
}
|
||||
}
|
||||
}
|
||||
br {}
|
||||
div { class: "container mx-auto",
|
||||
div { class: "grid grid-cols-5",
|
||||
div {
|
||||
button {
|
||||
class: "inline-flex items-center text-white bg-green-500 border-0 py-1 px-3 hover:bg-green-700 rounded",
|
||||
onmousedown: |evt| evt.stop_propagation(),
|
||||
onclick: move |_| {
|
||||
window().set_always_on_top(!always_on_top());
|
||||
always_on_top.toggle();
|
||||
},
|
||||
"Always On Top"
|
||||
}
|
||||
}
|
||||
div {
|
||||
button {
|
||||
class: "inline-flex items-center text-white bg-blue-500 border-0 py-1 px-3 hover:bg-green-700 rounded",
|
||||
onmousedown: |evt| evt.stop_propagation(),
|
||||
onclick: move |_| {
|
||||
window().set_decorations(!decorations());
|
||||
decorations.toggle();
|
||||
},
|
||||
"Set Decorations"
|
||||
}
|
||||
}
|
||||
div {
|
||||
button {
|
||||
class: "inline-flex items-center text-white bg-blue-500 border-0 py-1 px-3 hover:bg-green-700 rounded",
|
||||
onmousedown: |evt| evt.stop_propagation(),
|
||||
onclick: move |_| window().set_title("Dioxus Application"),
|
||||
"Change Title"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn SetOnTop() -> Element {
|
||||
let mut always_on_top = use_signal(|| false);
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
button {
|
||||
class: "inline-flex items-center text-white bg-green-500 border-0 py-1 px-3 hover:bg-green-700 rounded",
|
||||
onmousedown: |evt| evt.stop_propagation(),
|
||||
onclick: move |_| {
|
||||
window().set_always_on_top(!always_on_top());
|
||||
always_on_top.toggle();
|
||||
},
|
||||
"Always On Top"
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn SetDecorations() -> Element {
|
||||
let mut decorations = use_signal(|| false);
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
button {
|
||||
class: "inline-flex items-center text-white bg-blue-500 border-0 py-1 px-3 hover:bg-green-700 rounded",
|
||||
onmousedown: |evt| evt.stop_propagation(),
|
||||
onclick: move |_| {
|
||||
window().set_decorations(!decorations());
|
||||
decorations.toggle();
|
||||
},
|
||||
"Set Decorations"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn SetTitle() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
button {
|
||||
class: "inline-flex items-center text-white bg-blue-500 border-0 py-1 px-3 hover:bg-green-700 rounded",
|
||||
onmousedown: |evt| evt.stop_propagation(),
|
||||
onclick: move |_| window().set_title("Dioxus Application"),
|
||||
"Change Title"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
//! Listen for window focus events using a wry event handler
|
||||
//!
|
||||
//! This example shows how to use the use_wry_event_handler hook to listen for window focus events.
|
||||
//! We can intercept any Wry event, but in this case we're only interested in the WindowEvent::Focused event.
|
||||
//!
|
||||
//! This lets you do things like backgrounding tasks, pausing animations, or changing the UI when the window is focused or not.
|
||||
|
||||
use dioxus::desktop::tao::event::Event as WryEvent;
|
||||
use dioxus::desktop::tao::event::WindowEvent;
|
||||
use dioxus::desktop::use_wry_event_handler;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
//! Adjust the zoom of a desktop app
|
||||
//!
|
||||
//! This example shows how to adjust the zoom of a desktop app using the webview.zoom method.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
|
@ -8,6 +12,8 @@ fn app() -> Element {
|
|||
let mut level = use_signal(|| 1.0);
|
||||
|
||||
rsx! {
|
||||
h1 { "Zoom level: {level}" }
|
||||
p { "Change the zoom level of the webview by typing a number in the input below."}
|
||||
input {
|
||||
r#type: "number",
|
||||
value: "{level}",
|
||||
|
|
|
@ -37,11 +37,16 @@ impl WebviewInstance {
|
|||
dom: VirtualDom,
|
||||
shared: Rc<SharedContext>,
|
||||
) -> WebviewInstance {
|
||||
let window = cfg.window.clone().build(&shared.target).unwrap();
|
||||
let mut window = cfg.window.clone();
|
||||
|
||||
// tao makes small windows for some reason, make them bigger
|
||||
if cfg.window.window.inner_size.is_none() {
|
||||
window = window.with_inner_size(tao::dpi::LogicalSize::new(800.0, 600.0));
|
||||
}
|
||||
|
||||
// We assume that if the icon is None in cfg, then the user just didnt set it
|
||||
if cfg.window.window.window_icon.is_none() {
|
||||
window.set_window_icon(Some(
|
||||
window = window.with_window_icon(Some(
|
||||
tao::window::Icon::from_rgba(
|
||||
include_bytes!("./assets/default_icon.bin").to_vec(),
|
||||
460,
|
||||
|
@ -51,6 +56,8 @@ impl WebviewInstance {
|
|||
));
|
||||
}
|
||||
|
||||
let window = window.build(&shared.target).unwrap();
|
||||
|
||||
let mut web_context = WebContext::new(cfg.data_dir.clone());
|
||||
let edit_queue = EditQueue::default();
|
||||
let asset_handlers = AssetHandlerRegistry::new(dom.runtime());
|
||||
|
|
|
@ -90,6 +90,14 @@ impl FormData {
|
|||
self.inner.value()
|
||||
}
|
||||
|
||||
/// Get the value of the form event as a parsed type
|
||||
pub fn parsed<T>(&self) -> Result<T, T::Err>
|
||||
where
|
||||
T: std::str::FromStr,
|
||||
{
|
||||
self.value().parse()
|
||||
}
|
||||
|
||||
/// Try to parse the value as a boolean
|
||||
///
|
||||
/// Returns false if the value is not a boolean, or if it is false!
|
||||
|
|
|
@ -10,7 +10,7 @@ keywords = ["dom", "ui", "gui", "react"]
|
|||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
dioxus-desktop = { workspace = true, default-features = false, features = ["tokio_runtime"] }
|
||||
dioxus-desktop = { workspace = true, features = ["tokio_runtime"] }
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
|
|
@ -99,6 +99,10 @@ pub struct LinkProps {
|
|||
/// The onclick event handler.
|
||||
pub onclick: Option<EventHandler<MouseEvent>>,
|
||||
|
||||
/// The onmounted event handler.
|
||||
/// Fired when the <a> element is mounted.
|
||||
pub onmounted: Option<EventHandler<MountedEvent>>,
|
||||
|
||||
#[props(default)]
|
||||
/// Whether the default behavior should be executed if an `onclick` handler is provided.
|
||||
///
|
||||
|
@ -269,10 +273,17 @@ pub fn Link(props: LinkProps) -> Element {
|
|||
}
|
||||
};
|
||||
|
||||
let onmounted = move |event| {
|
||||
if let Some(handler) = props.onmounted.clone() {
|
||||
handler.call(event);
|
||||
}
|
||||
};
|
||||
|
||||
rsx! {
|
||||
a {
|
||||
onclick: action,
|
||||
href,
|
||||
onmounted: onmounted,
|
||||
prevent_default,
|
||||
class,
|
||||
rel,
|
||||
|
|
Loading…
Reference in a new issue