Update examples, add css

This commit is contained in:
Jonathan Kelley 2024-02-14 12:33:07 -08:00
parent 25d103c1a5
commit bdbae8ccb0
No known key found for this signature in database
GPG key ID: 1FBB50F7EB0A08BE
57 changed files with 965 additions and 596 deletions

1
Cargo.lock generated
View file

@ -2565,6 +2565,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"tokio", "tokio",
"warp",
] ]
[[package]] [[package]]

View file

@ -125,6 +125,7 @@ publish = false
manganis = { workspace = true, optional = true} manganis = { workspace = true, optional = true}
reqwest = { version = "0.11.9", features = ["json"], optional = true} reqwest = { version = "0.11.9", features = ["json"], optional = true}
http-range = {version = "0.1.5", optional = true } http-range = {version = "0.1.5", optional = true }
warp = { version = "0.3.0", optional = true }
[dev-dependencies] [dev-dependencies]
dioxus = { workspace = true, features = ["router"]} dioxus = { workspace = true, features = ["router"]}
@ -154,7 +155,7 @@ server = ["dioxus/axum"]
default = ["dioxus/desktop"] default = ["dioxus/desktop"]
web = ["dioxus/web"] web = ["dioxus/web"]
collect-assets = ["manganis"] collect-assets = ["manganis"]
http = ["reqwest", "http-range"] http = ["reqwest", "http-range", "warp"]
[[example]] [[example]]
name = "login_form" name = "login_form"

View file

@ -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 dioxus::prelude::*;
use std::{collections::VecDeque, fmt::Debug, rc::Rc}; use std::{collections::VecDeque, fmt::Debug, rc::Rc};
@ -5,41 +10,24 @@ fn main() {
launch(app); 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 { 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 log_event = move |event: Rc<dyn Debug>| {
let mut events = events.write(); // Only store the last 20 events
if events.read().len() >= 20 {
if events.len() >= MAX_EVENTS { events.write().pop_front();
events.pop_front();
} }
events.write().push_back(event);
events.push_back(event);
}; };
rsx! { rsx! {
div { style: "{CONTAINER_STYLE}", style { {include_str!("./assets/events.css")} }
div { id: "container",
// focusing is necessary to catch keyboard events // 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()), onmousemove: move |event| log_event(event.data()),
onclick: move |event| log_event(event.data()), onclick: move |event| log_event(event.data()),
ondoubleclick: 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" "Hover, click, type or scroll to see the info down below"
} }
div { div { id: "log",
for event in events.read().iter() { for event in events.read().iter() {
div { "{event:?}" } div { "{event:?}" }
} }

23
examples/assets/clock.css Normal file
View 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;
}

View 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
View 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;
}

View 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;
}

View file

View 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;
}

View 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);
}

View 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
View 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;
}

View 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;
}

View file

@ -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::*; use dioxus::prelude::*;
fn main() { fn main() {
@ -10,17 +19,17 @@ fn app() -> Element {
let child = use_memo(move || { let child = use_memo(move || {
rsx! { rsx! {
Child { Child { count }
count
}
} }
}); });
rsx! { rsx! {
// Some toggle/controls to show the child or increment the count
button { onclick: move |_| show_child.toggle(), "Toggle child" } button { onclick: move |_| show_child.toggle(), "Toggle child" }
button { onclick: move |_| count += 1, "Increment count" } button { onclick: move |_| count += 1, "Increment count" }
if show_child() { if show_child() {
{child.cloned()} {child()}
} }
} }
} }
@ -44,12 +53,12 @@ fn Child(count: Signal<i32>) -> Element {
} }
}); });
use_effect(move || { use_effect(move || println!("Child count: {}", count()));
println!("Child count: {}", count());
});
rsx! { rsx! {
"hellO!" div {
"Child component"
{early} {early}
} }
}
} }

View file

@ -1,7 +1,12 @@
/* //! Calculator
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. //! 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::events::*;
use dioxus::html::input_data::keyboard_types::Key; use dioxus::html::input_data::keyboard_types::Key;

View file

@ -1,38 +1,28 @@
#![allow(non_snake_case)] //! 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
//! Example: Calculator //! 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 //! Generally, you'll want to split your state into several signals if you have a large application, but for small
//! methods to be performed on them. Dioxus exposes the ability to use the model pattern through the "use_model" hook. //! applications, or focused components, this is a great way to manage state.
//!
//! 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.
use dioxus::desktop::tao::dpi::LogicalSize; use dioxus::desktop::tao::dpi::LogicalSize;
use dioxus::desktop::{Config, WindowBuilder}; use dioxus::desktop::{Config, WindowBuilder};
use dioxus::events::*;
use dioxus::html::input_data::keyboard_types::Key; use dioxus::html::input_data::keyboard_types::Key;
use dioxus::html::MouseEvent; use dioxus::html::MouseEvent;
use dioxus::prelude::*; use dioxus::prelude::*;
fn main() { fn main() {
let cfg = Config::new().with_window( LaunchBuilder::desktop()
.with_cfg(
Config::new().with_window(
WindowBuilder::new() WindowBuilder::new()
.with_title("Calculator Demo") .with_title("Calculator Demo")
.with_resizable(false) .with_resizable(false)
.with_inner_size(LogicalSize::new(320.0, 530.0)), .with_inner_size(LogicalSize::new(320.0, 530.0)),
); ),
)
LaunchBuilder::desktop().with_cfg(cfg).launch(app); .launch(app);
} }
const STYLE: &str = include_str!("./assets/calculator.css"); const STYLE: &str = include_str!("./assets/calculator.css");
@ -109,6 +99,7 @@ struct Calculator {
waiting_for_operand: bool, waiting_for_operand: bool,
cur_val: f64, cur_val: f64,
} }
#[derive(Clone)] #[derive(Clone)]
enum Operator { enum Operator {
Add, Add,
@ -116,6 +107,7 @@ enum Operator {
Mul, Mul,
Div, Div,
} }
impl Calculator { impl Calculator {
fn new() -> Self { fn new() -> Self {
Calculator { Calculator {

View file

@ -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::*; use dioxus::prelude::*;
fn main() { fn main() {
@ -5,20 +10,36 @@ fn main() {
} }
fn app() -> Element { fn app() -> Element {
let mut count = use_signal(|| 0); let mut millis = use_signal(|| 0);
use_future(move || async move { use_future(move || async move {
// Save our initial timea
let start = std::time::Instant::now();
loop { loop {
tokio::time::sleep(std::time::Duration::from_millis(10)).await; // In lieu of an interval, we just sleep until the next update
count += 1; 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 || { // Format the time as a string
println!("High-Five counter: {}", count()); // 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! { 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}" }
}
} }
} }

View file

@ -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 std::rc::Rc;
use dioxus::prelude::*; use dioxus::prelude::*;
@ -7,6 +12,7 @@ fn main() {
} }
fn app() -> Element { 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 elements = use_signal(Vec::<Rc<MountedData>>::new);
let mut running = use_signal(|| true); let mut running = use_signal(|| true);
@ -14,7 +20,7 @@ fn app() -> Element {
let mut focused = 0; let mut focused = 0;
loop { loop {
tokio::time::sleep(std::time::Duration::from_millis(10)).await; tokio::time::sleep(std::time::Duration::from_millis(50)).await;
if !running() { if !running() {
continue; continue;
@ -31,17 +37,24 @@ fn app() -> Element {
}); });
rsx! { rsx! {
div { style { {include_str!("./assets/roulette.css")} }
h1 { "Input Roulette" } 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 { for i in 0..100 {
input { input {
r#type: "number",
value: "{i}", value: "{i}",
onmounted: move |cx| { onmounted: move |cx| elements.write().push(cx.data()),
elements.write().push(cx.data()); oninput: move |_| running.set(false),
},
oninput: move |_| {
running.set(false);
}
} }
} }
} }

View file

@ -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
View 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" }
}
}
}
}

View file

@ -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::*; use dioxus::prelude::*;
fn main() { fn main() {
@ -16,7 +26,7 @@ fn main() {
integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5", integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5",
crossorigin: "anonymous" crossorigin: "anonymous"
} }
style { {r#" .red { background-color: rgb(202, 60, 60) !important; } "#} } style { {include_str!("./assets/crm.css")} }
h1 { "Dioxus CRM Example" } h1 { "Dioxus CRM Example" }
Router::<Route> {} Router::<Route> {}
} }
@ -32,23 +42,24 @@ struct Client {
description: String, description: String,
} }
/// The pages of the app, each with a route
#[derive(Routable, Clone)] #[derive(Routable, Clone)]
enum Route { enum Route {
#[route("/")] #[route("/")]
ClientList, List,
#[route("/new")] #[route("/new")]
ClientAdd, New,
#[route("/settings")] #[route("/settings")]
Settings, Settings,
} }
#[component] #[component]
fn ClientList() -> Element { fn List() -> Element {
rsx! { rsx! {
h2 { "List of Clients" } 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" } Link { to: Route::Settings, class: "pure-button", "Settings" }
for client in CLIENTS.read().iter() { for client in CLIENTS.read().iter() {
div { class: "client", style: "margin-bottom: 50px", div { class: "client", style: "margin-bottom: 50px",
@ -60,12 +71,12 @@ fn ClientList() -> Element {
} }
#[component] #[component]
fn ClientAdd() -> Element { fn New() -> Element {
let mut first_name = use_signal(String::new); let mut first_name = use_signal(String::new);
let mut last_name = use_signal(String::new); let mut last_name = use_signal(String::new);
let mut description = use_signal(String::new); let mut description = use_signal(String::new);
let submit_client = move |_: FormEvent| { let submit_client = move |_| {
// Write the client // Write the client
CLIENTS.write().push(Client { CLIENTS.write().push(Client {
first_name: first_name(), first_name: first_name(),
@ -74,7 +85,7 @@ fn ClientAdd() -> Element {
}); });
// And then navigate back to the client list // And then navigate back to the client list
dioxus::router::router().push(Route::ClientList); router().push(Route::List);
}; };
rsx! { rsx! {
@ -87,7 +98,7 @@ fn ClientAdd() -> Element {
id: "first_name", id: "first_name",
r#type: "text", r#type: "text",
placeholder: "First Name…", placeholder: "First Name…",
required: "", required: true,
value: "{first_name}", value: "{first_name}",
oninput: move |e| first_name.set(e.value()), oninput: move |e| first_name.set(e.value()),
@ -99,19 +110,19 @@ fn ClientAdd() -> Element {
} }
div { class: "pure-control-group", div { class: "pure-control-group",
label { "for": "last_name", "Last Name" } label { r#for: "last_name", "Last Name" }
input { input {
id: "last_name", id: "last_name",
r#type: "text", r#type: "text",
placeholder: "Last Name…", placeholder: "Last Name…",
required: "", required: true,
value: "{last_name}", value: "{last_name}",
oninput: move |e| last_name.set(e.value()) oninput: move |e| last_name.set(e.value())
} }
} }
div { class: "pure-control-group", div { class: "pure-control-group",
label { "for": "description", "Description" } label { r#for: "description", "Description" }
textarea { textarea {
id: "description", id: "description",
placeholder: "Description…", placeholder: "Description…",
@ -122,7 +133,7 @@ fn ClientAdd() -> Element {
div { class: "pure-controls", div { class: "pure-controls",
button { r#type: "submit", class: "pure-button pure-button-primary", "Save" } 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", class: "pure-button pure-button-primary red",
onclick: move |_| { onclick: move |_| {
CLIENTS.write().clear(); CLIENTS.write().clear();
dioxus::router::router().push(Route::ClientList); dioxus::router::router().push(Route::List);
}, },
"Remove all Clients" "Remove all Clients"
} }
Link { to: Route::ClientList, class: "pure-button", "Go back" } Link { to: Route::List, class: "pure-button", "Go back" }
} }
} }

View file

@ -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::*; use dioxus::prelude::*;
#[cfg(not(feature = "collect-assets"))] #[cfg(not(feature = "collect-assets"))]
@ -14,7 +21,7 @@ fn main() {
fn app() -> Element { fn app() -> Element {
rsx! { rsx! {
div { div {
p { "This should show an image:" } h1 { "This should show an image:" }
img { src: ASSET_PATH.to_string() } img { src: ASSET_PATH.to_string() }
} }
} }

View file

@ -1,28 +1,22 @@
//! This example shows how to use a custom index.html and custom <HEAD> extensions //! 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. //! to add things like stylesheets, scripts, and third-party JS libraries.
use dioxus::desktop::Config;
use dioxus::prelude::*; use dioxus::prelude::*;
fn main() { fn main() {
LaunchBuilder::desktop() LaunchBuilder::desktop()
.with_cfg( .with_cfg(
Config::new().with_custom_head("<style>body { background-color: red; }</style>".into()), dioxus::desktop::Config::new().with_custom_index(
)
.launch(app);
LaunchBuilder::desktop()
.with_cfg(
Config::new().with_custom_index(
r#" r#"
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>Dioxus app</title> <title>Dioxus app</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>body { background-color: blue; }</style> <style>body { background-color: olive; }</style>
</head> </head>
<body> <body>
<h1>External HTML</h1>
<div id="main"></div> <div id="main"></div>
</body> </body>
</html> </html>
@ -35,6 +29,6 @@ fn main() {
fn app() -> Element { fn app() -> Element {
rsx! { rsx! {
div { h1 { "hello world!" } } h1 { "Custom HTML!" }
} }
} }

View file

@ -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::*; use dioxus::prelude::*;
fn main() { fn main() {
@ -8,13 +12,12 @@ fn app() -> Element {
let mut disabled = use_signal(|| false); let mut disabled = use_signal(|| false);
rsx! { rsx! {
div { div { style: "text-align: center; margin: 20px; display: flex; flex-direction: column; align-items: center;",
button { onclick: move |_| disabled.toggle(), button { onclick: move |_| disabled.toggle(),
"click to " "click to "
if disabled() { "enable" } else { "disable" } if disabled() { "enable" } else { "disable" }
" the lower button" " the lower button"
} }
button { disabled, "lower button" } button { disabled, "lower button" }
} }
} }

View file

@ -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 dioxus::prelude::*;
use std::collections::HashMap; use std::collections::HashMap;
@ -6,8 +15,18 @@ fn main() {
} }
fn app() -> Element { 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()); 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 { 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") let list = reqwest::get("https://dog.ceo/api/breeds/list/all")
.await .await
.unwrap() .unwrap()
@ -19,7 +38,7 @@ fn app() -> Element {
}; };
rsx! { 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}", li { key: "{cur_breed}",
button { onclick: move |_| breed.set(cur_breed.clone()), button { onclick: move |_| breed.set(cur_breed.clone()),
"{cur_breed}" "{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 { let Some(breed_list) = breed_list() else {
return rsx! { "loading breeds..." }; return rsx! { "loading breeds..." };
}; };
rsx! { rsx! {
style { {include_str!("./assets/dog_app.css")} }
h1 { "Select a dog breed!" } h1 { "Select a dog breed!" }
div { height: "500px", display: "flex", div { height: "500px", display: "flex",
ul { flex: "50%", {breed_list} } ul { width: "100px", {breed_list} }
div { flex: "50%", BreedPic { breed } } div { flex: 1, BreedPic { breed } }
} }
} }
} }
#[component] #[component]
fn BreedPic(breed: Signal<String>) -> Element { fn BreedPic(breed: Signal<String>) -> Element {
// This resource will restart whenever the breed changes
let mut fut = use_resource(move || async move { 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")) reqwest::get(format!("https://dog.ceo/api/breed/{breed}/images/random"))
.await .await
.unwrap() .unwrap()
@ -61,13 +89,3 @@ fn BreedPic(breed: Signal<String>) -> Element {
None => rsx! { "loading image..." }, 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,
}

View file

@ -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::desktop::{use_asset_handler, wry::http::Response};
use dioxus::prelude::*; use dioxus::prelude::*;
@ -16,8 +22,8 @@ fn app() -> Element {
}); });
rsx! { rsx! {
div { style { {include_str!("./assets/custom_assets.css")} }
h1 { "Dynamic Assets" }
img { src: "/logos/logo.png" } img { src: "/logos/logo.png" }
} }
}
} }

View file

@ -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::*}; use dioxus::{dioxus_core::CapturedError, prelude::*};
fn main() { fn main() {
@ -7,7 +14,10 @@ fn main() {
fn app() -> Element { fn app() -> Element {
rsx! { rsx! {
ErrorBoundary { ErrorBoundary {
handle_error: |error: CapturedError| rsx! {"Found error {error}"}, handle_error: |error: CapturedError| rsx! {
h1 { "An error occurred" }
pre { "{error:#?}" }
},
DemoC { x: 1 } DemoC { x: 1 }
} }
} }
@ -15,11 +25,18 @@ fn app() -> Element {
#[component] #[component]
fn DemoC(x: i32) -> Element { fn DemoC(x: i32) -> Element {
let result = Err("Error");
result.throw()?;
rsx! { 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"
}
} }
} }

View file

@ -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::*; use dioxus::prelude::*;
fn main() { fn main() {
@ -5,20 +10,33 @@ fn main() {
} }
fn app() -> Element { fn app() -> Element {
// Create a future that will resolve once the javascript has been succesffully executed.
let future = use_resource(move || async move { 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( let mut eval = eval(
r#" r#"
dioxus.send("Hi from JS!"); dioxus.send("Hi from JS!");
let msg = await dioxus.recv(); let msg = await dioxus.recv();
console.log(msg); console.log(msg);
return "hello world"; return "hi from JS!";
"#, "#,
) )
.unwrap(); .unwrap();
// Send a message to the JS code.
eval.send("Hi from Rust!".into()).unwrap(); 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(); let res = eval.recv().await.unwrap();
// This will print "Hi from JS!" and "Hi from Rust!".
println!("{:?}", eval.await); println!("{:?}", eval.await);
res res
}); });

View file

@ -1,12 +1,9 @@
//! Example: File Explorer //! Example: File Explorer
//! -------------------------
//! //!
//! This is a fun little desktop application that lets you explore the file system. //! 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. //! This example is interesting because it's mixing filesystem operations and GUI, which is typically hard for UI to do.
//! //! We store the state entirely in a single signal, making the explorer logic fairly easy to reason about.
//! 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.
use dioxus::desktop::{Config, WindowBuilder}; use dioxus::desktop::{Config, WindowBuilder};
use dioxus::prelude::*; use dioxus::prelude::*;
@ -37,12 +34,11 @@ fn app() -> Element {
} }
style { "{_STYLE}" } style { "{_STYLE}" }
main { main {
{files.read().path_names.iter().enumerate().map(|(dir_id, path)| { for (dir_id, path) in files.read().path_names.iter().enumerate() {
{
let path_end = path.split('/').last().unwrap_or(path.as_str()); let path_end = path.split('/').last().unwrap_or(path.as_str());
rsx! ( rsx! {
div { div { class: "folder", key: "{path}",
class: "folder",
key: "{path}",
i { class: "material-icons", i { class: "material-icons",
onclick: move |_| files.write().enter_dir(dir_id), onclick: move |_| files.write().enter_dir(dir_id),
if path_end.contains('.') { if path_end.contains('.') {
@ -54,8 +50,9 @@ fn app() -> Element {
} }
h1 { "{path_end}" } h1 { "{path_end}" }
} }
) }
})}, }
}
if let Some(err) = files.read().err.as_ref() { if let Some(err) = files.read().err.as_ref() {
div { div {
code { "{err}" } 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 { struct Files {
path_stack: Vec<String>, path_stack: Vec<String>,
path_names: Vec<String>, path_names: Vec<String>,

View file

@ -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::html::HasFileData;
use dioxus::prelude::*; use dioxus::prelude::*;
use tokio::time::sleep; use tokio::time::sleep;
fn main() { fn main() {
launch(App); launch(app);
} }
fn App() -> Element { fn app() -> Element {
let mut enable_directory_upload = use_signal(|| false); let mut enable_directory_upload = use_signal(|| false);
let mut files_uploaded = use_signal(|| Vec::new() as Vec<String>); let mut files_uploaded = use_signal(|| Vec::new() as Vec<String>);
@ -31,12 +35,16 @@ fn App() -> Element {
}; };
rsx! { rsx! {
label { style { {include_str!("./assets/file_upload.css")} }
input { input {
r#type: "checkbox", r#type: "checkbox",
id: "directory-upload",
checked: enable_directory_upload, checked: enable_directory_upload,
oninput: move |evt| enable_directory_upload.set(evt.checked()), oninput: move |evt| enable_directory_upload.set(evt.checked()),
}, },
label {
r#for: "directory-upload",
"Enable directory upload" "Enable directory upload"
} }
@ -47,16 +55,18 @@ fn App() -> Element {
directory: enable_directory_upload, directory: enable_directory_upload,
onchange: upload_files, onchange: upload_files,
} }
div { div {
width: "100px", // cheating with a little bit of JS...
height: "100px", "ondragover": "this.style.backgroundColor='#88FF88';",
border: "1px solid black", "ondragleave": "this.style.backgroundColor='#FFFFFF';",
id: "drop-zone",
prevent_default: "ondrop dragover dragenter", prevent_default: "ondrop dragover dragenter",
ondrop: handle_file_drop, ondrop: handle_file_drop,
ondragover: move |event| event.stop_propagation(), ondragover: move |event| event.stop_propagation(),
"Drop files here" "Drop files here"
} }
ul { ul {
for file in files_uploaded.read().iter() { for file in files_uploaded.read().iter() {
li { "{file}" } li { "{file}" }

View 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" } }
)
}

View file

@ -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::*; use dioxus::prelude::*;
fn main() { fn main() {
launch(|| { launch(|| {
rsx! { rsx! {
style { {include_str!("./assets/flat_router.css")} }
Router::<Route> {} Router::<Route> {}
} }
}) })
@ -11,7 +21,7 @@ fn main() {
#[derive(Routable, Clone)] #[derive(Routable, Clone)]
#[rustfmt::skip] #[rustfmt::skip]
enum Route { enum Route {
#[layout(Footer)] #[layout(Footer)] // wrap the entire app in a footer
#[route("/")] #[route("/")]
Home {}, Home {},
@ -28,45 +38,47 @@ enum Route {
#[component] #[component]
fn Footer() -> Element { fn Footer() -> Element {
rsx! { rsx! {
Outlet::<Route> {}
p { "----" }
nav { nav {
style { {STYLE} }
Link { to: Route::Home {}, class: "nav-btn", "Home" } Link { to: Route::Home {}, class: "nav-btn", "Home" }
Link { to: Route::Games {}, class: "nav-btn", "Games" } Link { to: Route::Games {}, class: "nav-btn", "Games" }
Link { to: Route::Play {}, class: "nav-btn", "Play" } Link { to: Route::Play {}, class: "nav-btn", "Play" }
Link { to: Route::Settings {}, class: "nav-btn", "Settings" } Link { to: Route::Settings {}, class: "nav-btn", "Settings" }
} }
div { id: "content",
Outlet::<Route> {}
}
} }
} }
#[component] #[component]
fn Home() -> Element { 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] #[component]
fn Games() -> Element { 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] #[component]
fn Play() -> Element { 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] #[component]
fn Settings() -> Element { 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;
}
"#;

View file

@ -1,7 +1,7 @@
//! Forms //! Forms
//! //!
//! Dioxus forms deviate slightly from html, automatically returning all named inputs //! Dioxus forms deviate slightly from html, automatically returning all named inputs
//! in the "values" field //! in the "values" field.
use dioxus::prelude::*; use dioxus::prelude::*;

38
examples/future.rs Normal file
View 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" }
}
}
}

View file

@ -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 dioxus::prelude::*;
use std::fmt::Display;
fn main() { fn main() {
launch_desktop(app); launch_desktop(app);

View file

@ -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::*; use dioxus::prelude::*;
@ -13,8 +16,43 @@ static DOUBLED_COUNT: GlobalMemo<i32> = Signal::global_memo(|| COUNT() * 2);
fn app() -> Element { fn app() -> Element {
rsx! { 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!" } button { onclick: move |_| *COUNT.write() += 1, "Up high!" }
}
}
#[component]
fn Decrement() -> Element {
rsx! {
button { onclick: move |_| *COUNT.write() -= 1, "Down low!" } 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" }
}
}

View file

@ -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::*; use dioxus::prelude::*;
fn main() { fn main() {

View file

@ -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"
}
}
}
}

View file

@ -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::*; use dioxus::prelude::*;
fn main() { fn main() {
launch_desktop(App); launch_desktop(app);
} }
#[component] fn app() -> Element {
fn App() -> Element {
rsx! ( rsx! (
div { style { {include_str!("./assets/links.css")} }
p { a { href: "http://dioxuslabs.com/", "Default link - links outside of your app" } } Router::<Route> {}
p {
a {
href: "http://dioxuslabs.com/",
prevent_default: "onclick",
onclick: |_| println!("Hello Dioxus"),
"Custom event link - links inside of your app"
}
}
}
div { Router::<Route> {} }
) )
} }
@ -28,6 +25,10 @@ enum Route {
#[layout(Header)] #[layout(Header)]
#[route("/")] #[route("/")]
Home {}, Home {},
#[route("/default-links")]
DefaultLinks {},
#[route("/settings")] #[route("/settings")]
Settings {}, Settings {},
} }
@ -36,14 +37,11 @@ enum Route {
fn Header() -> Element { fn Header() -> Element {
rsx! { rsx! {
h1 { "Your app here" } h1 { "Your app here" }
ul { nav { id: "nav",
li {
Link { to: Route::Home {}, "home" } Link { to: Route::Home {}, "home" }
} Link { to: Route::DefaultLinks {}, "default links" }
li {
Link { to: Route::Settings {}, "settings" } Link { to: Route::Settings {}, "settings" }
} }
}
Outlet::<Route> {} Outlet::<Route> {}
} }
} }
@ -57,3 +55,23 @@ fn Home() -> Element {
fn Settings() -> Element { fn Settings() -> Element {
rsx!( h1 { "Settings" } ) 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"
}
}
}
}

View file

@ -1,5 +1,10 @@
//! This example demonstrates the following: //! Implementing a login form
//! Futures in a callback, Router, and Forms //!
//! 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::*; use dioxus::prelude::*;

View file

@ -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::*; use dioxus::prelude::*;
fn main() { fn main() {

View file

@ -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::*; use dioxus::prelude::*;
fn main() { fn main() {

View file

@ -1,8 +1,7 @@
#![allow(non_snake_case)] //! Optional props
//! Example: README.md showcase
//! //!
//! 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::*; use dioxus::prelude::*;
@ -12,19 +11,27 @@ fn main() {
fn app() -> Element { fn app() -> Element {
rsx! { 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 { Button {
a: "asd".to_string(), a: "asd".to_string(),
// b can be omitted, and it will be filled with its default value
c: "asd".to_string(), c: "asd".to_string(),
d: Some("asd".to_string()), d: Some("asd".to_string()),
e: Some("asd".to_string()), e: Some("asd".to_string()),
} }
Button { Button {
a: "asd".to_string(), a: "asd".to_string(),
b: "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(), c: "asd".to_string(),
d: Some("asd".to_string()), d: Some("asd".to_string()),
e: "asd".to_string(), e: "asd".to_string(),
} }
// `b` and `e` are ommitted
Button { Button {
a: "asd".to_string(), a: "asd".to_string(),
c: "asd".to_string(), c: "asd".to_string(),
@ -51,6 +58,7 @@ struct ButtonProps {
type SthElse<T> = Option<T>; type SthElse<T> = Option<T>;
#[allow(non_snake_casea)]
fn Button(props: ButtonProps) -> Element { fn Button(props: ButtonProps) -> Element {
rsx! { rsx! {
button { button {

View file

@ -1,4 +1,14 @@
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, use_wry_event_handler, LogicalSize,
WindowBuilder,
};
use dioxus::prelude::*; use dioxus::prelude::*;
fn main() { fn main() {
@ -6,7 +16,12 @@ fn main() {
} }
fn app() -> Element { fn app() -> Element {
let mut show_overlay = use_signal(|| true);
use_global_shortcut("cmd+g", move || show_overlay.toggle());
rsx! { rsx! {
if show_overlay() {
div { div {
width: "100%", width: "100%",
height: "100%", height: "100%",
@ -23,6 +38,7 @@ fn app() -> Element {
"This is an overlay!" "This is an overlay!"
} }
} }
}
} }
fn make_config() -> dioxus::desktop::Config { fn make_config() -> dioxus::desktop::Config {

View file

@ -1,9 +1,8 @@
//! This example shows how to create a popup window and send data back to the parent window. //! This example shows how to create a popup window and send data back to the parent window.
//! Currently Dioxus doesn't support nested renderers, hence the need to create popups as separate windows.
use std::rc::Rc;
use dioxus::prelude::*; use dioxus::prelude::*;
use futures_util::StreamExt; use std::rc::Rc;
fn main() { fn main() {
launch_desktop(app); 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. // 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 { let handle = use_coroutine(|mut rx: UnboundedReceiver<String>| async move {
use futures_util::StreamExt;
while let Some(message) = rx.next().await { while let Some(message) = rx.next().await {
emails_sent.write().push(message); emails_sent.write().push(message);
} }
@ -22,7 +22,7 @@ fn app() -> Element {
let open_compose_window = move |_evt: MouseEvent| { let open_compose_window = move |_evt: MouseEvent| {
let tx = handle.tx(); let tx = handle.tx();
dioxus::desktop::window().new_window( 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(), 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); let mut user_input = use_signal(String::new);
rsx! { rsx! {
div { div {
h1 { "Compose a new email" } h1 { "Compose a new email" }
button { button {
onclick: move |_| { onclick: move |_| {
send(user_input.cloned()); send(user_input.cloned());
dioxus::desktop::window().close(); dioxus::desktop::window().close();
}, },
"Click to send" "Send"
} }
input { oninput: move |e| user_input.set(e.value()), value: "{user_input}" } input { oninput: move |e| user_input.set(e.value()), value: "{user_input}" }
} }
} }

View file

@ -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" }
}
}
}

View file

@ -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()),
}
}
}

View file

@ -1,4 +1,5 @@
#![allow(non_snake_case)] //! The typical TodoMVC app, implemented in Dioxus.
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_elements::input_data::keyboard_types::Key; use dioxus_elements::input_data::keyboard_types::Key;
use std::collections::HashMap; use std::collections::HashMap;
@ -21,15 +22,21 @@ struct TodoItem {
contents: String, contents: String,
} }
const STYLE: &str = include_str!("./assets/todomvc.css");
fn app() -> Element { 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 mut todos = use_signal(HashMap::<u32, TodoItem>::new);
let filter = use_signal(|| FilterState::All); 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 = let active_todo_count =
use_memo(move || todos.read().values().filter(|item| !item.checked).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 filtered_todos = use_memo(move || {
let mut filtered_todos = todos let mut filtered_todos = todos
.read() .read()
@ -47,6 +54,8 @@ fn app() -> Element {
filtered_todos 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 toggle_all = move |_| {
let check = active_todo_count() != 0; let check = active_todo_count() != 0;
for (_, item) in todos.write().iter_mut() { for (_, item) in todos.write().iter_mut() {
@ -55,8 +64,8 @@ fn app() -> Element {
}; };
rsx! { rsx! {
style { {include_str!("./assets/todomvc.css")} }
section { class: "todoapp", section { class: "todoapp",
style { {STYLE} }
TodoHeader { todos } TodoHeader { todos }
section { class: "main", section { class: "main",
if !todos.read().is_empty() { if !todos.read().is_empty() {
@ -69,17 +78,29 @@ fn app() -> Element {
} }
label { r#for: "toggle-all" } 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", ul { class: "todo-list",
for id in filtered_todos() { for id in filtered_todos() {
TodoEntry { key: "{id}", id, todos } TodoEntry { key: "{id}", id, todos }
} }
} }
// We only show the footer if there are todos.
if !todos.read().is_empty() { if !todos.read().is_empty() {
ListFooter { active_todo_count, todos, filter } 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] #[component]
fn TodoEntry(mut todos: Signal<HashMap<u32, TodoItem>>, id: u32) -> Element { fn TodoEntry(mut todos: Signal<HashMap<u32, TodoItem>>, id: u32) -> Element {
let mut is_editing = use_signal(|| false); 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 checked = use_memo(move || todos.read().get(&id).unwrap().checked);
let contents = use_memo(move || todos.read().get(&id).unwrap().contents.clone()); let contents = use_memo(move || todos.read().get(&id).unwrap().contents.clone());
rsx! { 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", div { class: "view",
input { input {
class: "toggle", class: "toggle",
r#type: "checkbox", r#type: "checkbox",
id: "cbg-{id}", id: "cbg-{id}",
checked: "{checked}", 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 { label {
r#for: "cbg-{id}", r#for: "cbg-{id}",
@ -145,6 +179,8 @@ fn TodoEntry(mut todos: Signal<HashMap<u32, TodoItem>>, id: u32) -> Element {
prevent_default: "onclick" prevent_default: "onclick"
} }
} }
// Only render the actual input if we're editing
if is_editing() { if is_editing() {
input { input {
class: "edit", class: "edit",
@ -170,6 +206,8 @@ fn ListFooter(
active_todo_count: ReadOnlySignal<usize>, active_todo_count: ReadOnlySignal<usize>,
mut filter: Signal<FilterState>, mut filter: Signal<FilterState>,
) -> Element { ) -> 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)); let show_clear_completed = use_memo(move || todos.read().values().any(|todo| todo.checked));
rsx! { 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" }
}
}
}
}

View file

@ -4,9 +4,7 @@ use dioxus::desktop::{use_asset_handler, AssetRequest};
use dioxus::prelude::*; use dioxus::prelude::*;
use http::{header::*, response::Builder as ResponseBuilder, status::StatusCode}; use http::{header::*, response::Builder as ResponseBuilder, status::StatusCode};
use std::{io::SeekFrom, path::PathBuf}; use std::{io::SeekFrom, path::PathBuf};
use tokio::io::AsyncReadExt; use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
use tokio::io::AsyncSeekExt;
use tokio::io::AsyncWriteExt;
const VIDEO_PATH: &str = "./examples/assets/test_video.mp4"; const VIDEO_PATH: &str = "./examples/assets/test_video.mp4";

View file

@ -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::*; use dioxus::prelude::*;
fn main() { fn main() {
@ -6,8 +12,21 @@ fn main() {
fn app() -> Element { fn app() -> Element {
rsx! { rsx! {
web-component { div {
"my-prop": "5%", 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,
} }
} }
} }

View file

@ -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::desktop::{window, Config, WindowBuilder};
use dioxus::prelude::*; use dioxus::prelude::*;
@ -14,29 +25,40 @@ fn main() {
} }
fn app() -> Element { fn app() -> Element {
let mut fullscreen = use_signal(|| false);
let mut always_on_top = use_signal(|| false);
let mut decorations = use_signal(|| false);
rsx!( rsx!(
link { link { href: "https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", rel: "stylesheet" }
href: "https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", Header {}
rel: "stylesheet" 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", 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", a { class: "flex title-font font-medium items-center text-white mb-4 md:mb-0",
span { class: "ml-3 text-xl", "Dioxus" } span { class: "ml-3 text-xl", "Dioxus" }
} }
nav { class: "md:ml-auto flex flex-wrap items-center text-base justify-center" } nav { class: "md:ml-auto flex flex-wrap items-center text-base justify-center" }
// Set the window to minimized
button { 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", 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(), onmousedown: |evt| evt.stop_propagation(),
onclick: move |_| window().set_minimized(true), onclick: move |_| window().set_minimized(true),
"Minimize" "Minimize"
} }
// Toggle fullscreen
button { 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", 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(), onmousedown: |evt| evt.stop_propagation(),
@ -47,6 +69,9 @@ fn app() -> Element {
}, },
"Fullscreen" "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 { 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", 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(), onmousedown: |evt| evt.stop_propagation(),
@ -55,9 +80,14 @@ fn app() -> Element {
} }
} }
} }
br {} }
div { class: "container mx-auto", }
div { class: "grid grid-cols-5",
#[component]
fn SetOnTop() -> Element {
let mut always_on_top = use_signal(|| false);
rsx! {
div { div {
button { button {
class: "inline-flex items-center text-white bg-green-500 border-0 py-1 px-3 hover:bg-green-700 rounded", class: "inline-flex items-center text-white bg-green-500 border-0 py-1 px-3 hover:bg-green-700 rounded",
@ -69,6 +99,14 @@ fn app() -> Element {
"Always On Top" "Always On Top"
} }
} }
}
}
#[component]
fn SetDecorations() -> Element {
let mut decorations = use_signal(|| false);
rsx! {
div { div {
button { button {
class: "inline-flex items-center text-white bg-blue-500 border-0 py-1 px-3 hover:bg-green-700 rounded", class: "inline-flex items-center text-white bg-blue-500 border-0 py-1 px-3 hover:bg-green-700 rounded",
@ -80,6 +118,12 @@ fn app() -> Element {
"Set Decorations" "Set Decorations"
} }
} }
}
}
#[component]
fn SetTitle() -> Element {
rsx! {
div { div {
button { button {
class: "inline-flex items-center text-white bg-blue-500 border-0 py-1 px-3 hover:bg-green-700 rounded", class: "inline-flex items-center text-white bg-blue-500 border-0 py-1 px-3 hover:bg-green-700 rounded",
@ -89,6 +133,4 @@ fn app() -> Element {
} }
} }
} }
}
)
} }

View file

@ -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::Event as WryEvent;
use dioxus::desktop::tao::event::WindowEvent; use dioxus::desktop::tao::event::WindowEvent;
use dioxus::desktop::use_wry_event_handler; use dioxus::desktop::use_wry_event_handler;

View file

@ -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::*; use dioxus::prelude::*;
fn main() { fn main() {
@ -8,6 +12,8 @@ fn app() -> Element {
let mut level = use_signal(|| 1.0); let mut level = use_signal(|| 1.0);
rsx! { rsx! {
h1 { "Zoom level: {level}" }
p { "Change the zoom level of the webview by typing a number in the input below."}
input { input {
r#type: "number", r#type: "number",
value: "{level}", value: "{level}",

View file

@ -37,11 +37,16 @@ impl WebviewInstance {
dom: VirtualDom, dom: VirtualDom,
shared: Rc<SharedContext>, shared: Rc<SharedContext>,
) -> WebviewInstance { ) -> 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 // 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() { if cfg.window.window.window_icon.is_none() {
window.set_window_icon(Some( window = window.with_window_icon(Some(
tao::window::Icon::from_rgba( tao::window::Icon::from_rgba(
include_bytes!("./assets/default_icon.bin").to_vec(), include_bytes!("./assets/default_icon.bin").to_vec(),
460, 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 mut web_context = WebContext::new(cfg.data_dir.clone());
let edit_queue = EditQueue::default(); let edit_queue = EditQueue::default();
let asset_handlers = AssetHandlerRegistry::new(dom.runtime()); let asset_handlers = AssetHandlerRegistry::new(dom.runtime());

View file

@ -90,6 +90,14 @@ impl FormData {
self.inner.value() 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 /// Try to parse the value as a boolean
/// ///
/// Returns false if the value is not a boolean, or if it is false! /// Returns false if the value is not a boolean, or if it is false!

View file

@ -10,7 +10,7 @@ keywords = ["dom", "ui", "gui", "react"]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
[dependencies] [dependencies]
dioxus-desktop = { workspace = true, default-features = false, features = ["tokio_runtime"] } dioxus-desktop = { workspace = true, features = ["tokio_runtime"] }
[lib] [lib]
doctest = false doctest = false

View file

@ -99,6 +99,10 @@ pub struct LinkProps {
/// The onclick event handler. /// The onclick event handler.
pub onclick: Option<EventHandler<MouseEvent>>, pub onclick: Option<EventHandler<MouseEvent>>,
/// The onmounted event handler.
/// Fired when the <a> element is mounted.
pub onmounted: Option<EventHandler<MountedEvent>>,
#[props(default)] #[props(default)]
/// Whether the default behavior should be executed if an `onclick` handler is provided. /// 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! { rsx! {
a { a {
onclick: action, onclick: action,
href, href,
onmounted: onmounted,
prevent_default, prevent_default,
class, class,
rel, rel,