Merge pull request #2879 from iced-rs/beacon
`comet` debugger and `devtools` foundations
This commit is contained in:
commit
97498aaddc
105 changed files with 3462 additions and 3097 deletions
70
Cargo.lock
generated
70
Cargo.lock
generated
|
|
@ -2402,6 +2402,8 @@ version = "0.14.0-dev"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"criterion",
|
"criterion",
|
||||||
"iced_core",
|
"iced_core",
|
||||||
|
"iced_debug",
|
||||||
|
"iced_devtools",
|
||||||
"iced_futures",
|
"iced_futures",
|
||||||
"iced_highlighter",
|
"iced_highlighter",
|
||||||
"iced_renderer",
|
"iced_renderer",
|
||||||
|
|
@ -2413,6 +2415,20 @@ dependencies = [
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iced_beacon"
|
||||||
|
version = "0.14.0-dev"
|
||||||
|
dependencies = [
|
||||||
|
"bincode",
|
||||||
|
"futures",
|
||||||
|
"iced_core",
|
||||||
|
"log",
|
||||||
|
"semver",
|
||||||
|
"serde",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_core"
|
name = "iced_core"
|
||||||
version = "0.14.0-dev"
|
version = "0.14.0-dev"
|
||||||
|
|
@ -2425,11 +2441,30 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"rustc-hash 2.1.1",
|
"rustc-hash 2.1.1",
|
||||||
|
"serde",
|
||||||
"smol_str",
|
"smol_str",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
"web-time",
|
"web-time",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iced_debug"
|
||||||
|
version = "0.14.0-dev"
|
||||||
|
dependencies = [
|
||||||
|
"iced_beacon",
|
||||||
|
"iced_core",
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iced_devtools"
|
||||||
|
version = "0.14.0-dev"
|
||||||
|
dependencies = [
|
||||||
|
"iced_debug",
|
||||||
|
"iced_program",
|
||||||
|
"iced_widget",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_futures"
|
name = "iced_futures"
|
||||||
version = "0.14.0-dev"
|
version = "0.14.0-dev"
|
||||||
|
|
@ -2472,6 +2507,14 @@ dependencies = [
|
||||||
"syntect",
|
"syntect",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iced_program"
|
||||||
|
version = "0.14.0-dev"
|
||||||
|
dependencies = [
|
||||||
|
"iced_graphics",
|
||||||
|
"iced_runtime",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_renderer"
|
name = "iced_renderer"
|
||||||
version = "0.14.0-dev"
|
version = "0.14.0-dev"
|
||||||
|
|
@ -2489,6 +2532,7 @@ version = "0.14.0-dev"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"iced_core",
|
"iced_core",
|
||||||
|
"iced_debug",
|
||||||
"iced_futures",
|
"iced_futures",
|
||||||
"raw-window-handle 0.6.2",
|
"raw-window-handle 0.6.2",
|
||||||
"sipper",
|
"sipper",
|
||||||
|
|
@ -2562,9 +2606,8 @@ dependencies = [
|
||||||
name = "iced_winit"
|
name = "iced_winit"
|
||||||
version = "0.14.0-dev"
|
version = "0.14.0-dev"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"iced_futures",
|
"iced_debug",
|
||||||
"iced_graphics",
|
"iced_program",
|
||||||
"iced_runtime",
|
|
||||||
"log",
|
"log",
|
||||||
"rustc-hash 2.1.1",
|
"rustc-hash 2.1.1",
|
||||||
"sysinfo",
|
"sysinfo",
|
||||||
|
|
@ -4985,6 +5028,15 @@ version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2fdfc24bc566f839a2da4c4295b82db7d25a24253867d5c64355abb5799bdbe"
|
checksum = "c2fdfc24bc566f839a2da4c4295b82db7d25a24253867d5c64355abb5799bdbe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "semver"
|
||||||
|
version = "1.0.26"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.219"
|
version = "1.0.219"
|
||||||
|
|
@ -5768,9 +5820,21 @@ dependencies = [
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
"socket2",
|
"socket2",
|
||||||
|
"tokio-macros",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-macros"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-native-tls"
|
name = "tokio-native-tls"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
|
|
||||||
17
Cargo.toml
17
Cargo.toml
|
|
@ -42,7 +42,7 @@ markdown = ["iced_widget/markdown"]
|
||||||
# Enables lazy widgets
|
# Enables lazy widgets
|
||||||
lazy = ["iced_widget/lazy"]
|
lazy = ["iced_widget/lazy"]
|
||||||
# Enables a debug view in native platforms (press F12)
|
# Enables a debug view in native platforms (press F12)
|
||||||
debug = ["iced_winit/debug"]
|
debug = ["iced_winit/debug", "iced_devtools"]
|
||||||
# Enables the `thread-pool` futures executor as the `executor::Default` on native platforms
|
# Enables the `thread-pool` futures executor as the `executor::Default` on native platforms
|
||||||
thread-pool = ["iced_futures/thread-pool"]
|
thread-pool = ["iced_futures/thread-pool"]
|
||||||
# Enables `tokio` as the `executor::Default` on native platforms
|
# Enables `tokio` as the `executor::Default` on native platforms
|
||||||
|
|
@ -71,6 +71,7 @@ unconditional-rendering = ["iced_winit/unconditional-rendering"]
|
||||||
sipper = ["iced_runtime/sipper"]
|
sipper = ["iced_runtime/sipper"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
iced_debug.workspace = true
|
||||||
iced_core.workspace = true
|
iced_core.workspace = true
|
||||||
iced_futures.workspace = true
|
iced_futures.workspace = true
|
||||||
iced_renderer.workspace = true
|
iced_renderer.workspace = true
|
||||||
|
|
@ -79,6 +80,9 @@ iced_widget.workspace = true
|
||||||
iced_winit.features = ["program"]
|
iced_winit.features = ["program"]
|
||||||
iced_winit.workspace = true
|
iced_winit.workspace = true
|
||||||
|
|
||||||
|
iced_devtools.workspace = true
|
||||||
|
iced_devtools.optional = true
|
||||||
|
|
||||||
iced_highlighter.workspace = true
|
iced_highlighter.workspace = true
|
||||||
iced_highlighter.optional = true
|
iced_highlighter.optional = true
|
||||||
|
|
||||||
|
|
@ -108,10 +112,14 @@ strip = "debuginfo"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
|
"beacon",
|
||||||
"core",
|
"core",
|
||||||
|
"debug",
|
||||||
|
"devtools",
|
||||||
"futures",
|
"futures",
|
||||||
"graphics",
|
"graphics",
|
||||||
"highlighter",
|
"highlighter",
|
||||||
|
"program",
|
||||||
"renderer",
|
"renderer",
|
||||||
"runtime",
|
"runtime",
|
||||||
"test",
|
"test",
|
||||||
|
|
@ -135,10 +143,14 @@ rust-version = "1.85"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
iced = { version = "0.14.0-dev", path = "." }
|
iced = { version = "0.14.0-dev", path = "." }
|
||||||
|
iced_beacon = { version = "0.14.0-dev", path = "beacon" }
|
||||||
iced_core = { version = "0.14.0-dev", path = "core" }
|
iced_core = { version = "0.14.0-dev", path = "core" }
|
||||||
|
iced_debug = { version = "0.14.0-dev", path = "debug" }
|
||||||
|
iced_devtools = { version = "0.14.0-dev", path = "devtools" }
|
||||||
iced_futures = { version = "0.14.0-dev", path = "futures" }
|
iced_futures = { version = "0.14.0-dev", path = "futures" }
|
||||||
iced_graphics = { version = "0.14.0-dev", path = "graphics" }
|
iced_graphics = { version = "0.14.0-dev", path = "graphics" }
|
||||||
iced_highlighter = { version = "0.14.0-dev", path = "highlighter" }
|
iced_highlighter = { version = "0.14.0-dev", path = "highlighter" }
|
||||||
|
iced_program = { version = "0.14.0-dev", path = "program" }
|
||||||
iced_renderer = { version = "0.14.0-dev", path = "renderer" }
|
iced_renderer = { version = "0.14.0-dev", path = "renderer" }
|
||||||
iced_runtime = { version = "0.14.0-dev", path = "runtime" }
|
iced_runtime = { version = "0.14.0-dev", path = "runtime" }
|
||||||
iced_test = { version = "0.14.0-dev", path = "test" }
|
iced_test = { version = "0.14.0-dev", path = "test" }
|
||||||
|
|
@ -147,6 +159,7 @@ iced_wgpu = { version = "0.14.0-dev", path = "wgpu" }
|
||||||
iced_widget = { version = "0.14.0-dev", path = "widget" }
|
iced_widget = { version = "0.14.0-dev", path = "widget" }
|
||||||
iced_winit = { version = "0.14.0-dev", path = "winit" }
|
iced_winit = { version = "0.14.0-dev", path = "winit" }
|
||||||
|
|
||||||
|
bincode = "1.3"
|
||||||
bitflags = "2.0"
|
bitflags = "2.0"
|
||||||
bytemuck = { version = "1.0", features = ["derive"] }
|
bytemuck = { version = "1.0", features = ["derive"] }
|
||||||
bytes = "1.6"
|
bytes = "1.6"
|
||||||
|
|
@ -172,6 +185,8 @@ qrcode = { version = "0.13", default-features = false }
|
||||||
raw-window-handle = "0.6"
|
raw-window-handle = "0.6"
|
||||||
resvg = "0.42"
|
resvg = "0.42"
|
||||||
rustc-hash = "2.0"
|
rustc-hash = "2.0"
|
||||||
|
serde = "1.0"
|
||||||
|
semver = "1.0"
|
||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
sipper = "0.1"
|
sipper = "0.1"
|
||||||
smol = "2"
|
smol = "2"
|
||||||
|
|
|
||||||
29
beacon/Cargo.toml
Normal file
29
beacon/Cargo.toml
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
[package]
|
||||||
|
name = "iced_beacon"
|
||||||
|
description = "A client/server protocol to monitor and supervise iced applications"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
homepage.workspace = true
|
||||||
|
categories.workspace = true
|
||||||
|
keywords.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
iced_core.workspace = true
|
||||||
|
iced_core.features = ["serde"]
|
||||||
|
|
||||||
|
bincode.workspace = true
|
||||||
|
futures.workspace = true
|
||||||
|
log.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
|
||||||
|
tokio.workspace = true
|
||||||
|
tokio.features = ["rt", "rt-multi-thread", "net", "sync", "time", "io-util", "macros"]
|
||||||
|
|
||||||
|
serde.workspace = true
|
||||||
|
serde.features = ["derive"]
|
||||||
|
|
||||||
|
semver.workspace = true
|
||||||
|
semver.features = ["serde"]
|
||||||
156
beacon/src/client.rs
Normal file
156
beacon/src/client.rs
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
use crate::core::time::{Duration, SystemTime};
|
||||||
|
use crate::span;
|
||||||
|
use crate::theme;
|
||||||
|
|
||||||
|
use semver::Version;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::io::{self, AsyncWriteExt};
|
||||||
|
use tokio::net;
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
use tokio::time;
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::{self, AtomicBool};
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
pub const SERVER_ADDRESS: &str = "127.0.0.1:9167";
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Client {
|
||||||
|
sender: mpsc::Sender<Message>,
|
||||||
|
is_connected: Arc<AtomicBool>,
|
||||||
|
_handle: Arc<thread::JoinHandle<()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum Message {
|
||||||
|
Connected {
|
||||||
|
at: SystemTime,
|
||||||
|
name: String,
|
||||||
|
version: Version,
|
||||||
|
},
|
||||||
|
EventLogged {
|
||||||
|
at: SystemTime,
|
||||||
|
event: Event,
|
||||||
|
},
|
||||||
|
Quit {
|
||||||
|
at: SystemTime,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum Event {
|
||||||
|
ThemeChanged(theme::Palette),
|
||||||
|
SpanStarted(span::Stage),
|
||||||
|
SpanFinished(span::Stage, Duration),
|
||||||
|
MessageLogged(String),
|
||||||
|
CommandsSpawned(usize),
|
||||||
|
SubscriptionsTracked(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Client {
|
||||||
|
pub fn log(&self, event: Event) {
|
||||||
|
let _ = self.sender.try_send(Message::EventLogged {
|
||||||
|
at: SystemTime::now(),
|
||||||
|
event,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_connected(&self) -> bool {
|
||||||
|
self.is_connected.load(atomic::Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn quit(&self) {
|
||||||
|
let _ = self.sender.try_send(Message::Quit {
|
||||||
|
at: SystemTime::now(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn connect(name: String) -> Client {
|
||||||
|
let (sender, receiver) = mpsc::channel(100);
|
||||||
|
let is_connected = Arc::new(AtomicBool::new(false));
|
||||||
|
|
||||||
|
let handle = {
|
||||||
|
let is_connected = is_connected.clone();
|
||||||
|
|
||||||
|
std::thread::spawn(move || run(name, is_connected.clone(), receiver))
|
||||||
|
};
|
||||||
|
|
||||||
|
Client {
|
||||||
|
sender,
|
||||||
|
is_connected,
|
||||||
|
_handle: Arc::new(handle),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn run(
|
||||||
|
name: String,
|
||||||
|
is_connected: Arc<AtomicBool>,
|
||||||
|
mut receiver: mpsc::Receiver<Message>,
|
||||||
|
) {
|
||||||
|
let version = semver::Version::parse(env!("CARGO_PKG_VERSION"))
|
||||||
|
.expect("Parse package version");
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match _connect().await {
|
||||||
|
Ok(mut stream) => {
|
||||||
|
is_connected.store(true, atomic::Ordering::Relaxed);
|
||||||
|
|
||||||
|
let _ = send(
|
||||||
|
&mut stream,
|
||||||
|
Message::Connected {
|
||||||
|
at: SystemTime::now(),
|
||||||
|
name: name.clone(),
|
||||||
|
version: version.clone(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
while let Some(output) = receiver.recv().await {
|
||||||
|
match send(&mut stream, output).await {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(error) => {
|
||||||
|
if error.kind() != io::ErrorKind::BrokenPipe {
|
||||||
|
log::warn!(
|
||||||
|
"Error sending message to server: {error}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
is_connected.store(false, atomic::Ordering::Relaxed);
|
||||||
|
time::sleep(time::Duration::from_secs(2)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn _connect() -> Result<net::TcpStream, io::Error> {
|
||||||
|
log::debug!("Attempting to connect to server...");
|
||||||
|
let stream = net::TcpStream::connect(SERVER_ADDRESS).await?;
|
||||||
|
|
||||||
|
stream.set_nodelay(true)?;
|
||||||
|
stream.writable().await?;
|
||||||
|
|
||||||
|
Ok(stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send(
|
||||||
|
stream: &mut net::TcpStream,
|
||||||
|
message: Message,
|
||||||
|
) -> Result<(), io::Error> {
|
||||||
|
let bytes = bincode::serialize(&message).expect("Encode input message");
|
||||||
|
let size = bytes.len() as u64;
|
||||||
|
|
||||||
|
stream.write_all(&size.to_be_bytes()).await?;
|
||||||
|
stream.write_all(&bytes).await?;
|
||||||
|
stream.flush().await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
244
beacon/src/lib.rs
Normal file
244
beacon/src/lib.rs
Normal file
|
|
@ -0,0 +1,244 @@
|
||||||
|
pub use iced_core as core;
|
||||||
|
pub use semver::Version;
|
||||||
|
|
||||||
|
pub mod client;
|
||||||
|
pub mod span;
|
||||||
|
|
||||||
|
mod stream;
|
||||||
|
|
||||||
|
pub use client::Client;
|
||||||
|
pub use span::Span;
|
||||||
|
|
||||||
|
use crate::core::theme;
|
||||||
|
use crate::core::time::{Duration, SystemTime};
|
||||||
|
|
||||||
|
use futures::{SinkExt, Stream};
|
||||||
|
use tokio::io::{self, AsyncReadExt};
|
||||||
|
use tokio::net;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Event {
|
||||||
|
Connected {
|
||||||
|
at: SystemTime,
|
||||||
|
name: String,
|
||||||
|
version: Version,
|
||||||
|
},
|
||||||
|
Disconnected {
|
||||||
|
at: SystemTime,
|
||||||
|
},
|
||||||
|
ThemeChanged {
|
||||||
|
at: SystemTime,
|
||||||
|
palette: theme::Palette,
|
||||||
|
},
|
||||||
|
SubscriptionsTracked {
|
||||||
|
at: SystemTime,
|
||||||
|
amount_alive: usize,
|
||||||
|
},
|
||||||
|
SpanFinished {
|
||||||
|
at: SystemTime,
|
||||||
|
duration: Duration,
|
||||||
|
span: Span,
|
||||||
|
},
|
||||||
|
QuitRequested {
|
||||||
|
at: SystemTime,
|
||||||
|
},
|
||||||
|
AlreadyRunning {
|
||||||
|
at: SystemTime,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Event {
|
||||||
|
pub fn at(&self) -> SystemTime {
|
||||||
|
match self {
|
||||||
|
Self::Connected { at, .. }
|
||||||
|
| Self::Disconnected { at, .. }
|
||||||
|
| Self::ThemeChanged { at, .. }
|
||||||
|
| Self::SubscriptionsTracked { at, .. }
|
||||||
|
| Self::SpanFinished { at, .. }
|
||||||
|
| Self::QuitRequested { at }
|
||||||
|
| Self::AlreadyRunning { at } => *at,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_running() -> bool {
|
||||||
|
std::net::TcpListener::bind(client::SERVER_ADDRESS).is_err()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run() -> impl Stream<Item = Event> {
|
||||||
|
stream::channel(|mut output| async move {
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
|
||||||
|
let server = loop {
|
||||||
|
match net::TcpListener::bind(client::SERVER_ADDRESS).await {
|
||||||
|
Ok(server) => break server,
|
||||||
|
Err(error) => {
|
||||||
|
if error.kind() == io::ErrorKind::AddrInUse {
|
||||||
|
let _ = output
|
||||||
|
.send(Event::AlreadyRunning {
|
||||||
|
at: SystemTime::now(),
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
delay().await;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let Ok((mut stream, _)) = server.accept().await else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = stream.set_nodelay(true);
|
||||||
|
|
||||||
|
let mut last_message = String::new();
|
||||||
|
let mut last_commands_spawned = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match receive(&mut stream, &mut buffer).await {
|
||||||
|
Ok(message) => {
|
||||||
|
match message {
|
||||||
|
client::Message::Connected {
|
||||||
|
at,
|
||||||
|
name,
|
||||||
|
version,
|
||||||
|
} => {
|
||||||
|
let _ = output
|
||||||
|
.send(Event::Connected {
|
||||||
|
at,
|
||||||
|
name,
|
||||||
|
version,
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
client::Message::EventLogged { at, event } => {
|
||||||
|
match event {
|
||||||
|
client::Event::ThemeChanged(palette) => {
|
||||||
|
let _ = output
|
||||||
|
.send(Event::ThemeChanged {
|
||||||
|
at,
|
||||||
|
palette,
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
client::Event::SubscriptionsTracked(
|
||||||
|
amount_alive,
|
||||||
|
) => {
|
||||||
|
let _ = output
|
||||||
|
.send(Event::SubscriptionsTracked {
|
||||||
|
at,
|
||||||
|
amount_alive,
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
client::Event::MessageLogged(message) => {
|
||||||
|
last_message = message;
|
||||||
|
}
|
||||||
|
client::Event::CommandsSpawned(
|
||||||
|
commands,
|
||||||
|
) => {
|
||||||
|
last_commands_spawned = commands;
|
||||||
|
}
|
||||||
|
client::Event::SpanStarted(
|
||||||
|
span::Stage::Update,
|
||||||
|
) => {
|
||||||
|
last_message.clear();
|
||||||
|
last_commands_spawned = 0;
|
||||||
|
}
|
||||||
|
client::Event::SpanStarted(_) => {}
|
||||||
|
client::Event::SpanFinished(
|
||||||
|
stage,
|
||||||
|
duration,
|
||||||
|
) => {
|
||||||
|
let span = match stage {
|
||||||
|
span::Stage::Boot => Span::Boot,
|
||||||
|
span::Stage::Update => {
|
||||||
|
Span::Update {
|
||||||
|
message: last_message
|
||||||
|
.clone(),
|
||||||
|
commands_spawned:
|
||||||
|
last_commands_spawned,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
span::Stage::View(window) => {
|
||||||
|
Span::View { window }
|
||||||
|
}
|
||||||
|
span::Stage::Layout(window) => {
|
||||||
|
Span::Layout { window }
|
||||||
|
}
|
||||||
|
span::Stage::Interact(window) => {
|
||||||
|
Span::Interact { window }
|
||||||
|
}
|
||||||
|
span::Stage::Draw(window) => {
|
||||||
|
Span::Draw { window }
|
||||||
|
}
|
||||||
|
span::Stage::Present(window) => {
|
||||||
|
Span::Present { window }
|
||||||
|
}
|
||||||
|
span::Stage::Custom(
|
||||||
|
window,
|
||||||
|
name,
|
||||||
|
) => Span::Custom { window, name },
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = output
|
||||||
|
.send(Event::SpanFinished {
|
||||||
|
at,
|
||||||
|
duration,
|
||||||
|
span,
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client::Message::Quit { at } => {
|
||||||
|
let _ = output
|
||||||
|
.send(Event::QuitRequested { at })
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Err(Error::IOFailed(_)) => {
|
||||||
|
let _ = output
|
||||||
|
.send(Event::Disconnected {
|
||||||
|
at: SystemTime::now(),
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Err(Error::DecodingFailed(error)) => {
|
||||||
|
log::warn!("Error decoding beacon output: {error}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn receive(
|
||||||
|
stream: &mut net::TcpStream,
|
||||||
|
buffer: &mut Vec<u8>,
|
||||||
|
) -> Result<client::Message, Error> {
|
||||||
|
let size = stream.read_u64().await? as usize;
|
||||||
|
|
||||||
|
if buffer.len() < size {
|
||||||
|
buffer.resize(size, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let _n = stream.read_exact(&mut buffer[..size]).await?;
|
||||||
|
|
||||||
|
Ok(bincode::deserialize(buffer)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delay() {
|
||||||
|
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
enum Error {
|
||||||
|
#[error("input/output operation failed: {0}")]
|
||||||
|
IOFailed(#[from] io::Error),
|
||||||
|
#[error("decoding failed: {0}")]
|
||||||
|
DecodingFailed(#[from] Box<bincode::ErrorKind>),
|
||||||
|
}
|
||||||
77
beacon/src/span.rs
Normal file
77
beacon/src/span.rs
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
use crate::core::window;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub enum Span {
|
||||||
|
Boot,
|
||||||
|
Update {
|
||||||
|
message: String,
|
||||||
|
commands_spawned: usize,
|
||||||
|
},
|
||||||
|
View {
|
||||||
|
window: window::Id,
|
||||||
|
},
|
||||||
|
Layout {
|
||||||
|
window: window::Id,
|
||||||
|
},
|
||||||
|
Interact {
|
||||||
|
window: window::Id,
|
||||||
|
},
|
||||||
|
Draw {
|
||||||
|
window: window::Id,
|
||||||
|
},
|
||||||
|
Present {
|
||||||
|
window: window::Id,
|
||||||
|
},
|
||||||
|
Custom {
|
||||||
|
window: window::Id,
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Span {
|
||||||
|
pub fn stage(&self) -> Stage {
|
||||||
|
match self {
|
||||||
|
Span::Boot => Stage::Boot,
|
||||||
|
Span::Update { .. } => Stage::Update,
|
||||||
|
Span::View { window } => Stage::View(*window),
|
||||||
|
Span::Layout { window } => Stage::Layout(*window),
|
||||||
|
Span::Interact { window } => Stage::Interact(*window),
|
||||||
|
Span::Draw { window } => Stage::Draw(*window),
|
||||||
|
Span::Present { window } => Stage::Present(*window),
|
||||||
|
Span::Custom { window, name } => {
|
||||||
|
Stage::Custom(*window, name.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
|
||||||
|
)]
|
||||||
|
pub enum Stage {
|
||||||
|
Boot,
|
||||||
|
Update,
|
||||||
|
View(window::Id),
|
||||||
|
Layout(window::Id),
|
||||||
|
Interact(window::Id),
|
||||||
|
Draw(window::Id),
|
||||||
|
Present(window::Id),
|
||||||
|
Custom(window::Id, String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Stage {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(match self {
|
||||||
|
Stage::Boot => "Boot",
|
||||||
|
Stage::Update => "Update",
|
||||||
|
Stage::View(_) => "View",
|
||||||
|
Stage::Layout(_) => "Layout",
|
||||||
|
Stage::Interact(_) => "Interact",
|
||||||
|
Stage::Draw(_) => "Draw",
|
||||||
|
Stage::Present(_) => "Present",
|
||||||
|
Stage::Custom(_, name) => name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
15
beacon/src/stream.rs
Normal file
15
beacon/src/stream.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
use futures::Future;
|
||||||
|
use futures::channel::mpsc;
|
||||||
|
use futures::stream::{self, Stream, StreamExt};
|
||||||
|
|
||||||
|
pub fn channel<T, F>(f: impl Fn(mpsc::Sender<T>) -> F) -> impl Stream<Item = T>
|
||||||
|
where
|
||||||
|
F: Future<Output = ()>,
|
||||||
|
{
|
||||||
|
let (sender, receiver) = mpsc::channel(1);
|
||||||
|
|
||||||
|
stream::select(
|
||||||
|
receiver,
|
||||||
|
stream::once(f(sender)).filter_map(|_| async { None }),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -133,12 +133,11 @@ fn benchmark<'a>(
|
||||||
|
|
||||||
cache = Some(user_interface.into_cache());
|
cache = Some(user_interface.into_cache());
|
||||||
|
|
||||||
let submission = renderer.present::<&str>(
|
let submission = renderer.present(
|
||||||
Some(Color::BLACK),
|
Some(Color::BLACK),
|
||||||
format,
|
format,
|
||||||
&texture_view,
|
&texture_view,
|
||||||
&viewport,
|
&viewport,
|
||||||
&[],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let _ = device.poll(wgpu::Maintain::WaitForSubmissionIndex(submission));
|
let _ = device.poll(wgpu::Maintain::WaitForSubmissionIndex(submission));
|
||||||
|
|
|
||||||
|
|
@ -31,3 +31,7 @@ web-time.workspace = true
|
||||||
|
|
||||||
dark-light.workspace = true
|
dark-light.workspace = true
|
||||||
dark-light.optional = true
|
dark-light.optional = true
|
||||||
|
|
||||||
|
serde.workspace = true
|
||||||
|
serde.optional = true
|
||||||
|
serde.features = ["derive"]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
/// A color in the `sRGB` color space.
|
/// A color in the `sRGB` color space.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct Color {
|
pub struct Color {
|
||||||
/// Red component, 0.0 - 1.0
|
/// Red component, 0.0 - 1.0
|
||||||
pub r: f32,
|
pub r: f32,
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ pub struct Settings {
|
||||||
/// Enabling it can produce a smoother result in some widgets, like the
|
/// Enabling it can produce a smoother result in some widgets, like the
|
||||||
/// `canvas` widget, at a performance cost.
|
/// `canvas` widget, at a performance cost.
|
||||||
///
|
///
|
||||||
/// By default, it is disabled.
|
/// By default, it is enabled.
|
||||||
pub antialiasing: bool,
|
pub antialiasing: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,7 +42,7 @@ impl Default for Settings {
|
||||||
fonts: Vec::new(),
|
fonts: Vec::new(),
|
||||||
default_font: Font::default(),
|
default_font: Font::default(),
|
||||||
default_text_size: Pixels(16.0),
|
default_text_size: Pixels(16.0),
|
||||||
antialiasing: false,
|
antialiasing: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -252,7 +252,7 @@ impl fmt::Display for Custom {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The base style of a [`Theme`].
|
/// The base style of a theme.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub struct Style {
|
pub struct Style {
|
||||||
/// The background [`Color`] of the application.
|
/// The background [`Color`] of the application.
|
||||||
|
|
@ -262,16 +262,27 @@ pub struct Style {
|
||||||
pub text_color: Color,
|
pub text_color: Color,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The default blank style of a [`Theme`].
|
/// The default blank style of a theme.
|
||||||
pub trait Base {
|
pub trait Base {
|
||||||
/// Returns the default base [`Style`] of a [`Theme`].
|
/// Returns the default base [`Style`] of a theme.
|
||||||
fn base(&self) -> Style;
|
fn base(&self) -> Style;
|
||||||
|
|
||||||
|
/// Returns the color [`Palette`] of the theme.
|
||||||
|
///
|
||||||
|
/// This [`Palette`] may be used by the runtime for
|
||||||
|
/// debugging purposes; like displaying performance
|
||||||
|
/// metrics or devtools.
|
||||||
|
fn palette(&self) -> Option<Palette>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Base for Theme {
|
impl Base for Theme {
|
||||||
fn base(&self) -> Style {
|
fn base(&self) -> Style {
|
||||||
default(self)
|
default(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn palette(&self) -> Option<Palette> {
|
||||||
|
Some(self.palette())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The default [`Style`] of a built-in [`Theme`].
|
/// The default [`Style`] of a built-in [`Theme`].
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use std::sync::LazyLock;
|
||||||
|
|
||||||
/// A color palette.
|
/// A color palette.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct Palette {
|
pub struct Palette {
|
||||||
/// The background [`Color`] of the [`Palette`].
|
/// The background [`Color`] of the [`Palette`].
|
||||||
pub background: Color,
|
pub background: Color,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
pub use web_time::Duration;
|
pub use web_time::Duration;
|
||||||
pub use web_time::Instant;
|
pub use web_time::Instant;
|
||||||
|
pub use web_time::SystemTime;
|
||||||
|
|
||||||
/// Creates a [`Duration`] representing the given amount of milliseconds.
|
/// Creates a [`Duration`] representing the given amount of milliseconds.
|
||||||
pub fn milliseconds(milliseconds: u64) -> Duration {
|
pub fn milliseconds(milliseconds: u64) -> Duration {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ use std::sync::atomic::{self, AtomicU64};
|
||||||
|
|
||||||
/// The id of the window.
|
/// The id of the window.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct Id(u64);
|
pub struct Id(u64);
|
||||||
|
|
||||||
static COUNT: AtomicU64 = AtomicU64::new(1);
|
static COUNT: AtomicU64 = AtomicU64::new(1);
|
||||||
|
|
|
||||||
22
debug/Cargo.toml
Normal file
22
debug/Cargo.toml
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
[package]
|
||||||
|
name = "iced_debug"
|
||||||
|
description = "A pluggable API for debugging iced applications"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
homepage.workspace = true
|
||||||
|
categories.workspace = true
|
||||||
|
keywords.workspace = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
enable = ["dep:iced_beacon"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
iced_core.workspace = true
|
||||||
|
log.workspace = true
|
||||||
|
|
||||||
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
|
iced_beacon.workspace = true
|
||||||
|
iced_beacon.optional = true
|
||||||
282
debug/src/lib.rs
Normal file
282
debug/src/lib.rs
Normal file
|
|
@ -0,0 +1,282 @@
|
||||||
|
pub use iced_core as core;
|
||||||
|
|
||||||
|
use crate::core::theme;
|
||||||
|
use crate::core::window;
|
||||||
|
|
||||||
|
pub use internal::Span;
|
||||||
|
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
pub fn init(name: &str) {
|
||||||
|
internal::init(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggle_comet() -> Result<(), io::Error> {
|
||||||
|
internal::toggle_comet()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn theme_changed(f: impl FnOnce() -> Option<theme::Palette>) {
|
||||||
|
internal::theme_changed(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tasks_spawned(amount: usize) {
|
||||||
|
internal::tasks_spawned(amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn subscriptions_tracked(amount: usize) {
|
||||||
|
internal::subscriptions_tracked(amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn boot() -> Span {
|
||||||
|
internal::boot()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(message: &impl std::fmt::Debug) -> Span {
|
||||||
|
internal::update(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view(window: window::Id) -> Span {
|
||||||
|
internal::view(window)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn layout(window: window::Id) -> Span {
|
||||||
|
internal::layout(window)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interact(window: window::Id) -> Span {
|
||||||
|
internal::interact(window)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(window: window::Id) -> Span {
|
||||||
|
internal::draw(window)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn present(window: window::Id) -> Span {
|
||||||
|
internal::present(window)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn time(window: window::Id, name: impl AsRef<str>) -> Span {
|
||||||
|
internal::time(window, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn skip_next_timing() {
|
||||||
|
internal::skip_next_timing();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "enable", not(target_arch = "wasm32")))]
|
||||||
|
mod internal {
|
||||||
|
use crate::core::theme;
|
||||||
|
use crate::core::time::Instant;
|
||||||
|
use crate::core::window;
|
||||||
|
|
||||||
|
use iced_beacon as beacon;
|
||||||
|
|
||||||
|
use beacon::client::{self, Client};
|
||||||
|
use beacon::span;
|
||||||
|
|
||||||
|
use std::io;
|
||||||
|
use std::process;
|
||||||
|
use std::sync::atomic::{self, AtomicBool};
|
||||||
|
use std::sync::{LazyLock, RwLock};
|
||||||
|
|
||||||
|
pub fn init(name: &str) {
|
||||||
|
let name = name.split("::").next().unwrap_or(name);
|
||||||
|
|
||||||
|
name.clone_into(&mut NAME.write().expect("Write application name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggle_comet() -> Result<(), io::Error> {
|
||||||
|
if BEACON.is_connected() {
|
||||||
|
BEACON.quit();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
let _ = process::Command::new("iced_comet")
|
||||||
|
.stdin(process::Stdio::null())
|
||||||
|
.stdout(process::Stdio::null())
|
||||||
|
.stderr(process::Stdio::null())
|
||||||
|
.spawn()?;
|
||||||
|
|
||||||
|
if let Some(palette) =
|
||||||
|
LAST_PALETTE.read().expect("Read last palette").as_ref()
|
||||||
|
{
|
||||||
|
BEACON.log(client::Event::ThemeChanged(*palette));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn theme_changed(f: impl FnOnce() -> Option<theme::Palette>) {
|
||||||
|
let Some(palette) = f() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if LAST_PALETTE.read().expect("Read last palette").as_ref()
|
||||||
|
!= Some(&palette)
|
||||||
|
{
|
||||||
|
BEACON.log(client::Event::ThemeChanged(palette));
|
||||||
|
|
||||||
|
*LAST_PALETTE.write().expect("Write last palette") = Some(palette);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tasks_spawned(amount: usize) {
|
||||||
|
BEACON.log(client::Event::CommandsSpawned(amount));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn subscriptions_tracked(amount: usize) {
|
||||||
|
BEACON.log(client::Event::SubscriptionsTracked(amount));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn boot() -> Span {
|
||||||
|
span(span::Stage::Boot)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(message: &impl std::fmt::Debug) -> Span {
|
||||||
|
let span = span(span::Stage::Update);
|
||||||
|
|
||||||
|
let start = Instant::now();
|
||||||
|
let message = format!("{message:?}");
|
||||||
|
let elapsed = start.elapsed();
|
||||||
|
|
||||||
|
if elapsed.as_millis() >= 1 {
|
||||||
|
log::warn!(
|
||||||
|
"Slow `Debug` implementation of `Message` (took {elapsed:?})!"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
BEACON.log(client::Event::MessageLogged(if message.len() > 49 {
|
||||||
|
format!("{}...", &message[..49])
|
||||||
|
} else {
|
||||||
|
message
|
||||||
|
}));
|
||||||
|
|
||||||
|
span
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view(window: window::Id) -> Span {
|
||||||
|
span(span::Stage::View(window))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn layout(window: window::Id) -> Span {
|
||||||
|
span(span::Stage::Layout(window))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interact(window: window::Id) -> Span {
|
||||||
|
span(span::Stage::Interact(window))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(window: window::Id) -> Span {
|
||||||
|
span(span::Stage::Draw(window))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn present(window: window::Id) -> Span {
|
||||||
|
span(span::Stage::Present(window))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn time(window: window::Id, name: impl AsRef<str>) -> Span {
|
||||||
|
span(span::Stage::Custom(window, name.as_ref().to_owned()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn skip_next_timing() {
|
||||||
|
SKIP_NEXT_SPAN.store(true, atomic::Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn span(span: span::Stage) -> Span {
|
||||||
|
BEACON.log(client::Event::SpanStarted(span.clone()));
|
||||||
|
|
||||||
|
Span {
|
||||||
|
span,
|
||||||
|
start: Instant::now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Span {
|
||||||
|
span: span::Stage,
|
||||||
|
start: Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Span {
|
||||||
|
pub fn finish(self) {
|
||||||
|
if SKIP_NEXT_SPAN.fetch_and(false, atomic::Ordering::Relaxed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BEACON.log(client::Event::SpanFinished(
|
||||||
|
self.span,
|
||||||
|
self.start.elapsed(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static BEACON: LazyLock<Client> = LazyLock::new(|| {
|
||||||
|
client::connect(NAME.read().expect("Read application name").to_owned())
|
||||||
|
});
|
||||||
|
|
||||||
|
static NAME: RwLock<String> = RwLock::new(String::new());
|
||||||
|
static LAST_PALETTE: RwLock<Option<theme::Palette>> = RwLock::new(None);
|
||||||
|
static SKIP_NEXT_SPAN: AtomicBool = AtomicBool::new(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(not(feature = "enable"), target_arch = "wasm32"))]
|
||||||
|
mod internal {
|
||||||
|
use crate::core::theme;
|
||||||
|
use crate::core::window;
|
||||||
|
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
pub fn init(_name: &str) {}
|
||||||
|
|
||||||
|
pub fn toggle_comet() -> Result<(), io::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn theme_changed(_f: impl FnOnce() -> Option<theme::Palette>) {}
|
||||||
|
|
||||||
|
pub fn tasks_spawned(_amount: usize) {}
|
||||||
|
|
||||||
|
pub fn subscriptions_tracked(_amount: usize) {}
|
||||||
|
|
||||||
|
pub fn boot() -> Span {
|
||||||
|
Span
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(_message: &impl std::fmt::Debug) -> Span {
|
||||||
|
Span
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view(_window: window::Id) -> Span {
|
||||||
|
Span
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn layout(_window: window::Id) -> Span {
|
||||||
|
Span
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interact(_window: window::Id) -> Span {
|
||||||
|
Span
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(_window: window::Id) -> Span {
|
||||||
|
Span
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn present(_window: window::Id) -> Span {
|
||||||
|
Span
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn time(_window: window::Id, _name: impl AsRef<str>) -> Span {
|
||||||
|
Span
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn skip_next_timing() {}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Span;
|
||||||
|
|
||||||
|
impl Span {
|
||||||
|
pub fn finish(self) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
devtools/Cargo.toml
Normal file
19
devtools/Cargo.toml
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
[package]
|
||||||
|
name = "iced_devtools"
|
||||||
|
version.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
homepage.workspace = true
|
||||||
|
categories.workspace = true
|
||||||
|
keywords.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
iced_program.workspace = true
|
||||||
|
iced_widget.workspace = true
|
||||||
|
iced_debug.workspace = true
|
||||||
19
devtools/src/executor.rs
Normal file
19
devtools/src/executor.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
use crate::futures::futures::channel::mpsc;
|
||||||
|
use crate::runtime::Task;
|
||||||
|
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
pub fn spawn_blocking<T>(
|
||||||
|
f: impl FnOnce(mpsc::Sender<T>) + Send + 'static,
|
||||||
|
) -> Task<T>
|
||||||
|
where
|
||||||
|
T: Send + 'static,
|
||||||
|
{
|
||||||
|
let (sender, receiver) = mpsc::channel(1);
|
||||||
|
|
||||||
|
let _ = thread::spawn(move || {
|
||||||
|
f(sender);
|
||||||
|
});
|
||||||
|
|
||||||
|
Task::stream(receiver)
|
||||||
|
}
|
||||||
418
devtools/src/lib.rs
Normal file
418
devtools/src/lib.rs
Normal file
|
|
@ -0,0 +1,418 @@
|
||||||
|
#![allow(missing_docs)]
|
||||||
|
use iced_debug as debug;
|
||||||
|
use iced_program as program;
|
||||||
|
use iced_widget as widget;
|
||||||
|
use iced_widget::core;
|
||||||
|
use iced_widget::runtime;
|
||||||
|
use iced_widget::runtime::futures;
|
||||||
|
|
||||||
|
mod executor;
|
||||||
|
|
||||||
|
use crate::core::keyboard;
|
||||||
|
use crate::core::theme::{self, Base, Theme};
|
||||||
|
use crate::core::time::seconds;
|
||||||
|
use crate::core::window;
|
||||||
|
use crate::core::{Color, Element, Length::Fill};
|
||||||
|
use crate::futures::Subscription;
|
||||||
|
use crate::program::Program;
|
||||||
|
use crate::runtime::Task;
|
||||||
|
use crate::widget::{
|
||||||
|
bottom_right, button, center, column, container, horizontal_space, row,
|
||||||
|
scrollable, stack, text, themer,
|
||||||
|
};
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
use std::io;
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
pub fn attach(program: impl Program + 'static) -> impl Program {
|
||||||
|
struct Attach<P> {
|
||||||
|
program: P,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P> Program for Attach<P>
|
||||||
|
where
|
||||||
|
P: Program + 'static,
|
||||||
|
{
|
||||||
|
type State = DevTools<P>;
|
||||||
|
type Message = Event<P>;
|
||||||
|
type Theme = P::Theme;
|
||||||
|
type Renderer = P::Renderer;
|
||||||
|
type Executor = P::Executor;
|
||||||
|
|
||||||
|
fn name() -> &'static str {
|
||||||
|
P::name()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
||||||
|
let (state, boot) = self.program.boot();
|
||||||
|
let (devtools, task) = DevTools::new(state);
|
||||||
|
|
||||||
|
(
|
||||||
|
devtools,
|
||||||
|
Task::batch([
|
||||||
|
boot.map(Event::Program),
|
||||||
|
task.map(Event::Message),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(
|
||||||
|
&self,
|
||||||
|
state: &mut Self::State,
|
||||||
|
message: Self::Message,
|
||||||
|
) -> Task<Self::Message> {
|
||||||
|
state.update(&self.program, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view<'a>(
|
||||||
|
&self,
|
||||||
|
state: &'a Self::State,
|
||||||
|
window: window::Id,
|
||||||
|
) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
|
||||||
|
state.view(&self.program, window)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn title(&self, state: &Self::State, window: window::Id) -> String {
|
||||||
|
state.title(&self.program, window)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subscription(
|
||||||
|
&self,
|
||||||
|
state: &Self::State,
|
||||||
|
) -> runtime::futures::Subscription<Self::Message> {
|
||||||
|
state.subscription(&self.program)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn theme(
|
||||||
|
&self,
|
||||||
|
state: &Self::State,
|
||||||
|
window: window::Id,
|
||||||
|
) -> Self::Theme {
|
||||||
|
state.theme(&self.program, window)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn style(
|
||||||
|
&self,
|
||||||
|
state: &Self::State,
|
||||||
|
theme: &Self::Theme,
|
||||||
|
) -> theme::Style {
|
||||||
|
state.style(&self.program, theme)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 {
|
||||||
|
state.scale_factor(&self.program, window)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Attach { program }
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DevTools<P>
|
||||||
|
where
|
||||||
|
P: Program,
|
||||||
|
{
|
||||||
|
state: P::State,
|
||||||
|
mode: Mode,
|
||||||
|
show_notification: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum Message {
|
||||||
|
HideNotification,
|
||||||
|
ToggleComet,
|
||||||
|
InstallComet,
|
||||||
|
InstallationLogged(String),
|
||||||
|
InstallationFinished,
|
||||||
|
CancelSetup,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Mode {
|
||||||
|
None,
|
||||||
|
Setup(Setup),
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Setup {
|
||||||
|
Idle,
|
||||||
|
Running { logs: Vec<String> },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P> DevTools<P>
|
||||||
|
where
|
||||||
|
P: Program + 'static,
|
||||||
|
{
|
||||||
|
pub fn new(state: P::State) -> (Self, Task<Message>) {
|
||||||
|
(
|
||||||
|
Self {
|
||||||
|
state,
|
||||||
|
mode: Mode::None,
|
||||||
|
show_notification: true,
|
||||||
|
},
|
||||||
|
executor::spawn_blocking(|mut sender| {
|
||||||
|
thread::sleep(seconds(2));
|
||||||
|
let _ = sender.try_send(());
|
||||||
|
})
|
||||||
|
.map(|_| Message::HideNotification),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn title(&self, program: &P, window: window::Id) -> String {
|
||||||
|
program.title(&self.state, window)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, program: &P, event: Event<P>) -> Task<Event<P>> {
|
||||||
|
match event {
|
||||||
|
Event::Message(message) => match message {
|
||||||
|
Message::HideNotification => {
|
||||||
|
self.show_notification = false;
|
||||||
|
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::ToggleComet => {
|
||||||
|
if let Mode::Setup(setup) = &self.mode {
|
||||||
|
if matches!(setup, Setup::Idle) {
|
||||||
|
self.mode = Mode::None;
|
||||||
|
}
|
||||||
|
} else if let Err(error) = debug::toggle_comet() {
|
||||||
|
if error.kind() == io::ErrorKind::NotFound {
|
||||||
|
self.mode = Mode::Setup(Setup::Idle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::InstallComet => {
|
||||||
|
self.mode =
|
||||||
|
Mode::Setup(Setup::Running { logs: Vec::new() });
|
||||||
|
|
||||||
|
executor::spawn_blocking(|mut sender| {
|
||||||
|
use std::io::{BufRead, BufReader};
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
|
let Ok(install) = Command::new("cargo")
|
||||||
|
.args([
|
||||||
|
"install",
|
||||||
|
"--locked",
|
||||||
|
"--git",
|
||||||
|
"https://github.com/iced-rs/comet.git",
|
||||||
|
"--rev",
|
||||||
|
"5efd34550e42974a0e85af7560c60401bfc13919",
|
||||||
|
])
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut stderr = BufReader::new(
|
||||||
|
install.stderr.expect("stderr must be piped"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut log = String::new();
|
||||||
|
|
||||||
|
while let Ok(n) = stderr.read_line(&mut log) {
|
||||||
|
if n == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = sender.try_send(
|
||||||
|
Message::InstallationLogged(log.clone()),
|
||||||
|
);
|
||||||
|
|
||||||
|
log.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = sender.try_send(Message::InstallationFinished);
|
||||||
|
})
|
||||||
|
.map(Event::Message)
|
||||||
|
}
|
||||||
|
Message::InstallationLogged(log) => {
|
||||||
|
if let Mode::Setup(Setup::Running { logs }) = &mut self.mode
|
||||||
|
{
|
||||||
|
logs.push(log);
|
||||||
|
}
|
||||||
|
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::InstallationFinished => {
|
||||||
|
self.mode = Mode::None;
|
||||||
|
|
||||||
|
let _ = debug::toggle_comet();
|
||||||
|
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::CancelSetup => {
|
||||||
|
self.mode = Mode::None;
|
||||||
|
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Event::Program(message) => {
|
||||||
|
program.update(&mut self.state, message).map(Event::Program)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view(
|
||||||
|
&self,
|
||||||
|
program: &P,
|
||||||
|
window: window::Id,
|
||||||
|
) -> Element<'_, Event<P>, P::Theme, P::Renderer> {
|
||||||
|
let view = program.view(&self.state, window).map(Event::Program);
|
||||||
|
let theme = program.theme(&self.state, window);
|
||||||
|
|
||||||
|
let derive_theme = move || {
|
||||||
|
theme
|
||||||
|
.palette()
|
||||||
|
.map(|palette| Theme::custom("DevTools".to_owned(), palette))
|
||||||
|
.unwrap_or_default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mode = match &self.mode {
|
||||||
|
Mode::None => None,
|
||||||
|
Mode::Setup(setup) => {
|
||||||
|
let stage: Element<'_, _, Theme, P::Renderer> = match setup {
|
||||||
|
Setup::Idle => {
|
||||||
|
let controls = row![
|
||||||
|
button(text("Cancel").center().width(Fill))
|
||||||
|
.width(100)
|
||||||
|
.on_press(Message::CancelSetup)
|
||||||
|
.style(button::danger),
|
||||||
|
horizontal_space(),
|
||||||
|
button(text("Install").center().width(Fill))
|
||||||
|
.width(100)
|
||||||
|
.on_press(Message::InstallComet)
|
||||||
|
.style(button::success),
|
||||||
|
];
|
||||||
|
|
||||||
|
column![
|
||||||
|
text("comet is not installed!").size(20),
|
||||||
|
"In order to display performance metrics, the \
|
||||||
|
comet debugger must be installed in your system.",
|
||||||
|
"The comet debugger is an official companion tool \
|
||||||
|
that helps you debug your iced applications.",
|
||||||
|
"Do you wish to install it with the following \
|
||||||
|
command?",
|
||||||
|
container(
|
||||||
|
text(
|
||||||
|
"cargo install --locked \
|
||||||
|
--git https://github.com/iced-rs/comet.git"
|
||||||
|
)
|
||||||
|
.size(14)
|
||||||
|
)
|
||||||
|
.width(Fill)
|
||||||
|
.padding(5)
|
||||||
|
.style(container::dark),
|
||||||
|
controls,
|
||||||
|
]
|
||||||
|
.spacing(20)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
Setup::Running { logs } => column![
|
||||||
|
text("Installing comet...").size(20),
|
||||||
|
container(
|
||||||
|
scrollable(
|
||||||
|
column(
|
||||||
|
logs.iter()
|
||||||
|
.map(|log| text(log).size(12).into()),
|
||||||
|
)
|
||||||
|
.spacing(3),
|
||||||
|
)
|
||||||
|
.spacing(10)
|
||||||
|
.width(Fill)
|
||||||
|
.height(300)
|
||||||
|
.anchor_bottom(),
|
||||||
|
)
|
||||||
|
.padding(10)
|
||||||
|
.style(container::dark)
|
||||||
|
]
|
||||||
|
.spacing(20)
|
||||||
|
.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let setup = center(
|
||||||
|
container(stage)
|
||||||
|
.padding(20)
|
||||||
|
.width(500)
|
||||||
|
.style(container::bordered_box),
|
||||||
|
)
|
||||||
|
.padding(10)
|
||||||
|
.style(|_theme| {
|
||||||
|
container::Style::default()
|
||||||
|
.background(Color::BLACK.scale_alpha(0.8))
|
||||||
|
});
|
||||||
|
|
||||||
|
Some(setup)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.map(|mode| {
|
||||||
|
themer(derive_theme(), Element::from(mode).map(Event::Message))
|
||||||
|
});
|
||||||
|
|
||||||
|
let notification = self.show_notification.then(|| {
|
||||||
|
themer(
|
||||||
|
derive_theme(),
|
||||||
|
bottom_right(
|
||||||
|
container(text("Press F12 to open debug metrics"))
|
||||||
|
.padding(10)
|
||||||
|
.style(container::dark),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
stack![view]
|
||||||
|
.push_maybe(mode)
|
||||||
|
.push_maybe(notification)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn subscription(&self, program: &P) -> Subscription<Event<P>> {
|
||||||
|
let subscription =
|
||||||
|
program.subscription(&self.state).map(Event::Program);
|
||||||
|
|
||||||
|
let hotkeys =
|
||||||
|
futures::keyboard::on_key_press(|key, _modifiers| match key {
|
||||||
|
keyboard::Key::Named(keyboard::key::Named::F12) => {
|
||||||
|
Some(Message::ToggleComet)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.map(Event::Message);
|
||||||
|
|
||||||
|
Subscription::batch([subscription, hotkeys])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn theme(&self, program: &P, window: window::Id) -> P::Theme {
|
||||||
|
program.theme(&self.state, window)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn style(&self, program: &P, theme: &P::Theme) -> theme::Style {
|
||||||
|
program.style(&self.state, theme)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scale_factor(&self, program: &P, window: window::Id) -> f64 {
|
||||||
|
program.scale_factor(&self.state, window)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Event<P>
|
||||||
|
where
|
||||||
|
P: Program,
|
||||||
|
{
|
||||||
|
Message(Message),
|
||||||
|
Program(P::Message),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P> fmt::Debug for Event<P>
|
||||||
|
where
|
||||||
|
P: Program,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Message(message) => message.fmt(f),
|
||||||
|
Self::Program(message) => message.fmt(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
docs/logo-no-shadow.svg
Normal file
2
docs/logo-no-shadow.svg
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="140" height="140" fill="none" version="1.1" viewBox="35 31 179 171"><rect x="42" y="31.001" width="169.9" height="169.9" rx="49.815" fill="url(#paint1_linear)"/><path d="m182.62 65.747-28.136 28.606-6.13-6.0291 28.136-28.606 6.13 6.0291zm-26.344 0.218-42.204 42.909-6.13-6.029 42.204-42.909 6.13 6.0291zm-61.648 23.913c5.3254-5.3831 10.65-10.765 21.569-21.867l6.13 6.0291c-10.927 11.11-16.258 16.498-21.587 21.885-4.4007 4.4488-8.8009 8.8968-16.359 16.573l31.977 8.358 25.968-26.402 6.13 6.0292-25.968 26.402 8.907 31.908 42.138-42.087 6.076 6.083-49.109 49.05-45.837-12.628-13.394-45.646 1.7714-1.801c10.928-11.111 16.258-16.499 21.588-21.886zm28.419 70.99-8.846-31.689-31.831-8.32 9.1945 31.335 31.482 8.674zm47.734-56.517 7.122-7.1221-6.08-6.0797-7.147 7.1474-30.171 30.674 6.13 6.029 30.146-30.649z" clip-rule="evenodd" fill="url(#paint2_linear)" fill-rule="evenodd"/><defs><filter id="filter0_f" x="55" y="47.001" width="144" height="168" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur" stdDeviation="2"/></filter><linearGradient id="paint0_linear" x1="127" x2="127" y1="51.001" y2="211" gradientUnits="userSpaceOnUse"><stop offset=".052083"/><stop stop-opacity=".08" offset="1"/></linearGradient><linearGradient id="paint1_linear" x1="212" x2="57.5" y1="31.001" y2="189" gradientUnits="userSpaceOnUse"><stop stop-color="#00A3FF" offset="0"/><stop stop-color="#30f" offset="1"/></linearGradient><linearGradient id="paint2_linear" x1="86.098" x2="206.01" y1="158.28" y2="35.327" gradientUnits="userSpaceOnUse"><stop stop-color="#fff" offset="0"/><stop stop-color="#fff" offset="1"/></linearGradient></defs></svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
|
|
@ -8,10 +8,9 @@ use iced::window;
|
||||||
use iced::{Element, Fill, Point, Rectangle, Renderer, Subscription, Theme};
|
use iced::{Element, Fill, Point, Rectangle, Renderer, Subscription, Theme};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::application("Arc - Iced", Arc::update, Arc::view)
|
iced::application(Arc::default, Arc::update, Arc::view)
|
||||||
.subscription(Arc::subscription)
|
.subscription(Arc::subscription)
|
||||||
.theme(|_| Theme::Dark)
|
.theme(|_| Theme::Dark)
|
||||||
.antialiasing(true)
|
|
||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,8 @@ use iced::widget::{button, container, horizontal_space, hover, right};
|
||||||
use iced::{Element, Theme};
|
use iced::{Element, Theme};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::application("Bezier Tool - Iced", Example::update, Example::view)
|
iced::application(Example::default, Example::update, Example::view)
|
||||||
.theme(|_| Theme::CatppuccinMocha)
|
.theme(|_| Theme::CatppuccinMocha)
|
||||||
.antialiasing(true)
|
|
||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,9 @@ use iced::{Center, Element, Fill, FillPortion, Font, Task, Theme};
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
iced::application("Changelog Generator", Generator::update, Generator::view)
|
iced::application(Generator::new, Generator::update, Generator::view)
|
||||||
.theme(Generator::theme)
|
.theme(Generator::theme)
|
||||||
.run_with(Generator::new)
|
.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Generator {
|
enum Generator {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use iced::{Element, Font};
|
||||||
const ICON_FONT: Font = Font::with_name("icons");
|
const ICON_FONT: Font = Font::with_name("icons");
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::application("Checkbox - Iced", Example::update, Example::view)
|
iced::application(Example::default, Example::update, Example::view)
|
||||||
.font(include_bytes!("../fonts/icons.ttf").as_slice())
|
.font(include_bytes!("../fonts/icons.ttf").as_slice())
|
||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,9 @@ use iced::{
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
iced::application("Clock - Iced", Clock::update, Clock::view)
|
iced::application(Clock::default, Clock::update, Clock::view)
|
||||||
.subscription(Clock::subscription)
|
.subscription(Clock::subscription)
|
||||||
.theme(Clock::theme)
|
.theme(Clock::theme)
|
||||||
.antialiasing(true)
|
|
||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,12 @@ use std::ops::RangeInclusive;
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::application(
|
iced::application(
|
||||||
"Color Palette - Iced",
|
ColorPalette::default,
|
||||||
ColorPalette::update,
|
ColorPalette::update,
|
||||||
ColorPalette::view,
|
ColorPalette::view,
|
||||||
)
|
)
|
||||||
.theme(ColorPalette::theme)
|
.theme(ColorPalette::theme)
|
||||||
.default_font(Font::MONOSPACE)
|
.default_font(Font::MONOSPACE)
|
||||||
.antialiasing(true)
|
|
||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use iced::widget::{
|
||||||
use iced::{Center, Element, Fill};
|
use iced::{Center, Element, Fill};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::run("Combo Box - Iced", Example::update, Example::view)
|
iced::run(Example::update, Example::view)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Example {
|
struct Example {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use iced::Center;
|
||||||
use iced::widget::{Column, button, column, text};
|
use iced::widget::{Column, button, column, text};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::run("A cool counter", Counter::update, Counter::view)
|
iced::run(Counter::update, Counter::view)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ use iced::widget::{center, column, slider, text};
|
||||||
use iced::{Center, Color, Element, Shadow, Vector};
|
use iced::{Center, Color, Element, Shadow, Vector};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::run("Custom Quad - Iced", Example::update, Example::view)
|
iced::run(Example::update, Example::view)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Example {
|
struct Example {
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,9 @@ use iced::window;
|
||||||
use iced::{Center, Color, Element, Fill, Subscription};
|
use iced::{Center, Color, Element, Fill, Subscription};
|
||||||
|
|
||||||
fn main() -> iced::Result {
|
fn main() -> iced::Result {
|
||||||
iced::application(
|
iced::application(IcedCubes::default, IcedCubes::update, IcedCubes::view)
|
||||||
"Custom Shader - Iced",
|
.subscription(IcedCubes::subscription)
|
||||||
IcedCubes::update,
|
.run()
|
||||||
IcedCubes::view,
|
|
||||||
)
|
|
||||||
.subscription(IcedCubes::subscription)
|
|
||||||
.run()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct IcedCubes {
|
struct IcedCubes {
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ use iced::widget::{center, column, slider, text};
|
||||||
use iced::{Center, Element};
|
use iced::{Center, Element};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::run("Custom Widget - Iced", Example::update, Example::view)
|
iced::run(Example::update, Example::view)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Example {
|
struct Example {
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,7 @@ use iced::widget::{Column, button, center, column, progress_bar, text};
|
||||||
use iced::{Center, Element, Function, Right, Task};
|
use iced::{Center, Element, Function, Right, Task};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::application(
|
iced::application(Example::default, Example::update, Example::view).run()
|
||||||
"Download Progress - Iced",
|
|
||||||
Example::update,
|
|
||||||
Example::view,
|
|
||||||
)
|
|
||||||
.run()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,11 @@ use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::application("Editor - Iced", Editor::update, Editor::view)
|
iced::application(Editor::new, Editor::update, Editor::view)
|
||||||
.theme(Editor::theme)
|
.theme(Editor::theme)
|
||||||
.font(include_bytes!("../fonts/icons.ttf").as_slice())
|
.font(include_bytes!("../fonts/icons.ttf").as_slice())
|
||||||
.default_font(Font::MONOSPACE)
|
.default_font(Font::MONOSPACE)
|
||||||
.run_with(Editor::new)
|
.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Editor {
|
struct Editor {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use iced::window;
|
||||||
use iced::{Center, Element, Fill, Subscription, Task};
|
use iced::{Center, Element, Fill, Subscription, Task};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::application("Events - Iced", Events::update, Events::view)
|
iced::application(Events::default, Events::update, Events::view)
|
||||||
.subscription(Events::subscription)
|
.subscription(Events::subscription)
|
||||||
.exit_on_close_request(false)
|
.exit_on_close_request(false)
|
||||||
.run()
|
.run()
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use iced::window;
|
||||||
use iced::{Center, Element, Task};
|
use iced::{Center, Element, Task};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::application("Exit - Iced", Exit::update, Exit::view).run()
|
iced::run(Exit::update, Exit::view)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use iced::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::application("Ferris - Iced", Image::update, Image::view)
|
iced::application(Image::default, Image::update, Image::view)
|
||||||
.subscription(Image::subscription)
|
.subscription(Image::subscription)
|
||||||
.theme(|_| Theme::TokyoNight)
|
.theme(|_| Theme::TokyoNight)
|
||||||
.run()
|
.run()
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,10 @@ use iced::{
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
fn main() -> iced::Result {
|
fn main() -> iced::Result {
|
||||||
iced::application("Gallery - Iced", Gallery::update, Gallery::view)
|
iced::application(Gallery::new, Gallery::update, Gallery::view)
|
||||||
.subscription(Gallery::subscription)
|
.subscription(Gallery::subscription)
|
||||||
.theme(Gallery::theme)
|
.theme(Gallery::theme)
|
||||||
.run_with(Gallery::new)
|
.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Gallery {
|
struct Gallery {
|
||||||
|
|
|
||||||
|
|
@ -14,16 +14,11 @@ use iced::{Center, Element, Fill, Function, Subscription, Task, Theme};
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
iced::application(
|
iced::application(GameOfLife::default, GameOfLife::update, GameOfLife::view)
|
||||||
"Game of Life - Iced",
|
.subscription(GameOfLife::subscription)
|
||||||
GameOfLife::update,
|
.theme(|_| Theme::Dark)
|
||||||
GameOfLife::view,
|
.centered()
|
||||||
)
|
.run()
|
||||||
.subscription(GameOfLife::subscription)
|
|
||||||
.theme(|_| Theme::Dark)
|
|
||||||
.antialiasing(true)
|
|
||||||
.centered()
|
|
||||||
.run()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GameOfLife {
|
struct GameOfLife {
|
||||||
|
|
|
||||||
|
|
@ -157,7 +157,7 @@ use iced::widget::{center_x, center_y, column, scrollable};
|
||||||
use rainbow::rainbow;
|
use rainbow::rainbow;
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::run("Custom 2D Geometry - Iced", |_: &mut _, _| {}, view)
|
iced::run((), view)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(_state: &()) -> Element<'_, ()> {
|
fn view(_state: &()) -> Element<'_, ()> {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use iced::{Center, Color, Element, Fill, Radians, Theme, color};
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
iced::application("Gradient - Iced", Gradient::update, Gradient::view)
|
iced::application(Gradient::default, Gradient::update, Gradient::view)
|
||||||
.style(Gradient::style)
|
.style(Gradient::style)
|
||||||
.transparent(true)
|
.transparent(true)
|
||||||
.run()
|
.run()
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use iced_wgpu::Renderer;
|
use iced_wgpu::Renderer;
|
||||||
use iced_widget::{bottom, column, row, slider, text, text_input};
|
use iced_widget::{bottom, column, row, slider, text, text_input};
|
||||||
use iced_winit::core::{Color, Element, Theme};
|
use iced_winit::core::{Color, Element, Theme};
|
||||||
use iced_winit::runtime::{Program, Task};
|
|
||||||
|
|
||||||
pub struct Controls {
|
pub struct Controls {
|
||||||
background_color: Color,
|
background_color: Color,
|
||||||
|
|
@ -27,12 +26,8 @@ impl Controls {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Program for Controls {
|
impl Controls {
|
||||||
type Theme = Theme;
|
pub fn update(&mut self, message: Message) {
|
||||||
type Message = Message;
|
|
||||||
type Renderer = Renderer;
|
|
||||||
|
|
||||||
fn update(&mut self, message: Message) -> Task<Message> {
|
|
||||||
match message {
|
match message {
|
||||||
Message::BackgroundColorChanged(color) => {
|
Message::BackgroundColorChanged(color) => {
|
||||||
self.background_color = color;
|
self.background_color = color;
|
||||||
|
|
@ -41,11 +36,9 @@ impl Program for Controls {
|
||||||
self.input = input;
|
self.input = input;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Task::none()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> Element<Message, Theme, Renderer> {
|
pub fn view(&self) -> Element<Message, Theme, Renderer> {
|
||||||
let background_color = self.background_color;
|
let background_color = self.background_color;
|
||||||
|
|
||||||
let sliders = row![
|
let sliders = row![
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,11 @@ use iced_winit::Clipboard;
|
||||||
use iced_winit::conversion;
|
use iced_winit::conversion;
|
||||||
use iced_winit::core::mouse;
|
use iced_winit::core::mouse;
|
||||||
use iced_winit::core::renderer;
|
use iced_winit::core::renderer;
|
||||||
use iced_winit::core::{Color, Font, Pixels, Size, Theme};
|
use iced_winit::core::time::Instant;
|
||||||
|
use iced_winit::core::window;
|
||||||
|
use iced_winit::core::{Event, Font, Pixels, Size, Theme};
|
||||||
use iced_winit::futures;
|
use iced_winit::futures;
|
||||||
use iced_winit::runtime::Debug;
|
use iced_winit::runtime::user_interface::{self, UserInterface};
|
||||||
use iced_winit::runtime::program;
|
|
||||||
use iced_winit::winit;
|
use iced_winit::winit;
|
||||||
|
|
||||||
use winit::{
|
use winit::{
|
||||||
|
|
@ -41,13 +42,14 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
||||||
format: wgpu::TextureFormat,
|
format: wgpu::TextureFormat,
|
||||||
renderer: Renderer,
|
renderer: Renderer,
|
||||||
scene: Scene,
|
scene: Scene,
|
||||||
state: program::State<Controls>,
|
controls: Controls,
|
||||||
cursor_position: Option<winit::dpi::PhysicalPosition<f64>>,
|
events: Vec<Event>,
|
||||||
|
cursor: mouse::Cursor,
|
||||||
|
cache: user_interface::Cache,
|
||||||
clipboard: Clipboard,
|
clipboard: Clipboard,
|
||||||
viewport: Viewport,
|
viewport: Viewport,
|
||||||
modifiers: ModifiersState,
|
modifiers: ModifiersState,
|
||||||
resized: bool,
|
resized: bool,
|
||||||
debug: Debug,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -143,9 +145,8 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
||||||
let controls = Controls::new();
|
let controls = Controls::new();
|
||||||
|
|
||||||
// Initialize iced
|
// Initialize iced
|
||||||
let mut debug = Debug::new();
|
|
||||||
|
|
||||||
let mut renderer = {
|
let renderer = {
|
||||||
let engine = Engine::new(
|
let engine = Engine::new(
|
||||||
&adapter,
|
&adapter,
|
||||||
device.clone(),
|
device.clone(),
|
||||||
|
|
@ -157,13 +158,6 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
||||||
Renderer::new(engine, Font::default(), Pixels::from(16))
|
Renderer::new(engine, Font::default(), Pixels::from(16))
|
||||||
};
|
};
|
||||||
|
|
||||||
let state = program::State::new(
|
|
||||||
controls,
|
|
||||||
viewport.logical_size(),
|
|
||||||
&mut renderer,
|
|
||||||
&mut debug,
|
|
||||||
);
|
|
||||||
|
|
||||||
// You should change this if you want to render continuously
|
// You should change this if you want to render continuously
|
||||||
event_loop.set_control_flow(ControlFlow::Wait);
|
event_loop.set_control_flow(ControlFlow::Wait);
|
||||||
|
|
||||||
|
|
@ -175,13 +169,14 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
||||||
surface,
|
surface,
|
||||||
format,
|
format,
|
||||||
scene,
|
scene,
|
||||||
state,
|
controls,
|
||||||
cursor_position: None,
|
events: Vec::new(),
|
||||||
|
cursor: mouse::Cursor::Unavailable,
|
||||||
modifiers: ModifiersState::default(),
|
modifiers: ModifiersState::default(),
|
||||||
|
cache: user_interface::Cache::new(),
|
||||||
clipboard,
|
clipboard,
|
||||||
viewport,
|
viewport,
|
||||||
resized: false,
|
resized: false,
|
||||||
debug,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -200,13 +195,14 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
||||||
format,
|
format,
|
||||||
renderer,
|
renderer,
|
||||||
scene,
|
scene,
|
||||||
state,
|
controls,
|
||||||
|
events,
|
||||||
viewport,
|
viewport,
|
||||||
cursor_position,
|
cursor,
|
||||||
modifiers,
|
modifiers,
|
||||||
clipboard,
|
clipboard,
|
||||||
|
cache,
|
||||||
resized,
|
resized,
|
||||||
debug,
|
|
||||||
} = self
|
} = self
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
|
|
@ -241,8 +237,6 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
||||||
|
|
||||||
match surface.get_current_texture() {
|
match surface.get_current_texture() {
|
||||||
Ok(frame) => {
|
Ok(frame) => {
|
||||||
let program = state.program();
|
|
||||||
|
|
||||||
let view = frame.texture.create_view(
|
let view = frame.texture.create_view(
|
||||||
&wgpu::TextureViewDescriptor::default(),
|
&wgpu::TextureViewDescriptor::default(),
|
||||||
);
|
);
|
||||||
|
|
@ -256,7 +250,7 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
||||||
let mut render_pass = Scene::clear(
|
let mut render_pass = Scene::clear(
|
||||||
&view,
|
&view,
|
||||||
&mut encoder,
|
&mut encoder,
|
||||||
program.background_color(),
|
controls.background_color(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Draw the scene
|
// Draw the scene
|
||||||
|
|
@ -267,29 +261,53 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
||||||
queue.submit([encoder.finish()]);
|
queue.submit([encoder.finish()]);
|
||||||
|
|
||||||
// Draw iced on top
|
// Draw iced on top
|
||||||
|
let mut interface = UserInterface::build(
|
||||||
|
controls.view(),
|
||||||
|
viewport.logical_size(),
|
||||||
|
std::mem::take(cache),
|
||||||
|
renderer,
|
||||||
|
);
|
||||||
|
|
||||||
|
let _ = interface.update(
|
||||||
|
&[Event::Window(
|
||||||
|
window::Event::RedrawRequested(
|
||||||
|
Instant::now(),
|
||||||
|
),
|
||||||
|
)],
|
||||||
|
*cursor,
|
||||||
|
renderer,
|
||||||
|
clipboard,
|
||||||
|
&mut Vec::new(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mouse_interaction = interface.draw(
|
||||||
|
renderer,
|
||||||
|
&Theme::Dark,
|
||||||
|
&renderer::Style::default(),
|
||||||
|
*cursor,
|
||||||
|
);
|
||||||
|
*cache = interface.into_cache();
|
||||||
|
|
||||||
renderer.present(
|
renderer.present(
|
||||||
None,
|
None,
|
||||||
frame.texture.format(),
|
frame.texture.format(),
|
||||||
&view,
|
&view,
|
||||||
viewport,
|
viewport,
|
||||||
&debug.overlay(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Present the frame
|
// Present the frame
|
||||||
frame.present();
|
frame.present();
|
||||||
|
|
||||||
// Update the mouse cursor
|
// Update the mouse cursor
|
||||||
window.set_cursor(
|
window.set_cursor(conversion::mouse_interaction(
|
||||||
iced_winit::conversion::mouse_interaction(
|
mouse_interaction,
|
||||||
state.mouse_interaction(),
|
));
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Err(error) => match error {
|
Err(error) => match error {
|
||||||
wgpu::SurfaceError::OutOfMemory => {
|
wgpu::SurfaceError::OutOfMemory => {
|
||||||
panic!(
|
panic!(
|
||||||
"Swapchain error: {error}. \
|
"Swapchain error: {error}. \
|
||||||
Rendering cannot continue."
|
Rendering cannot continue."
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
|
@ -300,7 +318,11 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WindowEvent::CursorMoved { position, .. } => {
|
WindowEvent::CursorMoved { position, .. } => {
|
||||||
*cursor_position = Some(position);
|
*cursor =
|
||||||
|
mouse::Cursor::Available(conversion::cursor_position(
|
||||||
|
position,
|
||||||
|
viewport.scale_factor(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
WindowEvent::ModifiersChanged(new_modifiers) => {
|
WindowEvent::ModifiersChanged(new_modifiers) => {
|
||||||
*modifiers = new_modifiers.state();
|
*modifiers = new_modifiers.state();
|
||||||
|
|
@ -315,37 +337,42 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map window event to iced event
|
// Map window event to iced event
|
||||||
if let Some(event) = iced_winit::conversion::window_event(
|
if let Some(event) = conversion::window_event(
|
||||||
event,
|
event,
|
||||||
window.scale_factor(),
|
window.scale_factor(),
|
||||||
*modifiers,
|
*modifiers,
|
||||||
) {
|
) {
|
||||||
state.queue_event(event);
|
events.push(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there are events pending
|
// If there are events pending
|
||||||
if !state.is_queue_empty() {
|
if !events.is_empty() {
|
||||||
// We update iced
|
// We process them
|
||||||
let _ = state.update(
|
let mut interface = UserInterface::build(
|
||||||
|
controls.view(),
|
||||||
viewport.logical_size(),
|
viewport.logical_size(),
|
||||||
cursor_position
|
std::mem::take(cache),
|
||||||
.map(|p| {
|
|
||||||
conversion::cursor_position(
|
|
||||||
p,
|
|
||||||
viewport.scale_factor(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.map(mouse::Cursor::Available)
|
|
||||||
.unwrap_or(mouse::Cursor::Unavailable),
|
|
||||||
renderer,
|
renderer,
|
||||||
&Theme::Dark,
|
|
||||||
&renderer::Style {
|
|
||||||
text_color: Color::WHITE,
|
|
||||||
},
|
|
||||||
clipboard,
|
|
||||||
debug,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let mut messages = Vec::new();
|
||||||
|
|
||||||
|
let _ = interface.update(
|
||||||
|
events,
|
||||||
|
*cursor,
|
||||||
|
renderer,
|
||||||
|
clipboard,
|
||||||
|
&mut messages,
|
||||||
|
);
|
||||||
|
|
||||||
|
events.clear();
|
||||||
|
*cache = interface.into_cache();
|
||||||
|
|
||||||
|
// update our UI with any messages
|
||||||
|
for message in messages {
|
||||||
|
controls.update(message);
|
||||||
|
}
|
||||||
|
|
||||||
// and request a redraw
|
// and request a redraw
|
||||||
window.request_redraw();
|
window.request_redraw();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,10 @@ use iced::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::application(Layout::title, Layout::update, Layout::view)
|
iced::application(Layout::default, Layout::update, Layout::view)
|
||||||
.subscription(Layout::subscription)
|
.subscription(Layout::subscription)
|
||||||
.theme(Layout::theme)
|
.theme(Layout::theme)
|
||||||
|
.title(Layout::title)
|
||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use std::collections::HashSet;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::run("Lazy - Iced", App::update, App::view)
|
iced::run(App::update, App::view)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct App {
|
struct App {
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,10 @@ use linear::Linear;
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::application(
|
iced::application(
|
||||||
"Loading Spinners - Iced",
|
LoadingSpinners::default,
|
||||||
LoadingSpinners::update,
|
LoadingSpinners::update,
|
||||||
LoadingSpinners::view,
|
LoadingSpinners::view,
|
||||||
)
|
)
|
||||||
.antialiasing(true)
|
|
||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use iced::{Center, Element};
|
||||||
use loupe::loupe;
|
use loupe::loupe;
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::run("Loupe - Iced", Loupe::update, Loupe::view)
|
iced::run(Loupe::update, Loupe::view)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,11 @@ use std::io;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::application("Markdown - Iced", Markdown::update, Markdown::view)
|
iced::application(Markdown::new, Markdown::update, Markdown::view)
|
||||||
.font(icon::FONT)
|
.font(icon::FONT)
|
||||||
.subscription(Markdown::subscription)
|
.subscription(Markdown::subscription)
|
||||||
.theme(Markdown::theme)
|
.theme(Markdown::theme)
|
||||||
.run_with(Markdown::new)
|
.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Markdown {
|
struct Markdown {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use iced::{Bottom, Color, Element, Fill, Subscription, Task};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::application("Modal - Iced", App::update, App::view)
|
iced::application(App::default, App::update, App::view)
|
||||||
.subscription(App::subscription)
|
.subscription(App::subscription)
|
||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,12 @@ use iced::{
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
fn main() -> iced::Result {
|
fn main() -> iced::Result {
|
||||||
iced::daemon(Example::title, Example::update, Example::view)
|
iced::daemon(Example::new, Example::update, Example::view)
|
||||||
.subscription(Example::subscription)
|
.subscription(Example::subscription)
|
||||||
|
.title(Example::title)
|
||||||
.theme(Example::theme)
|
.theme(Example::theme)
|
||||||
.scale_factor(Example::scale_factor)
|
.scale_factor(Example::scale_factor)
|
||||||
.run_with(Example::new)
|
.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Example {
|
struct Example {
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,7 @@ use std::collections::HashMap;
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
iced::application("Multitouch - Iced", Multitouch::update, Multitouch::view)
|
iced::application(Multitouch::default, Multitouch::update, Multitouch::view)
|
||||||
.antialiasing(true)
|
|
||||||
.centered()
|
.centered()
|
||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use iced::widget::{
|
||||||
use iced::{Center, Color, Element, Fill, Size, Subscription};
|
use iced::{Center, Color, Element, Fill, Size, Subscription};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::application("Pane Grid - Iced", Example::update, Example::view)
|
iced::application(Example::default, Example::update, Example::view)
|
||||||
.subscription(Example::subscription)
|
.subscription(Example::subscription)
|
||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use iced::widget::{column, pick_list, scrollable, vertical_space};
|
||||||
use iced::{Center, Element, Fill};
|
use iced::{Center, Element, Fill};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::run("Pick List - Iced", Example::update, Example::view)
|
iced::run(Example::update, Example::view)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@ use iced::widget::{self, center, column, image, row, text};
|
||||||
use iced::{Center, Element, Fill, Right, Task};
|
use iced::{Center, Element, Fill, Right, Task};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::application(Pokedex::title, Pokedex::update, Pokedex::view)
|
iced::application(Pokedex::new, Pokedex::update, Pokedex::view)
|
||||||
.run_with(Pokedex::new)
|
.title(Pokedex::title)
|
||||||
|
.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use iced::widget::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::run("Progress Bar - Iced", Progress::update, Progress::view)
|
iced::run(Progress::update, Progress::view)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use std::ops::RangeInclusive;
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::application(
|
iced::application(
|
||||||
"QR Code Generator - Iced",
|
QRGenerator::default,
|
||||||
QRGenerator::update,
|
QRGenerator::update,
|
||||||
QRGenerator::view,
|
QRGenerator::view,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ use ::image::ColorType;
|
||||||
fn main() -> iced::Result {
|
fn main() -> iced::Result {
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
iced::application("Screenshot - Iced", Example::update, Example::view)
|
iced::application(Example::default, Example::update, Example::view)
|
||||||
.subscription(Example::subscription)
|
.subscription(Example::subscription)
|
||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ static SCROLLABLE_ID: LazyLock<scrollable::Id> =
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::application(
|
iced::application(
|
||||||
"Scrollable - Iced",
|
ScrollableDemo::default,
|
||||||
ScrollableDemo::update,
|
ScrollableDemo::update,
|
||||||
ScrollableDemo::view,
|
ScrollableDemo::view,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,10 @@ use std::fmt::Debug;
|
||||||
|
|
||||||
fn main() -> iced::Result {
|
fn main() -> iced::Result {
|
||||||
iced::application(
|
iced::application(
|
||||||
"Sierpinski Triangle - Iced",
|
SierpinskiEmulator::default,
|
||||||
SierpinskiEmulator::update,
|
SierpinskiEmulator::update,
|
||||||
SierpinskiEmulator::view,
|
SierpinskiEmulator::view,
|
||||||
)
|
)
|
||||||
.antialiasing(true)
|
|
||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use iced::widget::{column, container, iced, slider, text, vertical_slider};
|
||||||
use iced::{Center, Element, Fill};
|
use iced::{Center, Element, Fill};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::run("Slider - Iced", Slider::update, Slider::view)
|
iced::run(Slider::update, Slider::view)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ pub fn main() -> iced::Result {
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
iced::application(
|
iced::application(
|
||||||
"Solar System - Iced",
|
SolarSystem::default,
|
||||||
SolarSystem::update,
|
SolarSystem::update,
|
||||||
SolarSystem::view,
|
SolarSystem::view,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use iced::widget::{button, center, column, row, text};
|
||||||
use iced::{Center, Element, Subscription, Theme};
|
use iced::{Center, Element, Subscription, Theme};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::application("Stopwatch - Iced", Stopwatch::update, Stopwatch::view)
|
iced::application(Stopwatch::default, Stopwatch::update, Stopwatch::view)
|
||||||
.subscription(Stopwatch::subscription)
|
.subscription(Stopwatch::subscription)
|
||||||
.theme(Stopwatch::theme)
|
.theme(Stopwatch::theme)
|
||||||
.run()
|
.run()
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use iced::widget::{
|
||||||
use iced::{Center, Element, Fill, Subscription, Theme};
|
use iced::{Center, Element, Fill, Subscription, Theme};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::application("Styling - Iced", Styling::update, Styling::view)
|
iced::application(Styling::default, Styling::update, Styling::view)
|
||||||
.subscription(Styling::subscription)
|
.subscription(Styling::subscription)
|
||||||
.theme(Styling::theme)
|
.theme(Styling::theme)
|
||||||
.run()
|
.run()
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use iced::widget::{center, center_x, checkbox, column, svg};
|
||||||
use iced::{Element, Fill, color};
|
use iced::{Element, Fill, color};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::run("SVG - Iced", Tiger::update, Tiger::view)
|
iced::run(Tiger::update, Tiger::view)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,7 @@ use iced::widget::{button, center, column, text};
|
||||||
use iced::{Element, Task, system};
|
use iced::{Element, Task, system};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::application(
|
iced::application(Example::new, Example::update, Example::view).run()
|
||||||
"System Information - Iced",
|
|
||||||
Example::update,
|
|
||||||
Example::view,
|
|
||||||
)
|
|
||||||
.run_with(Example::new)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,8 @@ use std::cell::RefCell;
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
iced::application("The Matrix - Iced", TheMatrix::update, TheMatrix::view)
|
iced::application(TheMatrix::default, TheMatrix::update, TheMatrix::view)
|
||||||
.subscription(TheMatrix::subscription)
|
.subscription(TheMatrix::subscription)
|
||||||
.antialiasing(true)
|
|
||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use iced::{Center, Element, Fill, Subscription, Task};
|
||||||
use toast::{Status, Toast};
|
use toast::{Status, Toast};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::application("Toast - Iced", App::update, App::view)
|
iced::application(App::default, App::update, App::view)
|
||||||
.subscription(App::subscription)
|
.subscription(App::subscription)
|
||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,12 @@ pub fn main() -> iced::Result {
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
iced::application(Todos::title, Todos::update, Todos::view)
|
iced::application(Todos::new, Todos::update, Todos::view)
|
||||||
.subscription(Todos::subscription)
|
.subscription(Todos::subscription)
|
||||||
|
.title(Todos::title)
|
||||||
.font(Todos::ICON_FONT)
|
.font(Todos::ICON_FONT)
|
||||||
.window_size((500.0, 800.0))
|
.window_size((500.0, 800.0))
|
||||||
.run_with(Todos::new)
|
.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use iced::widget::tooltip::Position;
|
||||||
use iced::widget::{button, center, container, tooltip};
|
use iced::widget::{button, center, container, tooltip};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::run("Tooltip - Iced", Tooltip::update, Tooltip::view)
|
iced::run(Tooltip::update, Tooltip::view)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,8 @@ pub fn main() -> iced::Result {
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
iced::application(Tour::title, Tour::update, Tour::view)
|
iced::application(Tour::default, Tour::update, Tour::view)
|
||||||
|
.title(Tour::title)
|
||||||
.centered()
|
.centered()
|
||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use iced::widget::{center, text};
|
||||||
use iced::{Element, Subscription};
|
use iced::{Element, Subscription};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::application("URL Handler - Iced", App::update, App::view)
|
iced::application(App::default, App::update, App::view)
|
||||||
.subscription(App::subscription)
|
.subscription(App::subscription)
|
||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,11 @@ use iced::{Center, Element, Fill, Point, Rectangle, Renderer, Theme, Vector};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::application(
|
iced::application(
|
||||||
"Vectorial Text - Iced",
|
VectorialText::default,
|
||||||
VectorialText::update,
|
VectorialText::update,
|
||||||
VectorialText::view,
|
VectorialText::view,
|
||||||
)
|
)
|
||||||
.theme(|_| Theme::Dark)
|
.theme(|_| Theme::Dark)
|
||||||
.antialiasing(true)
|
|
||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use iced::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::application("Visible Bounds - Iced", Example::update, Example::view)
|
iced::application(Example::default, Example::update, Example::view)
|
||||||
.subscription(Example::subscription)
|
.subscription(Example::subscription)
|
||||||
.theme(|_| Theme::Dark)
|
.theme(|_| Theme::Dark)
|
||||||
.run()
|
.run()
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,9 @@ use iced::{Center, Element, Fill, Subscription, Task, color};
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::application("WebSocket - Iced", WebSocket::update, WebSocket::view)
|
iced::application(WebSocket::new, WebSocket::update, WebSocket::view)
|
||||||
.subscription(WebSocket::subscription)
|
.subscription(WebSocket::subscription)
|
||||||
.run_with(WebSocket::new)
|
.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
struct WebSocket {
|
struct WebSocket {
|
||||||
|
|
|
||||||
|
|
@ -73,13 +73,12 @@ pub trait Compositor: Sized {
|
||||||
///
|
///
|
||||||
/// [`Renderer`]: Self::Renderer
|
/// [`Renderer`]: Self::Renderer
|
||||||
/// [`Surface`]: Self::Surface
|
/// [`Surface`]: Self::Surface
|
||||||
fn present<T: AsRef<str>>(
|
fn present(
|
||||||
&mut self,
|
&mut self,
|
||||||
renderer: &mut Self::Renderer,
|
renderer: &mut Self::Renderer,
|
||||||
surface: &mut Self::Surface,
|
surface: &mut Self::Surface,
|
||||||
viewport: &Viewport,
|
viewport: &Viewport,
|
||||||
background_color: Color,
|
background_color: Color,
|
||||||
overlay: &[T],
|
|
||||||
on_pre_present: impl FnOnce(),
|
on_pre_present: impl FnOnce(),
|
||||||
) -> Result<(), SurfaceError>;
|
) -> Result<(), SurfaceError>;
|
||||||
|
|
||||||
|
|
@ -186,13 +185,12 @@ impl Compositor for () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn present<T: AsRef<str>>(
|
fn present(
|
||||||
&mut self,
|
&mut self,
|
||||||
_renderer: &mut Self::Renderer,
|
_renderer: &mut Self::Renderer,
|
||||||
_surface: &mut Self::Surface,
|
_surface: &mut Self::Surface,
|
||||||
_viewport: &Viewport,
|
_viewport: &Viewport,
|
||||||
_background_color: Color,
|
_background_color: Color,
|
||||||
_overlay: &[T],
|
|
||||||
_on_pre_present: impl FnOnce(),
|
_on_pre_present: impl FnOnce(),
|
||||||
) -> Result<(), SurfaceError> {
|
) -> Result<(), SurfaceError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::Antialiasing;
|
use crate::Antialiasing;
|
||||||
use crate::core::{Font, Pixels};
|
use crate::core::{self, Font, Pixels};
|
||||||
|
|
||||||
/// The settings of a renderer.
|
/// The settings of a renderer.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
|
@ -27,3 +27,13 @@ impl Default for Settings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<core::Settings> for Settings {
|
||||||
|
fn from(settings: core::Settings) -> Self {
|
||||||
|
Self {
|
||||||
|
default_font: settings.default_font,
|
||||||
|
default_text_size: settings.default_text_size,
|
||||||
|
antialiasing: settings.antialiasing.then_some(Antialiasing::MSAAx4),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
18
program/Cargo.toml
Normal file
18
program/Cargo.toml
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "iced_program"
|
||||||
|
version.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
homepage.workspace = true
|
||||||
|
categories.workspace = true
|
||||||
|
keywords.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
iced_graphics.workspace = true
|
||||||
|
iced_runtime.workspace = true
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
@ -1,14 +1,21 @@
|
||||||
use crate::core::text;
|
//! The definition of an iced program.
|
||||||
use crate::graphics::compositor;
|
pub use iced_graphics as graphics;
|
||||||
use crate::shell;
|
pub use iced_runtime as runtime;
|
||||||
use crate::theme;
|
pub use iced_runtime::core;
|
||||||
use crate::window;
|
pub use iced_runtime::futures;
|
||||||
use crate::{Element, Executor, Result, Settings, Subscription, Task};
|
|
||||||
|
|
||||||
/// The internal definition of a [`Program`].
|
use crate::core::Element;
|
||||||
|
use crate::core::text;
|
||||||
|
use crate::core::theme;
|
||||||
|
use crate::core::window;
|
||||||
|
use crate::futures::{Executor, Subscription};
|
||||||
|
use crate::graphics::compositor;
|
||||||
|
use crate::runtime::Task;
|
||||||
|
|
||||||
|
/// An interactive, native, cross-platform, multi-windowed application.
|
||||||
///
|
///
|
||||||
/// You should not need to implement this trait directly. Instead, use the
|
/// A [`Program`] can execute asynchronous actions by returning a
|
||||||
/// methods available in the [`Program`] struct.
|
/// [`Task`] in some of its methods.
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
pub trait Program: Sized {
|
pub trait Program: Sized {
|
||||||
/// The state of the program.
|
/// The state of the program.
|
||||||
|
|
@ -26,6 +33,11 @@ pub trait Program: Sized {
|
||||||
/// The executor of the program.
|
/// The executor of the program.
|
||||||
type Executor: Executor;
|
type Executor: Executor;
|
||||||
|
|
||||||
|
/// Returns the unique name of the [`Program`].
|
||||||
|
fn name() -> &'static str;
|
||||||
|
|
||||||
|
fn boot(&self) -> (Self::State, Task<Self::Message>);
|
||||||
|
|
||||||
fn update(
|
fn update(
|
||||||
&self,
|
&self,
|
||||||
state: &mut Self::State,
|
state: &mut Self::State,
|
||||||
|
|
@ -39,7 +51,32 @@ pub trait Program: Sized {
|
||||||
) -> Element<'a, Self::Message, Self::Theme, Self::Renderer>;
|
) -> Element<'a, Self::Message, Self::Theme, Self::Renderer>;
|
||||||
|
|
||||||
fn title(&self, _state: &Self::State, _window: window::Id) -> String {
|
fn title(&self, _state: &Self::State, _window: window::Id) -> String {
|
||||||
String::from("A cool iced application!")
|
let mut title = String::new();
|
||||||
|
|
||||||
|
for (i, part) in Self::name().split("_").enumerate() {
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
let part = match part {
|
||||||
|
"a" | "an" | "of" | "in" | "and" => Cow::Borrowed(part),
|
||||||
|
_ => {
|
||||||
|
let mut part = part.to_owned();
|
||||||
|
|
||||||
|
if let Some(first_letter) = part.get_mut(0..1) {
|
||||||
|
first_letter.make_ascii_uppercase();
|
||||||
|
}
|
||||||
|
|
||||||
|
Cow::Owned(part)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if i > 0 {
|
||||||
|
title.push(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
title.push_str(&part);
|
||||||
|
}
|
||||||
|
|
||||||
|
format!("{title} - Iced")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subscription(
|
fn subscription(
|
||||||
|
|
@ -60,138 +97,9 @@ pub trait Program: Sized {
|
||||||
fn scale_factor(&self, _state: &Self::State, _window: window::Id) -> f64 {
|
fn scale_factor(&self, _state: &Self::State, _window: window::Id) -> f64 {
|
||||||
1.0
|
1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs the [`Program`].
|
|
||||||
///
|
|
||||||
/// The state of the [`Program`] must implement [`Default`].
|
|
||||||
/// If your state does not implement [`Default`], use [`run_with`]
|
|
||||||
/// instead.
|
|
||||||
///
|
|
||||||
/// [`run_with`]: Self::run_with
|
|
||||||
fn run(
|
|
||||||
self,
|
|
||||||
settings: Settings,
|
|
||||||
window_settings: Option<window::Settings>,
|
|
||||||
) -> Result
|
|
||||||
where
|
|
||||||
Self: 'static,
|
|
||||||
Self::State: Default,
|
|
||||||
{
|
|
||||||
self.run_with(settings, window_settings, || {
|
|
||||||
(Self::State::default(), Task::none())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Runs the [`Program`] with the given [`Settings`] and a closure that creates the initial state.
|
|
||||||
fn run_with<I>(
|
|
||||||
self,
|
|
||||||
settings: Settings,
|
|
||||||
window_settings: Option<window::Settings>,
|
|
||||||
initialize: I,
|
|
||||||
) -> Result
|
|
||||||
where
|
|
||||||
Self: 'static,
|
|
||||||
I: FnOnce() -> (Self::State, Task<Self::Message>) + 'static,
|
|
||||||
{
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
|
|
||||||
struct Instance<P: Program, I> {
|
|
||||||
program: P,
|
|
||||||
state: P::State,
|
|
||||||
_initialize: PhantomData<I>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Program, I: FnOnce() -> (P::State, Task<P::Message>)>
|
|
||||||
shell::Program for Instance<P, I>
|
|
||||||
{
|
|
||||||
type Message = P::Message;
|
|
||||||
type Theme = P::Theme;
|
|
||||||
type Renderer = P::Renderer;
|
|
||||||
type Flags = (P, I);
|
|
||||||
type Executor = P::Executor;
|
|
||||||
|
|
||||||
fn new(
|
|
||||||
(program, initialize): Self::Flags,
|
|
||||||
) -> (Self, Task<Self::Message>) {
|
|
||||||
let (state, task) = initialize();
|
|
||||||
|
|
||||||
(
|
|
||||||
Self {
|
|
||||||
program,
|
|
||||||
state,
|
|
||||||
_initialize: PhantomData,
|
|
||||||
},
|
|
||||||
task,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn title(&self, window: window::Id) -> String {
|
|
||||||
self.program.title(&self.state, window)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(
|
|
||||||
&mut self,
|
|
||||||
message: Self::Message,
|
|
||||||
) -> Task<Self::Message> {
|
|
||||||
self.program.update(&mut self.state, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(
|
|
||||||
&self,
|
|
||||||
window: window::Id,
|
|
||||||
) -> crate::Element<'_, Self::Message, Self::Theme, Self::Renderer>
|
|
||||||
{
|
|
||||||
self.program.view(&self.state, window)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn subscription(&self) -> Subscription<Self::Message> {
|
|
||||||
self.program.subscription(&self.state)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn theme(&self, window: window::Id) -> Self::Theme {
|
|
||||||
self.program.theme(&self.state, window)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn style(&self, theme: &Self::Theme) -> theme::Style {
|
|
||||||
self.program.style(&self.state, theme)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scale_factor(&self, window: window::Id) -> f64 {
|
|
||||||
self.program.scale_factor(&self.state, window)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::needless_update)]
|
|
||||||
let renderer_settings = crate::graphics::Settings {
|
|
||||||
default_font: settings.default_font,
|
|
||||||
default_text_size: settings.default_text_size,
|
|
||||||
antialiasing: if settings.antialiasing {
|
|
||||||
Some(crate::graphics::Antialiasing::MSAAx4)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
..crate::graphics::Settings::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(shell::program::run::<
|
|
||||||
Instance<Self, I>,
|
|
||||||
<Self::Renderer as compositor::Default>::Compositor,
|
|
||||||
>(
|
|
||||||
Settings {
|
|
||||||
id: settings.id,
|
|
||||||
fonts: settings.fonts,
|
|
||||||
default_font: settings.default_font,
|
|
||||||
default_text_size: settings.default_text_size,
|
|
||||||
antialiasing: settings.antialiasing,
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
renderer_settings,
|
|
||||||
window_settings,
|
|
||||||
(self, initialize),
|
|
||||||
)?)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decorates a [`Program`] with the given title function.
|
||||||
pub fn with_title<P: Program>(
|
pub fn with_title<P: Program>(
|
||||||
program: P,
|
program: P,
|
||||||
title: impl Fn(&P::State, window::Id) -> String,
|
title: impl Fn(&P::State, window::Id) -> String,
|
||||||
|
|
@ -216,6 +124,14 @@ pub fn with_title<P: Program>(
|
||||||
(self.title)(state, window)
|
(self.title)(state, window)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn name() -> &'static str {
|
||||||
|
P::name()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
||||||
|
self.program.boot()
|
||||||
|
}
|
||||||
|
|
||||||
fn update(
|
fn update(
|
||||||
&self,
|
&self,
|
||||||
state: &mut Self::State,
|
state: &mut Self::State,
|
||||||
|
|
@ -263,6 +179,7 @@ pub fn with_title<P: Program>(
|
||||||
WithTitle { program, title }
|
WithTitle { program, title }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decorates a [`Program`] with the given subscription function.
|
||||||
pub fn with_subscription<P: Program>(
|
pub fn with_subscription<P: Program>(
|
||||||
program: P,
|
program: P,
|
||||||
f: impl Fn(&P::State) -> Subscription<P::Message>,
|
f: impl Fn(&P::State) -> Subscription<P::Message>,
|
||||||
|
|
@ -289,6 +206,14 @@ pub fn with_subscription<P: Program>(
|
||||||
(self.subscription)(state)
|
(self.subscription)(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn name() -> &'static str {
|
||||||
|
P::name()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
||||||
|
self.program.boot()
|
||||||
|
}
|
||||||
|
|
||||||
fn update(
|
fn update(
|
||||||
&self,
|
&self,
|
||||||
state: &mut Self::State,
|
state: &mut Self::State,
|
||||||
|
|
@ -336,6 +261,7 @@ pub fn with_subscription<P: Program>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decorates a [`Program`] with the given theme function.
|
||||||
pub fn with_theme<P: Program>(
|
pub fn with_theme<P: Program>(
|
||||||
program: P,
|
program: P,
|
||||||
f: impl Fn(&P::State, window::Id) -> P::Theme,
|
f: impl Fn(&P::State, window::Id) -> P::Theme,
|
||||||
|
|
@ -363,6 +289,14 @@ pub fn with_theme<P: Program>(
|
||||||
(self.theme)(state, window)
|
(self.theme)(state, window)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn name() -> &'static str {
|
||||||
|
P::name()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
||||||
|
self.program.boot()
|
||||||
|
}
|
||||||
|
|
||||||
fn title(&self, state: &Self::State, window: window::Id) -> String {
|
fn title(&self, state: &Self::State, window: window::Id) -> String {
|
||||||
self.program.title(state, window)
|
self.program.title(state, window)
|
||||||
}
|
}
|
||||||
|
|
@ -406,6 +340,7 @@ pub fn with_theme<P: Program>(
|
||||||
WithTheme { program, theme: f }
|
WithTheme { program, theme: f }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decorates a [`Program`] with the given style function.
|
||||||
pub fn with_style<P: Program>(
|
pub fn with_style<P: Program>(
|
||||||
program: P,
|
program: P,
|
||||||
f: impl Fn(&P::State, &P::Theme) -> theme::Style,
|
f: impl Fn(&P::State, &P::Theme) -> theme::Style,
|
||||||
|
|
@ -433,6 +368,14 @@ pub fn with_style<P: Program>(
|
||||||
(self.style)(state, theme)
|
(self.style)(state, theme)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn name() -> &'static str {
|
||||||
|
P::name()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
||||||
|
self.program.boot()
|
||||||
|
}
|
||||||
|
|
||||||
fn title(&self, state: &Self::State, window: window::Id) -> String {
|
fn title(&self, state: &Self::State, window: window::Id) -> String {
|
||||||
self.program.title(state, window)
|
self.program.title(state, window)
|
||||||
}
|
}
|
||||||
|
|
@ -476,6 +419,7 @@ pub fn with_style<P: Program>(
|
||||||
WithStyle { program, style: f }
|
WithStyle { program, style: f }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decorates a [`Program`] with the given scale factor function.
|
||||||
pub fn with_scale_factor<P: Program>(
|
pub fn with_scale_factor<P: Program>(
|
||||||
program: P,
|
program: P,
|
||||||
f: impl Fn(&P::State, window::Id) -> f64,
|
f: impl Fn(&P::State, window::Id) -> f64,
|
||||||
|
|
@ -499,6 +443,14 @@ pub fn with_scale_factor<P: Program>(
|
||||||
self.program.title(state, window)
|
self.program.title(state, window)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn name() -> &'static str {
|
||||||
|
P::name()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
||||||
|
self.program.boot()
|
||||||
|
}
|
||||||
|
|
||||||
fn update(
|
fn update(
|
||||||
&self,
|
&self,
|
||||||
state: &mut Self::State,
|
state: &mut Self::State,
|
||||||
|
|
@ -549,6 +501,7 @@ pub fn with_scale_factor<P: Program>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decorates a [`Program`] with the given executor function.
|
||||||
pub fn with_executor<P: Program, E: Executor>(
|
pub fn with_executor<P: Program, E: Executor>(
|
||||||
program: P,
|
program: P,
|
||||||
) -> impl Program<State = P::State, Message = P::Message, Theme = P::Theme> {
|
) -> impl Program<State = P::State, Message = P::Message, Theme = P::Theme> {
|
||||||
|
|
@ -573,6 +526,14 @@ pub fn with_executor<P: Program, E: Executor>(
|
||||||
self.program.title(state, window)
|
self.program.title(state, window)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn name() -> &'static str {
|
||||||
|
P::name()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
||||||
|
self.program.boot()
|
||||||
|
}
|
||||||
|
|
||||||
fn update(
|
fn update(
|
||||||
&self,
|
&self,
|
||||||
state: &mut Self::State,
|
state: &mut Self::State,
|
||||||
|
|
@ -627,3 +588,57 @@ pub fn with_executor<P: Program, E: Executor>(
|
||||||
pub trait Renderer: text::Renderer + compositor::Default {}
|
pub trait Renderer: text::Renderer + compositor::Default {}
|
||||||
|
|
||||||
impl<T> Renderer for T where T: text::Renderer + compositor::Default {}
|
impl<T> Renderer for T where T: text::Renderer + compositor::Default {}
|
||||||
|
|
||||||
|
/// A particular instance of a running [`Program`].
|
||||||
|
#[allow(missing_debug_implementations)]
|
||||||
|
pub struct Instance<P: Program> {
|
||||||
|
program: P,
|
||||||
|
state: P::State,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: Program> Instance<P> {
|
||||||
|
/// Creates a new [`Instance`] of the given [`Program`].
|
||||||
|
pub fn new(program: P) -> (Self, Task<P::Message>) {
|
||||||
|
let (state, task) = program.boot();
|
||||||
|
|
||||||
|
(Self { program, state }, task)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current title of the [`Instance`].
|
||||||
|
pub fn title(&self, window: window::Id) -> String {
|
||||||
|
self.program.title(&self.state, window)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Processes the given message and updates the [`Instance`].
|
||||||
|
pub fn update(&mut self, message: P::Message) -> Task<P::Message> {
|
||||||
|
self.program.update(&mut self.state, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Produces the current widget tree of the [`Instance`].
|
||||||
|
pub fn view(
|
||||||
|
&self,
|
||||||
|
window: window::Id,
|
||||||
|
) -> Element<'_, P::Message, P::Theme, P::Renderer> {
|
||||||
|
self.program.view(&self.state, window)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current [`Subscription`] of the [`Instance`].
|
||||||
|
pub fn subscription(&self) -> Subscription<P::Message> {
|
||||||
|
self.program.subscription(&self.state)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current theme of the [`Instance`].
|
||||||
|
pub fn theme(&self, window: window::Id) -> P::Theme {
|
||||||
|
self.program.theme(&self.state, window)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current [`theme::Style`] of the [`Instance`].
|
||||||
|
pub fn style(&self, theme: &P::Theme) -> theme::Style {
|
||||||
|
self.program.style(&self.state, theme)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current scale factor of the [`Instance`].
|
||||||
|
pub fn scale_factor(&self, window: window::Id) -> f64 {
|
||||||
|
self.program.scale_factor(&self.state, window)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -316,13 +316,12 @@ where
|
||||||
delegate!(self, compositor, compositor.fetch_information())
|
delegate!(self, compositor, compositor.fetch_information())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn present<T: AsRef<str>>(
|
fn present(
|
||||||
&mut self,
|
&mut self,
|
||||||
renderer: &mut Self::Renderer,
|
renderer: &mut Self::Renderer,
|
||||||
surface: &mut Self::Surface,
|
surface: &mut Self::Surface,
|
||||||
viewport: &graphics::Viewport,
|
viewport: &graphics::Viewport,
|
||||||
background_color: Color,
|
background_color: Color,
|
||||||
overlay: &[T],
|
|
||||||
on_pre_present: impl FnOnce(),
|
on_pre_present: impl FnOnce(),
|
||||||
) -> Result<(), compositor::SurfaceError> {
|
) -> Result<(), compositor::SurfaceError> {
|
||||||
match (self, renderer, surface) {
|
match (self, renderer, surface) {
|
||||||
|
|
@ -335,7 +334,6 @@ where
|
||||||
surface,
|
surface,
|
||||||
viewport,
|
viewport,
|
||||||
background_color,
|
background_color,
|
||||||
overlay,
|
|
||||||
on_pre_present,
|
on_pre_present,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
|
@ -347,7 +345,6 @@ where
|
||||||
surface,
|
surface,
|
||||||
viewport,
|
viewport,
|
||||||
background_color,
|
background_color,
|
||||||
overlay,
|
|
||||||
on_pre_present,
|
on_pre_present,
|
||||||
),
|
),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,11 @@ keywords.workspace = true
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[features]
|
|
||||||
debug = []
|
|
||||||
multi-window = []
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bytes.workspace = true
|
bytes.workspace = true
|
||||||
iced_core.workspace = true
|
iced_core.workspace = true
|
||||||
|
iced_debug.workspace = true
|
||||||
|
|
||||||
iced_futures.workspace = true
|
iced_futures.workspace = true
|
||||||
|
|
||||||
raw-window-handle.workspace = true
|
raw-window-handle.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -1,220 +0,0 @@
|
||||||
#![allow(missing_docs)]
|
|
||||||
use crate::core::time;
|
|
||||||
|
|
||||||
use std::collections::VecDeque;
|
|
||||||
|
|
||||||
/// A bunch of time measurements for debugging purposes.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Debug {
|
|
||||||
is_enabled: bool,
|
|
||||||
|
|
||||||
startup_start: time::Instant,
|
|
||||||
startup_duration: time::Duration,
|
|
||||||
|
|
||||||
update_start: time::Instant,
|
|
||||||
update_durations: TimeBuffer,
|
|
||||||
|
|
||||||
view_start: time::Instant,
|
|
||||||
view_durations: TimeBuffer,
|
|
||||||
|
|
||||||
layout_start: time::Instant,
|
|
||||||
layout_durations: TimeBuffer,
|
|
||||||
|
|
||||||
event_start: time::Instant,
|
|
||||||
event_durations: TimeBuffer,
|
|
||||||
|
|
||||||
draw_start: time::Instant,
|
|
||||||
draw_durations: TimeBuffer,
|
|
||||||
|
|
||||||
render_start: time::Instant,
|
|
||||||
render_durations: TimeBuffer,
|
|
||||||
|
|
||||||
message_count: usize,
|
|
||||||
last_messages: VecDeque<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug {
|
|
||||||
/// Creates a new [`struct@Debug`].
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let now = time::Instant::now();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
is_enabled: false,
|
|
||||||
startup_start: now,
|
|
||||||
startup_duration: time::Duration::from_secs(0),
|
|
||||||
|
|
||||||
update_start: now,
|
|
||||||
update_durations: TimeBuffer::new(200),
|
|
||||||
|
|
||||||
view_start: now,
|
|
||||||
view_durations: TimeBuffer::new(200),
|
|
||||||
|
|
||||||
layout_start: now,
|
|
||||||
layout_durations: TimeBuffer::new(200),
|
|
||||||
|
|
||||||
event_start: now,
|
|
||||||
event_durations: TimeBuffer::new(200),
|
|
||||||
|
|
||||||
draw_start: now,
|
|
||||||
draw_durations: TimeBuffer::new(200),
|
|
||||||
|
|
||||||
render_start: now,
|
|
||||||
render_durations: TimeBuffer::new(50),
|
|
||||||
|
|
||||||
message_count: 0,
|
|
||||||
last_messages: VecDeque::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn toggle(&mut self) {
|
|
||||||
self.is_enabled = !self.is_enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn startup_started(&mut self) {
|
|
||||||
self.startup_start = time::Instant::now();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn startup_finished(&mut self) {
|
|
||||||
self.startup_duration = self.startup_start.elapsed();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_started(&mut self) {
|
|
||||||
self.update_start = time::Instant::now();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_finished(&mut self) {
|
|
||||||
self.update_durations.push(self.update_start.elapsed());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_started(&mut self) {
|
|
||||||
self.view_start = time::Instant::now();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_finished(&mut self) {
|
|
||||||
self.view_durations.push(self.view_start.elapsed());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn layout_started(&mut self) {
|
|
||||||
self.layout_start = time::Instant::now();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn layout_finished(&mut self) {
|
|
||||||
self.layout_durations.push(self.layout_start.elapsed());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn event_processing_started(&mut self) {
|
|
||||||
self.event_start = time::Instant::now();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn event_processing_finished(&mut self) {
|
|
||||||
self.event_durations.push(self.event_start.elapsed());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw_started(&mut self) {
|
|
||||||
self.draw_start = time::Instant::now();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw_finished(&mut self) {
|
|
||||||
self.draw_durations.push(self.draw_start.elapsed());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render_started(&mut self) {
|
|
||||||
self.render_start = time::Instant::now();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render_finished(&mut self) {
|
|
||||||
self.render_durations.push(self.render_start.elapsed());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn log_message<Message: std::fmt::Debug>(&mut self, message: &Message) {
|
|
||||||
self.last_messages.push_back(format!("{message:?}"));
|
|
||||||
|
|
||||||
if self.last_messages.len() > 10 {
|
|
||||||
let _ = self.last_messages.pop_front();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.message_count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn overlay(&self) -> Vec<String> {
|
|
||||||
if !self.is_enabled {
|
|
||||||
return Vec::new();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut lines = Vec::new();
|
|
||||||
|
|
||||||
fn key_value<T: std::fmt::Debug>(key: &str, value: T) -> String {
|
|
||||||
format!("{key} {value:?}")
|
|
||||||
}
|
|
||||||
|
|
||||||
lines.push(format!(
|
|
||||||
"{} {} - {}",
|
|
||||||
env!("CARGO_PKG_NAME"),
|
|
||||||
env!("CARGO_PKG_VERSION"),
|
|
||||||
env!("CARGO_PKG_REPOSITORY"),
|
|
||||||
));
|
|
||||||
lines.push(key_value("Startup:", self.startup_duration));
|
|
||||||
lines.push(key_value("Update:", self.update_durations.average()));
|
|
||||||
lines.push(key_value("View:", self.view_durations.average()));
|
|
||||||
lines.push(key_value("Layout:", self.layout_durations.average()));
|
|
||||||
lines.push(key_value(
|
|
||||||
"Event processing:",
|
|
||||||
self.event_durations.average(),
|
|
||||||
));
|
|
||||||
lines.push(key_value(
|
|
||||||
"Primitive generation:",
|
|
||||||
self.draw_durations.average(),
|
|
||||||
));
|
|
||||||
lines.push(key_value("Render:", self.render_durations.average()));
|
|
||||||
lines.push(key_value("Message count:", self.message_count));
|
|
||||||
lines.push(String::from("Last messages:"));
|
|
||||||
lines.extend(self.last_messages.iter().map(|msg| {
|
|
||||||
if msg.len() <= 100 {
|
|
||||||
format!(" {msg}")
|
|
||||||
} else {
|
|
||||||
format!(" {msg:.100}...")
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
lines
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Debug {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct TimeBuffer {
|
|
||||||
head: usize,
|
|
||||||
size: usize,
|
|
||||||
contents: Vec<time::Duration>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TimeBuffer {
|
|
||||||
fn new(capacity: usize) -> TimeBuffer {
|
|
||||||
TimeBuffer {
|
|
||||||
head: 0,
|
|
||||||
size: 0,
|
|
||||||
contents: vec![time::Duration::from_secs(0); capacity],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push(&mut self, duration: time::Duration) {
|
|
||||||
self.head = (self.head + 1) % self.contents.len();
|
|
||||||
self.contents[self.head] = duration;
|
|
||||||
self.size = (self.size + 1).min(self.contents.len());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn average(&self) -> time::Duration {
|
|
||||||
let sum: time::Duration = if self.size == self.contents.len() {
|
|
||||||
self.contents[..].iter().sum()
|
|
||||||
} else {
|
|
||||||
self.contents[..self.size].iter().sum()
|
|
||||||
};
|
|
||||||
|
|
||||||
sum / self.size.max(1) as u32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
#![allow(missing_docs)]
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct Debug;
|
|
||||||
|
|
||||||
impl Debug {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn startup_started(&mut self) {}
|
|
||||||
|
|
||||||
pub fn startup_finished(&mut self) {}
|
|
||||||
|
|
||||||
pub fn update_started(&mut self) {}
|
|
||||||
|
|
||||||
pub fn update_finished(&mut self) {}
|
|
||||||
|
|
||||||
pub fn view_started(&mut self) {}
|
|
||||||
|
|
||||||
pub fn view_finished(&mut self) {}
|
|
||||||
|
|
||||||
pub fn layout_started(&mut self) {}
|
|
||||||
|
|
||||||
pub fn layout_finished(&mut self) {}
|
|
||||||
|
|
||||||
pub fn event_processing_started(&mut self) {}
|
|
||||||
|
|
||||||
pub fn event_processing_finished(&mut self) {}
|
|
||||||
|
|
||||||
pub fn draw_started(&mut self) {}
|
|
||||||
|
|
||||||
pub fn draw_finished(&mut self) {}
|
|
||||||
|
|
||||||
pub fn render_started(&mut self) {}
|
|
||||||
|
|
||||||
pub fn render_finished(&mut self) {}
|
|
||||||
|
|
||||||
pub fn log_message<Message: std::fmt::Debug>(
|
|
||||||
&mut self,
|
|
||||||
_message: &Message,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn overlay(&self) -> Vec<String> {
|
|
||||||
Vec::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -13,29 +13,15 @@ pub mod clipboard;
|
||||||
pub mod font;
|
pub mod font;
|
||||||
pub mod keyboard;
|
pub mod keyboard;
|
||||||
pub mod overlay;
|
pub mod overlay;
|
||||||
pub mod program;
|
|
||||||
pub mod system;
|
pub mod system;
|
||||||
pub mod task;
|
pub mod task;
|
||||||
pub mod user_interface;
|
pub mod user_interface;
|
||||||
pub mod window;
|
pub mod window;
|
||||||
|
|
||||||
#[cfg(feature = "multi-window")]
|
|
||||||
pub mod multi_window;
|
|
||||||
|
|
||||||
// We disable debug capabilities on release builds unless the `debug` feature
|
|
||||||
// is explicitly enabled.
|
|
||||||
#[cfg(feature = "debug")]
|
|
||||||
#[path = "debug/basic.rs"]
|
|
||||||
mod debug;
|
|
||||||
#[cfg(not(feature = "debug"))]
|
|
||||||
#[path = "debug/null.rs"]
|
|
||||||
mod debug;
|
|
||||||
|
|
||||||
pub use iced_core as core;
|
pub use iced_core as core;
|
||||||
|
pub use iced_debug as debug;
|
||||||
pub use iced_futures as futures;
|
pub use iced_futures as futures;
|
||||||
|
|
||||||
pub use debug::Debug;
|
|
||||||
pub use program::Program;
|
|
||||||
pub use task::Task;
|
pub use task::Task;
|
||||||
pub use user_interface::UserInterface;
|
pub use user_interface::UserInterface;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
//! A multi-window application.
|
|
||||||
pub mod program;
|
|
||||||
pub mod state;
|
|
||||||
|
|
||||||
pub use program::Program;
|
|
||||||
pub use state::State;
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
//! Build interactive programs using The Elm Architecture.
|
|
||||||
use crate::Task;
|
|
||||||
use crate::core::text;
|
|
||||||
use crate::core::window;
|
|
||||||
use crate::core::{Element, Renderer};
|
|
||||||
|
|
||||||
/// The core of a user interface for a multi-window application following The Elm Architecture.
|
|
||||||
pub trait Program: Sized {
|
|
||||||
/// The graphics backend to use to draw the [`Program`].
|
|
||||||
type Renderer: Renderer + text::Renderer;
|
|
||||||
|
|
||||||
/// The type of __messages__ your [`Program`] will produce.
|
|
||||||
type Message: std::fmt::Debug + Send;
|
|
||||||
|
|
||||||
/// The theme used to draw the [`Program`].
|
|
||||||
type Theme;
|
|
||||||
|
|
||||||
/// Handles a __message__ and updates the state of the [`Program`].
|
|
||||||
///
|
|
||||||
/// This is where you define your __update logic__. All the __messages__,
|
|
||||||
/// produced by either user interactions or commands, will be handled by
|
|
||||||
/// this method.
|
|
||||||
///
|
|
||||||
/// Any [`Task`] returned will be executed immediately in the background by the
|
|
||||||
/// runtime.
|
|
||||||
fn update(&mut self, message: Self::Message) -> Task<Self::Message>;
|
|
||||||
|
|
||||||
/// Returns the widgets to display in the [`Program`] for the `window`.
|
|
||||||
///
|
|
||||||
/// These widgets can produce __messages__ based on user interaction.
|
|
||||||
fn view(
|
|
||||||
&self,
|
|
||||||
window: window::Id,
|
|
||||||
) -> Element<'_, Self::Message, Self::Theme, Self::Renderer>;
|
|
||||||
}
|
|
||||||
|
|
@ -1,278 +0,0 @@
|
||||||
//! The internal state of a multi-window [`Program`].
|
|
||||||
use crate::core::event::{self, Event};
|
|
||||||
use crate::core::mouse;
|
|
||||||
use crate::core::renderer;
|
|
||||||
use crate::core::widget::operation::{self, Operation};
|
|
||||||
use crate::core::{Clipboard, Size};
|
|
||||||
use crate::user_interface::{self, UserInterface};
|
|
||||||
use crate::{Debug, Program, Task};
|
|
||||||
|
|
||||||
/// The execution state of a multi-window [`Program`]. It leverages caching, event
|
|
||||||
/// processing, and rendering primitive storage.
|
|
||||||
#[allow(missing_debug_implementations)]
|
|
||||||
pub struct State<P>
|
|
||||||
where
|
|
||||||
P: Program + 'static,
|
|
||||||
{
|
|
||||||
program: P,
|
|
||||||
caches: Option<Vec<user_interface::Cache>>,
|
|
||||||
queued_events: Vec<Event>,
|
|
||||||
queued_messages: Vec<P::Message>,
|
|
||||||
mouse_interaction: mouse::Interaction,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P> State<P>
|
|
||||||
where
|
|
||||||
P: Program + 'static,
|
|
||||||
{
|
|
||||||
/// Creates a new [`State`] with the provided [`Program`], initializing its
|
|
||||||
/// primitive with the given logical bounds and renderer.
|
|
||||||
pub fn new(
|
|
||||||
program: P,
|
|
||||||
bounds: Size,
|
|
||||||
renderer: &mut P::Renderer,
|
|
||||||
debug: &mut Debug,
|
|
||||||
) -> Self {
|
|
||||||
let user_interface = build_user_interface(
|
|
||||||
&program,
|
|
||||||
user_interface::Cache::default(),
|
|
||||||
renderer,
|
|
||||||
bounds,
|
|
||||||
debug,
|
|
||||||
);
|
|
||||||
|
|
||||||
let caches = Some(vec![user_interface.into_cache()]);
|
|
||||||
|
|
||||||
State {
|
|
||||||
program,
|
|
||||||
caches,
|
|
||||||
queued_events: Vec::new(),
|
|
||||||
queued_messages: Vec::new(),
|
|
||||||
mouse_interaction: mouse::Interaction::None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a reference to the [`Program`] of the [`State`].
|
|
||||||
pub fn program(&self) -> &P {
|
|
||||||
&self.program
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Queues an event in the [`State`] for processing during an [`update`].
|
|
||||||
///
|
|
||||||
/// [`update`]: Self::update
|
|
||||||
pub fn queue_event(&mut self, event: Event) {
|
|
||||||
self.queued_events.push(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Queues a message in the [`State`] for processing during an [`update`].
|
|
||||||
///
|
|
||||||
/// [`update`]: Self::update
|
|
||||||
pub fn queue_message(&mut self, message: P::Message) {
|
|
||||||
self.queued_messages.push(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns whether the event queue of the [`State`] is empty or not.
|
|
||||||
pub fn is_queue_empty(&self) -> bool {
|
|
||||||
self.queued_events.is_empty() && self.queued_messages.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the current [`mouse::Interaction`] of the [`State`].
|
|
||||||
pub fn mouse_interaction(&self) -> mouse::Interaction {
|
|
||||||
self.mouse_interaction
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Processes all the queued events and messages, rebuilding and redrawing
|
|
||||||
/// the widgets of the linked [`Program`] if necessary.
|
|
||||||
///
|
|
||||||
/// Returns a list containing the instances of [`Event`] that were not
|
|
||||||
/// captured by any widget, and the [`Task`] obtained from [`Program`]
|
|
||||||
/// after updating it, only if an update was necessary.
|
|
||||||
pub fn update(
|
|
||||||
&mut self,
|
|
||||||
bounds: Size,
|
|
||||||
cursor: mouse::Cursor,
|
|
||||||
renderer: &mut P::Renderer,
|
|
||||||
theme: &P::Theme,
|
|
||||||
style: &renderer::Style,
|
|
||||||
clipboard: &mut dyn Clipboard,
|
|
||||||
debug: &mut Debug,
|
|
||||||
) -> (Vec<Event>, Option<Task<P::Message>>) {
|
|
||||||
let mut user_interfaces = build_user_interfaces(
|
|
||||||
&self.program,
|
|
||||||
self.caches.take().unwrap(),
|
|
||||||
renderer,
|
|
||||||
bounds,
|
|
||||||
debug,
|
|
||||||
);
|
|
||||||
|
|
||||||
debug.event_processing_started();
|
|
||||||
let mut messages = Vec::new();
|
|
||||||
|
|
||||||
let uncaptured_events = user_interfaces.iter_mut().fold(
|
|
||||||
vec![],
|
|
||||||
|mut uncaptured_events, ui| {
|
|
||||||
let (_, event_statuses) = ui.update(
|
|
||||||
&self.queued_events,
|
|
||||||
cursor,
|
|
||||||
renderer,
|
|
||||||
clipboard,
|
|
||||||
&mut messages,
|
|
||||||
);
|
|
||||||
|
|
||||||
uncaptured_events.extend(
|
|
||||||
self.queued_events
|
|
||||||
.iter()
|
|
||||||
.zip(event_statuses)
|
|
||||||
.filter_map(|(event, status)| {
|
|
||||||
matches!(status, event::Status::Ignored)
|
|
||||||
.then_some(event)
|
|
||||||
})
|
|
||||||
.cloned(),
|
|
||||||
);
|
|
||||||
uncaptured_events
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
self.queued_events.clear();
|
|
||||||
messages.append(&mut self.queued_messages);
|
|
||||||
debug.event_processing_finished();
|
|
||||||
|
|
||||||
let commands = if messages.is_empty() {
|
|
||||||
debug.draw_started();
|
|
||||||
|
|
||||||
for ui in &mut user_interfaces {
|
|
||||||
self.mouse_interaction =
|
|
||||||
ui.draw(renderer, theme, style, cursor);
|
|
||||||
}
|
|
||||||
|
|
||||||
debug.draw_finished();
|
|
||||||
|
|
||||||
self.caches = Some(
|
|
||||||
user_interfaces
|
|
||||||
.drain(..)
|
|
||||||
.map(UserInterface::into_cache)
|
|
||||||
.collect(),
|
|
||||||
);
|
|
||||||
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
let temp_caches = user_interfaces
|
|
||||||
.drain(..)
|
|
||||||
.map(UserInterface::into_cache)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
drop(user_interfaces);
|
|
||||||
|
|
||||||
let commands = Task::batch(messages.into_iter().map(|msg| {
|
|
||||||
debug.log_message(&msg);
|
|
||||||
|
|
||||||
debug.update_started();
|
|
||||||
let task = self.program.update(msg);
|
|
||||||
debug.update_finished();
|
|
||||||
|
|
||||||
task
|
|
||||||
}));
|
|
||||||
|
|
||||||
let mut user_interfaces = build_user_interfaces(
|
|
||||||
&self.program,
|
|
||||||
temp_caches,
|
|
||||||
renderer,
|
|
||||||
bounds,
|
|
||||||
debug,
|
|
||||||
);
|
|
||||||
|
|
||||||
debug.draw_started();
|
|
||||||
for ui in &mut user_interfaces {
|
|
||||||
self.mouse_interaction =
|
|
||||||
ui.draw(renderer, theme, style, cursor);
|
|
||||||
}
|
|
||||||
debug.draw_finished();
|
|
||||||
|
|
||||||
self.caches = Some(
|
|
||||||
user_interfaces
|
|
||||||
.drain(..)
|
|
||||||
.map(UserInterface::into_cache)
|
|
||||||
.collect(),
|
|
||||||
);
|
|
||||||
|
|
||||||
Some(commands)
|
|
||||||
};
|
|
||||||
|
|
||||||
(uncaptured_events, commands)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Applies widget [`Operation`]s to the [`State`].
|
|
||||||
pub fn operate(
|
|
||||||
&mut self,
|
|
||||||
renderer: &mut P::Renderer,
|
|
||||||
operations: impl Iterator<Item = Box<dyn Operation>>,
|
|
||||||
bounds: Size,
|
|
||||||
debug: &mut Debug,
|
|
||||||
) {
|
|
||||||
let mut user_interfaces = build_user_interfaces(
|
|
||||||
&self.program,
|
|
||||||
self.caches.take().unwrap(),
|
|
||||||
renderer,
|
|
||||||
bounds,
|
|
||||||
debug,
|
|
||||||
);
|
|
||||||
|
|
||||||
for operation in operations {
|
|
||||||
let mut current_operation = Some(operation);
|
|
||||||
|
|
||||||
while let Some(mut operation) = current_operation.take() {
|
|
||||||
for ui in &mut user_interfaces {
|
|
||||||
ui.operate(renderer, operation.as_mut());
|
|
||||||
}
|
|
||||||
|
|
||||||
match operation.finish() {
|
|
||||||
operation::Outcome::None => {}
|
|
||||||
operation::Outcome::Some(()) => {}
|
|
||||||
operation::Outcome::Chain(next) => {
|
|
||||||
current_operation = Some(next);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.caches = Some(
|
|
||||||
user_interfaces
|
|
||||||
.drain(..)
|
|
||||||
.map(UserInterface::into_cache)
|
|
||||||
.collect(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_user_interfaces<'a, P: Program>(
|
|
||||||
program: &'a P,
|
|
||||||
mut caches: Vec<user_interface::Cache>,
|
|
||||||
renderer: &mut P::Renderer,
|
|
||||||
size: Size,
|
|
||||||
debug: &mut Debug,
|
|
||||||
) -> Vec<UserInterface<'a, P::Message, P::Theme, P::Renderer>> {
|
|
||||||
caches
|
|
||||||
.drain(..)
|
|
||||||
.map(|cache| {
|
|
||||||
build_user_interface(program, cache, renderer, size, debug)
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_user_interface<'a, P: Program>(
|
|
||||||
program: &'a P,
|
|
||||||
cache: user_interface::Cache,
|
|
||||||
renderer: &mut P::Renderer,
|
|
||||||
size: Size,
|
|
||||||
debug: &mut Debug,
|
|
||||||
) -> UserInterface<'a, P::Message, P::Theme, P::Renderer> {
|
|
||||||
debug.view_started();
|
|
||||||
let view = program.view();
|
|
||||||
debug.view_finished();
|
|
||||||
|
|
||||||
debug.layout_started();
|
|
||||||
let user_interface = UserInterface::build(view, size, cache, renderer);
|
|
||||||
debug.layout_finished();
|
|
||||||
|
|
||||||
user_interface
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
//! Build interactive programs using The Elm Architecture.
|
|
||||||
use crate::Task;
|
|
||||||
|
|
||||||
use iced_core::Element;
|
|
||||||
use iced_core::text;
|
|
||||||
|
|
||||||
mod state;
|
|
||||||
|
|
||||||
pub use state::State;
|
|
||||||
|
|
||||||
/// The core of a user interface application following The Elm Architecture.
|
|
||||||
pub trait Program: Sized {
|
|
||||||
/// The graphics backend to use to draw the [`Program`].
|
|
||||||
type Renderer: text::Renderer;
|
|
||||||
|
|
||||||
/// The theme used to draw the [`Program`].
|
|
||||||
type Theme;
|
|
||||||
|
|
||||||
/// The type of __messages__ your [`Program`] will produce.
|
|
||||||
type Message: std::fmt::Debug + Send;
|
|
||||||
|
|
||||||
/// Handles a __message__ and updates the state of the [`Program`].
|
|
||||||
///
|
|
||||||
/// This is where you define your __update logic__. All the __messages__,
|
|
||||||
/// produced by either user interactions or commands, will be handled by
|
|
||||||
/// this method.
|
|
||||||
///
|
|
||||||
/// Any [`Task`] returned will be executed immediately in the
|
|
||||||
/// background by shells.
|
|
||||||
fn update(&mut self, message: Self::Message) -> Task<Self::Message>;
|
|
||||||
|
|
||||||
/// Returns the widgets to display in the [`Program`].
|
|
||||||
///
|
|
||||||
/// These widgets can produce __messages__ based on user interaction.
|
|
||||||
fn view(&self) -> Element<'_, Self::Message, Self::Theme, Self::Renderer>;
|
|
||||||
}
|
|
||||||
|
|
@ -1,229 +0,0 @@
|
||||||
use crate::core::event::{self, Event};
|
|
||||||
use crate::core::mouse;
|
|
||||||
use crate::core::renderer;
|
|
||||||
use crate::core::widget::operation::{self, Operation};
|
|
||||||
use crate::core::{Clipboard, Size};
|
|
||||||
use crate::user_interface::{self, UserInterface};
|
|
||||||
use crate::{Debug, Program, Task};
|
|
||||||
|
|
||||||
/// The execution state of a [`Program`]. It leverages caching, event
|
|
||||||
/// processing, and rendering primitive storage.
|
|
||||||
#[allow(missing_debug_implementations)]
|
|
||||||
pub struct State<P>
|
|
||||||
where
|
|
||||||
P: Program + 'static,
|
|
||||||
{
|
|
||||||
program: P,
|
|
||||||
cache: Option<user_interface::Cache>,
|
|
||||||
queued_events: Vec<Event>,
|
|
||||||
queued_messages: Vec<P::Message>,
|
|
||||||
mouse_interaction: mouse::Interaction,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P> State<P>
|
|
||||||
where
|
|
||||||
P: Program + 'static,
|
|
||||||
{
|
|
||||||
/// Creates a new [`State`] with the provided [`Program`], initializing its
|
|
||||||
/// primitive with the given logical bounds and renderer.
|
|
||||||
pub fn new(
|
|
||||||
mut program: P,
|
|
||||||
bounds: Size,
|
|
||||||
renderer: &mut P::Renderer,
|
|
||||||
debug: &mut Debug,
|
|
||||||
) -> Self {
|
|
||||||
let user_interface = build_user_interface(
|
|
||||||
&mut program,
|
|
||||||
user_interface::Cache::default(),
|
|
||||||
renderer,
|
|
||||||
bounds,
|
|
||||||
debug,
|
|
||||||
);
|
|
||||||
|
|
||||||
let cache = Some(user_interface.into_cache());
|
|
||||||
|
|
||||||
State {
|
|
||||||
program,
|
|
||||||
cache,
|
|
||||||
queued_events: Vec::new(),
|
|
||||||
queued_messages: Vec::new(),
|
|
||||||
mouse_interaction: mouse::Interaction::None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a reference to the [`Program`] of the [`State`].
|
|
||||||
pub fn program(&self) -> &P {
|
|
||||||
&self.program
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Queues an event in the [`State`] for processing during an [`update`].
|
|
||||||
///
|
|
||||||
/// [`update`]: Self::update
|
|
||||||
pub fn queue_event(&mut self, event: Event) {
|
|
||||||
self.queued_events.push(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Queues a message in the [`State`] for processing during an [`update`].
|
|
||||||
///
|
|
||||||
/// [`update`]: Self::update
|
|
||||||
pub fn queue_message(&mut self, message: P::Message) {
|
|
||||||
self.queued_messages.push(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns whether the event queue of the [`State`] is empty or not.
|
|
||||||
pub fn is_queue_empty(&self) -> bool {
|
|
||||||
self.queued_events.is_empty() && self.queued_messages.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the current [`mouse::Interaction`] of the [`State`].
|
|
||||||
pub fn mouse_interaction(&self) -> mouse::Interaction {
|
|
||||||
self.mouse_interaction
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Processes all the queued events and messages, rebuilding and redrawing
|
|
||||||
/// the widgets of the linked [`Program`] if necessary.
|
|
||||||
///
|
|
||||||
/// Returns a list containing the instances of [`Event`] that were not
|
|
||||||
/// captured by any widget, and the [`Task`] obtained from [`Program`]
|
|
||||||
/// after updating it, only if an update was necessary.
|
|
||||||
pub fn update(
|
|
||||||
&mut self,
|
|
||||||
bounds: Size,
|
|
||||||
cursor: mouse::Cursor,
|
|
||||||
renderer: &mut P::Renderer,
|
|
||||||
theme: &P::Theme,
|
|
||||||
style: &renderer::Style,
|
|
||||||
clipboard: &mut dyn Clipboard,
|
|
||||||
debug: &mut Debug,
|
|
||||||
) -> (Vec<Event>, Option<Task<P::Message>>) {
|
|
||||||
let mut user_interface = build_user_interface(
|
|
||||||
&mut self.program,
|
|
||||||
self.cache.take().unwrap(),
|
|
||||||
renderer,
|
|
||||||
bounds,
|
|
||||||
debug,
|
|
||||||
);
|
|
||||||
|
|
||||||
debug.event_processing_started();
|
|
||||||
let mut messages = Vec::new();
|
|
||||||
|
|
||||||
let (_, event_statuses) = user_interface.update(
|
|
||||||
&self.queued_events,
|
|
||||||
cursor,
|
|
||||||
renderer,
|
|
||||||
clipboard,
|
|
||||||
&mut messages,
|
|
||||||
);
|
|
||||||
|
|
||||||
let uncaptured_events = self
|
|
||||||
.queued_events
|
|
||||||
.iter()
|
|
||||||
.zip(event_statuses)
|
|
||||||
.filter_map(|(event, status)| {
|
|
||||||
matches!(status, event::Status::Ignored).then_some(event)
|
|
||||||
})
|
|
||||||
.cloned()
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
self.queued_events.clear();
|
|
||||||
messages.append(&mut self.queued_messages);
|
|
||||||
debug.event_processing_finished();
|
|
||||||
|
|
||||||
let task = if messages.is_empty() {
|
|
||||||
debug.draw_started();
|
|
||||||
self.mouse_interaction =
|
|
||||||
user_interface.draw(renderer, theme, style, cursor);
|
|
||||||
debug.draw_finished();
|
|
||||||
|
|
||||||
self.cache = Some(user_interface.into_cache());
|
|
||||||
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
// When there are messages, we are forced to rebuild twice
|
|
||||||
// for now :^)
|
|
||||||
let temp_cache = user_interface.into_cache();
|
|
||||||
|
|
||||||
let tasks = Task::batch(messages.into_iter().map(|message| {
|
|
||||||
debug.log_message(&message);
|
|
||||||
|
|
||||||
debug.update_started();
|
|
||||||
let task = self.program.update(message);
|
|
||||||
debug.update_finished();
|
|
||||||
|
|
||||||
task
|
|
||||||
}));
|
|
||||||
|
|
||||||
let mut user_interface = build_user_interface(
|
|
||||||
&mut self.program,
|
|
||||||
temp_cache,
|
|
||||||
renderer,
|
|
||||||
bounds,
|
|
||||||
debug,
|
|
||||||
);
|
|
||||||
|
|
||||||
debug.draw_started();
|
|
||||||
self.mouse_interaction =
|
|
||||||
user_interface.draw(renderer, theme, style, cursor);
|
|
||||||
debug.draw_finished();
|
|
||||||
|
|
||||||
self.cache = Some(user_interface.into_cache());
|
|
||||||
|
|
||||||
Some(tasks)
|
|
||||||
};
|
|
||||||
|
|
||||||
(uncaptured_events, task)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Applies [`Operation`]s to the [`State`]
|
|
||||||
pub fn operate(
|
|
||||||
&mut self,
|
|
||||||
renderer: &mut P::Renderer,
|
|
||||||
operations: impl Iterator<Item = Box<dyn Operation>>,
|
|
||||||
bounds: Size,
|
|
||||||
debug: &mut Debug,
|
|
||||||
) {
|
|
||||||
let mut user_interface = build_user_interface(
|
|
||||||
&mut self.program,
|
|
||||||
self.cache.take().unwrap(),
|
|
||||||
renderer,
|
|
||||||
bounds,
|
|
||||||
debug,
|
|
||||||
);
|
|
||||||
|
|
||||||
for operation in operations {
|
|
||||||
let mut current_operation = Some(operation);
|
|
||||||
|
|
||||||
while let Some(mut operation) = current_operation.take() {
|
|
||||||
user_interface.operate(renderer, operation.as_mut());
|
|
||||||
|
|
||||||
match operation.finish() {
|
|
||||||
operation::Outcome::None => {}
|
|
||||||
operation::Outcome::Some(()) => {}
|
|
||||||
operation::Outcome::Chain(next) => {
|
|
||||||
current_operation = Some(next);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.cache = Some(user_interface.into_cache());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_user_interface<'a, P: Program>(
|
|
||||||
program: &'a mut P,
|
|
||||||
cache: user_interface::Cache,
|
|
||||||
renderer: &mut P::Renderer,
|
|
||||||
size: Size,
|
|
||||||
debug: &mut Debug,
|
|
||||||
) -> UserInterface<'a, P::Message, P::Theme, P::Renderer> {
|
|
||||||
debug.view_started();
|
|
||||||
let view = program.view();
|
|
||||||
debug.view_finished();
|
|
||||||
|
|
||||||
debug.layout_started();
|
|
||||||
let user_interface = UserInterface::build(view, size, cache, renderer);
|
|
||||||
debug.layout_finished();
|
|
||||||
|
|
||||||
user_interface
|
|
||||||
}
|
|
||||||
|
|
@ -19,12 +19,18 @@ pub use sipper::{Never, Sender, Sipper, Straw, sipper, stream};
|
||||||
/// A [`Task`] _may_ produce a bunch of values of type `T`.
|
/// A [`Task`] _may_ produce a bunch of values of type `T`.
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
#[must_use = "`Task` must be returned to the runtime to take effect; normally in your `update` or `new` functions."]
|
#[must_use = "`Task` must be returned to the runtime to take effect; normally in your `update` or `new` functions."]
|
||||||
pub struct Task<T>(Option<BoxStream<Action<T>>>);
|
pub struct Task<T> {
|
||||||
|
stream: Option<BoxStream<Action<T>>>,
|
||||||
|
units: usize,
|
||||||
|
}
|
||||||
|
|
||||||
impl<T> Task<T> {
|
impl<T> Task<T> {
|
||||||
/// Creates a [`Task`] that does nothing.
|
/// Creates a [`Task`] that does nothing.
|
||||||
pub fn none() -> Self {
|
pub fn none() -> Self {
|
||||||
Self(None)
|
Self {
|
||||||
|
stream: None,
|
||||||
|
units: 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new [`Task`] that instantly produces the given value.
|
/// Creates a new [`Task`] that instantly produces the given value.
|
||||||
|
|
@ -83,9 +89,16 @@ impl<T> Task<T> {
|
||||||
where
|
where
|
||||||
T: 'static,
|
T: 'static,
|
||||||
{
|
{
|
||||||
Self(Some(boxed_stream(stream::select_all(
|
let select_all = stream::select_all(
|
||||||
tasks.into_iter().filter_map(|task| task.0),
|
tasks.into_iter().filter_map(|task| task.stream),
|
||||||
))))
|
);
|
||||||
|
|
||||||
|
let units = select_all.len();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
stream: Some(boxed_stream(select_all)),
|
||||||
|
units,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Maps the output of a [`Task`] with the given closure.
|
/// Maps the output of a [`Task`] with the given closure.
|
||||||
|
|
@ -113,21 +126,26 @@ impl<T> Task<T> {
|
||||||
T: MaybeSend + 'static,
|
T: MaybeSend + 'static,
|
||||||
O: MaybeSend + 'static,
|
O: MaybeSend + 'static,
|
||||||
{
|
{
|
||||||
Task(match self.0 {
|
Task {
|
||||||
None => None,
|
stream: match self.stream {
|
||||||
Some(stream) => {
|
None => None,
|
||||||
Some(boxed_stream(stream.flat_map(move |action| {
|
Some(stream) => {
|
||||||
match action.output() {
|
Some(boxed_stream(stream.flat_map(move |action| {
|
||||||
Ok(output) => f(output)
|
match action.output() {
|
||||||
.0
|
Ok(output) => {
|
||||||
.unwrap_or_else(|| boxed_stream(stream::empty())),
|
f(output).stream.unwrap_or_else(|| {
|
||||||
Err(action) => {
|
boxed_stream(stream::empty())
|
||||||
boxed_stream(stream::once(async move { action }))
|
})
|
||||||
|
}
|
||||||
|
Err(action) => boxed_stream(stream::once(
|
||||||
|
async move { action },
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
})))
|
||||||
})))
|
}
|
||||||
}
|
},
|
||||||
})
|
units: self.units,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Chains a new [`Task`] to be performed once the current one finishes completely.
|
/// Chains a new [`Task`] to be performed once the current one finishes completely.
|
||||||
|
|
@ -135,11 +153,17 @@ impl<T> Task<T> {
|
||||||
where
|
where
|
||||||
T: 'static,
|
T: 'static,
|
||||||
{
|
{
|
||||||
match self.0 {
|
match self.stream {
|
||||||
None => task,
|
None => task,
|
||||||
Some(first) => match task.0 {
|
Some(first) => match task.stream {
|
||||||
None => Task(Some(first)),
|
None => Self {
|
||||||
Some(second) => Task(Some(boxed_stream(first.chain(second)))),
|
stream: Some(first),
|
||||||
|
units: self.units,
|
||||||
|
},
|
||||||
|
Some(second) => Self {
|
||||||
|
stream: Some(boxed_stream(first.chain(second))),
|
||||||
|
units: self.units + task.units,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -149,35 +173,39 @@ impl<T> Task<T> {
|
||||||
where
|
where
|
||||||
T: MaybeSend + 'static,
|
T: MaybeSend + 'static,
|
||||||
{
|
{
|
||||||
match self.0 {
|
match self.stream {
|
||||||
None => Task::done(Vec::new()),
|
None => Task::done(Vec::new()),
|
||||||
Some(stream) => Task(Some(boxed_stream(
|
Some(stream) => Task {
|
||||||
stream::unfold(
|
stream: Some(boxed_stream(
|
||||||
(stream, Some(Vec::new())),
|
stream::unfold(
|
||||||
move |(mut stream, outputs)| async move {
|
(stream, Some(Vec::new())),
|
||||||
let mut outputs = outputs?;
|
move |(mut stream, outputs)| async move {
|
||||||
|
let mut outputs = outputs?;
|
||||||
|
|
||||||
let Some(action) = stream.next().await else {
|
let Some(action) = stream.next().await else {
|
||||||
return Some((
|
return Some((
|
||||||
Some(Action::Output(outputs)),
|
Some(Action::Output(outputs)),
|
||||||
(stream, None),
|
(stream, None),
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
match action.output() {
|
match action.output() {
|
||||||
Ok(output) => {
|
Ok(output) => {
|
||||||
outputs.push(output);
|
outputs.push(output);
|
||||||
|
|
||||||
Some((None, (stream, Some(outputs))))
|
Some((None, (stream, Some(outputs))))
|
||||||
|
}
|
||||||
|
Err(action) => Some((
|
||||||
|
Some(action),
|
||||||
|
(stream, Some(outputs)),
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
Err(action) => {
|
},
|
||||||
Some((Some(action), (stream, Some(outputs))))
|
)
|
||||||
}
|
.filter_map(future::ready),
|
||||||
}
|
)),
|
||||||
},
|
units: self.units,
|
||||||
)
|
},
|
||||||
.filter_map(future::ready),
|
|
||||||
))),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -197,26 +225,25 @@ impl<T> Task<T> {
|
||||||
where
|
where
|
||||||
T: 'static,
|
T: 'static,
|
||||||
{
|
{
|
||||||
match self.0 {
|
let (stream, handle) = match self.stream {
|
||||||
Some(stream) => {
|
Some(stream) => {
|
||||||
let (stream, handle) = stream::abortable(stream);
|
let (stream, handle) = stream::abortable(stream);
|
||||||
|
|
||||||
(
|
(Some(boxed_stream(stream)), InternalHandle::Manual(handle))
|
||||||
Self(Some(boxed_stream(stream))),
|
|
||||||
Handle {
|
|
||||||
internal: InternalHandle::Manual(handle),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
None => (
|
None => (
|
||||||
Self(None),
|
None,
|
||||||
Handle {
|
InternalHandle::Manual(stream::AbortHandle::new_pair().0),
|
||||||
internal: InternalHandle::Manual(
|
|
||||||
stream::AbortHandle::new_pair().0,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
}
|
};
|
||||||
|
|
||||||
|
(
|
||||||
|
Self {
|
||||||
|
stream,
|
||||||
|
units: self.units,
|
||||||
|
},
|
||||||
|
Handle { internal: handle },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new [`Task`] that runs the given [`Future`] and produces
|
/// Creates a new [`Task`] that runs the given [`Future`] and produces
|
||||||
|
|
@ -234,7 +261,15 @@ impl<T> Task<T> {
|
||||||
where
|
where
|
||||||
T: 'static,
|
T: 'static,
|
||||||
{
|
{
|
||||||
Self(Some(boxed_stream(stream.map(Action::Output))))
|
Self {
|
||||||
|
stream: Some(boxed_stream(stream.map(Action::Output))),
|
||||||
|
units: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the amount of work "units" of the [`Task`].
|
||||||
|
pub fn units(&self) -> usize {
|
||||||
|
self.units
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -368,13 +403,14 @@ where
|
||||||
|
|
||||||
let action = f(sender);
|
let action = f(sender);
|
||||||
|
|
||||||
Task(Some(boxed_stream(
|
Task {
|
||||||
stream::once(async move { action }).chain(
|
stream: Some(boxed_stream(stream::once(async move { action }).chain(
|
||||||
receiver.into_stream().filter_map(|result| async move {
|
receiver.into_stream().filter_map(|result| async move {
|
||||||
Some(Action::Output(result.ok()?))
|
Some(Action::Output(result.ok()?))
|
||||||
}),
|
}),
|
||||||
),
|
))),
|
||||||
)))
|
units: 1,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new [`Task`] that executes the [`Action`] returned by the closure and
|
/// Creates a new [`Task`] that executes the [`Action`] returned by the closure and
|
||||||
|
|
@ -387,22 +423,28 @@ where
|
||||||
|
|
||||||
let action = f(sender);
|
let action = f(sender);
|
||||||
|
|
||||||
Task(Some(boxed_stream(
|
Task {
|
||||||
stream::once(async move { action })
|
stream: Some(boxed_stream(
|
||||||
.chain(receiver.map(|result| Action::Output(result))),
|
stream::once(async move { action })
|
||||||
)))
|
.chain(receiver.map(|result| Action::Output(result))),
|
||||||
|
)),
|
||||||
|
units: 1,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new [`Task`] that executes the given [`Action`] and produces no output.
|
/// Creates a new [`Task`] that executes the given [`Action`] and produces no output.
|
||||||
pub fn effect<T>(action: impl Into<Action<Infallible>>) -> Task<T> {
|
pub fn effect<T>(action: impl Into<Action<Infallible>>) -> Task<T> {
|
||||||
let action = action.into();
|
let action = action.into();
|
||||||
|
|
||||||
Task(Some(boxed_stream(stream::once(async move {
|
Task {
|
||||||
action.output().expect_err("no output")
|
stream: Some(boxed_stream(stream::once(async move {
|
||||||
}))))
|
action.output().expect_err("no output")
|
||||||
|
}))),
|
||||||
|
units: 1,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the underlying [`Stream`] of the [`Task`].
|
/// Returns the underlying [`Stream`] of the [`Task`].
|
||||||
pub fn into_stream<T>(task: Task<T>) -> Option<BoxStream<Action<T>>> {
|
pub fn into_stream<T>(task: Task<T>) -> Option<BoxStream<Action<T>>> {
|
||||||
task.0
|
task.stream
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,4 +23,7 @@ pub use crate::core::renderer::{self, Renderer};
|
||||||
pub use crate::core::svg;
|
pub use crate::core::svg;
|
||||||
pub use crate::core::text::{self, Text};
|
pub use crate::core::text::{self, Text};
|
||||||
pub use crate::renderer::graphics;
|
pub use crate::renderer::graphics;
|
||||||
|
|
||||||
|
pub use iced_debug as debug;
|
||||||
|
|
||||||
pub use widget::Widget;
|
pub use widget::Widget;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
//! use iced::Theme;
|
//! use iced::Theme;
|
||||||
//!
|
//!
|
||||||
//! pub fn main() -> iced::Result {
|
//! pub fn main() -> iced::Result {
|
||||||
//! iced::application("A counter", update, view)
|
//! iced::application(u64::default, update, view)
|
||||||
//! .theme(|_| Theme::Dark)
|
//! .theme(|_| Theme::Dark)
|
||||||
//! .centered()
|
//! .centered()
|
||||||
//! .run()
|
//! .run()
|
||||||
|
|
@ -31,6 +31,7 @@
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
use crate::program::{self, Program};
|
use crate::program::{self, Program};
|
||||||
|
use crate::shell;
|
||||||
use crate::theme;
|
use crate::theme;
|
||||||
use crate::window;
|
use crate::window;
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -39,14 +40,14 @@ use crate::{
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
/// Creates an iced [`Application`] given its title, update, and view logic.
|
/// Creates an iced [`Application`] given its boot, update, and view logic.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```no_run,standalone_crate
|
/// ```no_run,standalone_crate
|
||||||
/// use iced::widget::{button, column, text, Column};
|
/// use iced::widget::{button, column, text, Column};
|
||||||
///
|
///
|
||||||
/// pub fn main() -> iced::Result {
|
/// pub fn main() -> iced::Result {
|
||||||
/// iced::application("A counter", update, view).run()
|
/// iced::application(u64::default, update, view).run()
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// #[derive(Debug, Clone)]
|
/// #[derive(Debug, Clone)]
|
||||||
|
|
@ -68,7 +69,7 @@ use std::borrow::Cow;
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn application<State, Message, Theme, Renderer>(
|
pub fn application<State, Message, Theme, Renderer>(
|
||||||
title: impl Title<State>,
|
boot: impl Boot<State, Message>,
|
||||||
update: impl Update<State, Message>,
|
update: impl Update<State, Message>,
|
||||||
view: impl for<'a> self::View<'a, State, Message, Theme, Renderer>,
|
view: impl for<'a> self::View<'a, State, Message, Theme, Renderer>,
|
||||||
) -> Application<impl Program<State = State, Message = Message, Theme = Theme>>
|
) -> Application<impl Program<State = State, Message = Message, Theme = Theme>>
|
||||||
|
|
@ -80,7 +81,8 @@ where
|
||||||
{
|
{
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
struct Instance<State, Message, Theme, Renderer, Update, View> {
|
struct Instance<State, Message, Theme, Renderer, Boot, Update, View> {
|
||||||
|
boot: Boot,
|
||||||
update: Update,
|
update: Update,
|
||||||
view: View,
|
view: View,
|
||||||
_state: PhantomData<State>,
|
_state: PhantomData<State>,
|
||||||
|
|
@ -89,12 +91,13 @@ where
|
||||||
_renderer: PhantomData<Renderer>,
|
_renderer: PhantomData<Renderer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<State, Message, Theme, Renderer, Update, View> Program
|
impl<State, Message, Theme, Renderer, Boot, Update, View> Program
|
||||||
for Instance<State, Message, Theme, Renderer, Update, View>
|
for Instance<State, Message, Theme, Renderer, Boot, Update, View>
|
||||||
where
|
where
|
||||||
Message: Send + std::fmt::Debug + 'static,
|
Message: Send + std::fmt::Debug + 'static,
|
||||||
Theme: Default + theme::Base,
|
Theme: Default + theme::Base,
|
||||||
Renderer: program::Renderer,
|
Renderer: program::Renderer,
|
||||||
|
Boot: self::Boot<State, Message>,
|
||||||
Update: self::Update<State, Message>,
|
Update: self::Update<State, Message>,
|
||||||
View: for<'a> self::View<'a, State, Message, Theme, Renderer>,
|
View: for<'a> self::View<'a, State, Message, Theme, Renderer>,
|
||||||
{
|
{
|
||||||
|
|
@ -104,6 +107,16 @@ where
|
||||||
type Renderer = Renderer;
|
type Renderer = Renderer;
|
||||||
type Executor = iced_futures::backend::default::Executor;
|
type Executor = iced_futures::backend::default::Executor;
|
||||||
|
|
||||||
|
fn name() -> &'static str {
|
||||||
|
let name = std::any::type_name::<State>();
|
||||||
|
|
||||||
|
name.split("::").next().unwrap_or("a_cool_application")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn boot(&self) -> (State, Task<Message>) {
|
||||||
|
self.boot.boot()
|
||||||
|
}
|
||||||
|
|
||||||
fn update(
|
fn update(
|
||||||
&self,
|
&self,
|
||||||
state: &mut Self::State,
|
state: &mut Self::State,
|
||||||
|
|
@ -123,6 +136,7 @@ where
|
||||||
|
|
||||||
Application {
|
Application {
|
||||||
raw: Instance {
|
raw: Instance {
|
||||||
|
boot,
|
||||||
update,
|
update,
|
||||||
view,
|
view,
|
||||||
_state: PhantomData,
|
_state: PhantomData,
|
||||||
|
|
@ -133,7 +147,6 @@ where
|
||||||
settings: Settings::default(),
|
settings: Settings::default(),
|
||||||
window: window::Settings::default(),
|
window: window::Settings::default(),
|
||||||
}
|
}
|
||||||
.title(title)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The underlying definition and configuration of an iced application.
|
/// The underlying definition and configuration of an iced application.
|
||||||
|
|
@ -152,28 +165,17 @@ pub struct Application<P: Program> {
|
||||||
|
|
||||||
impl<P: Program> Application<P> {
|
impl<P: Program> Application<P> {
|
||||||
/// Runs the [`Application`].
|
/// Runs the [`Application`].
|
||||||
///
|
|
||||||
/// The state of the [`Application`] must implement [`Default`].
|
|
||||||
/// If your state does not implement [`Default`], use [`run_with`]
|
|
||||||
/// instead.
|
|
||||||
///
|
|
||||||
/// [`run_with`]: Self::run_with
|
|
||||||
pub fn run(self) -> Result
|
pub fn run(self) -> Result
|
||||||
where
|
where
|
||||||
Self: 'static,
|
Self: 'static,
|
||||||
P::State: Default,
|
|
||||||
{
|
{
|
||||||
self.raw.run(self.settings, Some(self.window))
|
#[cfg(feature = "debug")]
|
||||||
}
|
let program = iced_devtools::attach(self.raw);
|
||||||
|
|
||||||
/// Runs the [`Application`] with a closure that creates the initial state.
|
#[cfg(not(feature = "debug"))]
|
||||||
pub fn run_with<I>(self, initialize: I) -> Result
|
let program = self.raw;
|
||||||
where
|
|
||||||
Self: 'static,
|
Ok(shell::run(program, self.settings, Some(self.window))?)
|
||||||
I: FnOnce() -> (P::State, Task<P::Message>) + 'static,
|
|
||||||
{
|
|
||||||
self.raw
|
|
||||||
.run_with(self.settings, Some(self.window), initialize)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the [`Settings`] that will be used to run the [`Application`].
|
/// Sets the [`Settings`] that will be used to run the [`Application`].
|
||||||
|
|
@ -305,7 +307,7 @@ impl<P: Program> Application<P> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the [`Title`] of the [`Application`].
|
/// Sets the [`Title`] of the [`Application`].
|
||||||
pub(crate) fn title(
|
pub fn title(
|
||||||
self,
|
self,
|
||||||
title: impl Title<P::State>,
|
title: impl Title<P::State>,
|
||||||
) -> Application<
|
) -> Application<
|
||||||
|
|
@ -395,6 +397,47 @@ impl<P: Program> Application<P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The logic to initialize the `State` of some [`Application`].
|
||||||
|
///
|
||||||
|
/// This trait is implemented for both `Fn() -> State` and
|
||||||
|
/// `Fn() -> (State, Task<Message>)`.
|
||||||
|
///
|
||||||
|
/// In practice, this means that [`application`] can both take
|
||||||
|
/// simple functions like `State::default` and more advanced ones
|
||||||
|
/// that return a [`Task`].
|
||||||
|
pub trait Boot<State, Message> {
|
||||||
|
/// Initializes the [`Application`] state.
|
||||||
|
fn boot(&self) -> (State, Task<Message>);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, C, State, Message> Boot<State, Message> for T
|
||||||
|
where
|
||||||
|
T: Fn() -> C,
|
||||||
|
C: IntoBoot<State, Message>,
|
||||||
|
{
|
||||||
|
fn boot(&self) -> (State, Task<Message>) {
|
||||||
|
self().into_boot()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The initial state of some [`Application`].
|
||||||
|
pub trait IntoBoot<State, Message> {
|
||||||
|
/// Turns some type into the initial state of some [`Application`].
|
||||||
|
fn into_boot(self) -> (State, Task<Message>);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<State, Message> IntoBoot<State, Message> for State {
|
||||||
|
fn into_boot(self) -> (State, Task<Message>) {
|
||||||
|
(self, Task::none())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<State, Message> IntoBoot<State, Message> for (State, Task<Message>) {
|
||||||
|
fn into_boot(self) -> (State, Task<Message>) {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The title logic of some [`Application`].
|
/// The title logic of some [`Application`].
|
||||||
///
|
///
|
||||||
/// This trait is implemented both for `&static str` and
|
/// This trait is implemented both for `&static str` and
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
//! Create and run daemons that run in the background.
|
//! Create and run daemons that run in the background.
|
||||||
use crate::application;
|
use crate::application;
|
||||||
use crate::program::{self, Program};
|
use crate::program::{self, Program};
|
||||||
|
use crate::shell;
|
||||||
use crate::theme;
|
use crate::theme;
|
||||||
use crate::window;
|
use crate::window;
|
||||||
use crate::{Element, Executor, Font, Result, Settings, Subscription, Task};
|
use crate::{Element, Executor, Font, Result, Settings, Subscription, Task};
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
/// Creates an iced [`Daemon`] given its title, update, and view logic.
|
/// Creates an iced [`Daemon`] given its boot, update, and view logic.
|
||||||
///
|
///
|
||||||
/// A [`Daemon`] will not open a window by default, but will run silently
|
/// A [`Daemon`] will not open a window by default, but will run silently
|
||||||
/// instead until a [`Task`] from [`window::open`] is returned by its update logic.
|
/// instead until a [`Task`] from [`window::open`] is returned by its update logic.
|
||||||
|
|
@ -18,7 +19,7 @@ use std::borrow::Cow;
|
||||||
///
|
///
|
||||||
/// [`exit`]: crate::exit
|
/// [`exit`]: crate::exit
|
||||||
pub fn daemon<State, Message, Theme, Renderer>(
|
pub fn daemon<State, Message, Theme, Renderer>(
|
||||||
title: impl Title<State>,
|
boot: impl application::Boot<State, Message>,
|
||||||
update: impl application::Update<State, Message>,
|
update: impl application::Update<State, Message>,
|
||||||
view: impl for<'a> self::View<'a, State, Message, Theme, Renderer>,
|
view: impl for<'a> self::View<'a, State, Message, Theme, Renderer>,
|
||||||
) -> Daemon<impl Program<State = State, Message = Message, Theme = Theme>>
|
) -> Daemon<impl Program<State = State, Message = Message, Theme = Theme>>
|
||||||
|
|
@ -30,7 +31,8 @@ where
|
||||||
{
|
{
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
struct Instance<State, Message, Theme, Renderer, Update, View> {
|
struct Instance<State, Message, Theme, Renderer, Boot, Update, View> {
|
||||||
|
boot: Boot,
|
||||||
update: Update,
|
update: Update,
|
||||||
view: View,
|
view: View,
|
||||||
_state: PhantomData<State>,
|
_state: PhantomData<State>,
|
||||||
|
|
@ -39,12 +41,13 @@ where
|
||||||
_renderer: PhantomData<Renderer>,
|
_renderer: PhantomData<Renderer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<State, Message, Theme, Renderer, Update, View> Program
|
impl<State, Message, Theme, Renderer, Boot, Update, View> Program
|
||||||
for Instance<State, Message, Theme, Renderer, Update, View>
|
for Instance<State, Message, Theme, Renderer, Boot, Update, View>
|
||||||
where
|
where
|
||||||
Message: Send + std::fmt::Debug + 'static,
|
Message: Send + std::fmt::Debug + 'static,
|
||||||
Theme: Default + theme::Base,
|
Theme: Default + theme::Base,
|
||||||
Renderer: program::Renderer,
|
Renderer: program::Renderer,
|
||||||
|
Boot: application::Boot<State, Message>,
|
||||||
Update: application::Update<State, Message>,
|
Update: application::Update<State, Message>,
|
||||||
View: for<'a> self::View<'a, State, Message, Theme, Renderer>,
|
View: for<'a> self::View<'a, State, Message, Theme, Renderer>,
|
||||||
{
|
{
|
||||||
|
|
@ -54,6 +57,16 @@ where
|
||||||
type Renderer = Renderer;
|
type Renderer = Renderer;
|
||||||
type Executor = iced_futures::backend::default::Executor;
|
type Executor = iced_futures::backend::default::Executor;
|
||||||
|
|
||||||
|
fn name() -> &'static str {
|
||||||
|
let name = std::any::type_name::<State>();
|
||||||
|
|
||||||
|
name.split("::").next().unwrap_or("a_cool_daemon")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
||||||
|
self.boot.boot()
|
||||||
|
}
|
||||||
|
|
||||||
fn update(
|
fn update(
|
||||||
&self,
|
&self,
|
||||||
state: &mut Self::State,
|
state: &mut Self::State,
|
||||||
|
|
@ -73,6 +86,7 @@ where
|
||||||
|
|
||||||
Daemon {
|
Daemon {
|
||||||
raw: Instance {
|
raw: Instance {
|
||||||
|
boot,
|
||||||
update,
|
update,
|
||||||
view,
|
view,
|
||||||
_state: PhantomData,
|
_state: PhantomData,
|
||||||
|
|
@ -82,7 +96,6 @@ where
|
||||||
},
|
},
|
||||||
settings: Settings::default(),
|
settings: Settings::default(),
|
||||||
}
|
}
|
||||||
.title(title)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The underlying definition and configuration of an iced daemon.
|
/// The underlying definition and configuration of an iced daemon.
|
||||||
|
|
@ -100,27 +113,11 @@ pub struct Daemon<P: Program> {
|
||||||
|
|
||||||
impl<P: Program> Daemon<P> {
|
impl<P: Program> Daemon<P> {
|
||||||
/// Runs the [`Daemon`].
|
/// Runs the [`Daemon`].
|
||||||
///
|
|
||||||
/// The state of the [`Daemon`] must implement [`Default`].
|
|
||||||
/// If your state does not implement [`Default`], use [`run_with`]
|
|
||||||
/// instead.
|
|
||||||
///
|
|
||||||
/// [`run_with`]: Self::run_with
|
|
||||||
pub fn run(self) -> Result
|
pub fn run(self) -> Result
|
||||||
where
|
where
|
||||||
Self: 'static,
|
Self: 'static,
|
||||||
P::State: Default,
|
|
||||||
{
|
{
|
||||||
self.raw.run(self.settings, None)
|
Ok(shell::run(self.raw, self.settings, None)?)
|
||||||
}
|
|
||||||
|
|
||||||
/// Runs the [`Daemon`] with a closure that creates the initial state.
|
|
||||||
pub fn run_with<I>(self, initialize: I) -> Result
|
|
||||||
where
|
|
||||||
Self: 'static,
|
|
||||||
I: FnOnce() -> (P::State, Task<P::Message>) + 'static,
|
|
||||||
{
|
|
||||||
self.raw.run_with(self.settings, None, initialize)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the [`Settings`] that will be used to run the [`Daemon`].
|
/// Sets the [`Settings`] that will be used to run the [`Daemon`].
|
||||||
|
|
@ -157,7 +154,7 @@ impl<P: Program> Daemon<P> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the [`Title`] of the [`Daemon`].
|
/// Sets the [`Title`] of the [`Daemon`].
|
||||||
pub(crate) fn title(
|
pub fn title(
|
||||||
self,
|
self,
|
||||||
title: impl Title<P::State>,
|
title: impl Title<P::State>,
|
||||||
) -> Daemon<
|
) -> Daemon<
|
||||||
|
|
|
||||||
21
src/lib.rs
21
src/lib.rs
|
|
@ -31,7 +31,7 @@
|
||||||
//!
|
//!
|
||||||
//! ```no_run,standalone_crate
|
//! ```no_run,standalone_crate
|
||||||
//! pub fn main() -> iced::Result {
|
//! pub fn main() -> iced::Result {
|
||||||
//! iced::run("A cool counter", update, view)
|
//! iced::run(update, view)
|
||||||
//! }
|
//! }
|
||||||
//! # fn update(state: &mut (), message: ()) {}
|
//! # fn update(state: &mut (), message: ()) {}
|
||||||
//! # fn view(state: &()) -> iced::Element<()> { iced::widget::text("").into() }
|
//! # fn view(state: &()) -> iced::Element<()> { iced::widget::text("").into() }
|
||||||
|
|
@ -198,16 +198,20 @@
|
||||||
//! calling [`run`]:
|
//! calling [`run`]:
|
||||||
//!
|
//!
|
||||||
//! ```no_run,standalone_crate
|
//! ```no_run,standalone_crate
|
||||||
//! # #[derive(Default)]
|
|
||||||
//! # struct State;
|
//! # struct State;
|
||||||
//! use iced::Theme;
|
//! use iced::Theme;
|
||||||
//!
|
//!
|
||||||
//! pub fn main() -> iced::Result {
|
//! pub fn main() -> iced::Result {
|
||||||
//! iced::application("A cool application", update, view)
|
//! iced::application(new, update, view)
|
||||||
//! .theme(theme)
|
//! .theme(theme)
|
||||||
//! .run()
|
//! .run()
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
|
//! fn new() -> State {
|
||||||
|
//! // ...
|
||||||
|
//! # State
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
//! fn theme(state: &State) -> Theme {
|
//! fn theme(state: &State) -> Theme {
|
||||||
//! Theme::TokyoNight
|
//! Theme::TokyoNight
|
||||||
//! }
|
//! }
|
||||||
|
|
@ -335,7 +339,6 @@
|
||||||
//! You will need to define a `subscription` function and use the [`Application`] builder:
|
//! You will need to define a `subscription` function and use the [`Application`] builder:
|
||||||
//!
|
//!
|
||||||
//! ```no_run,standalone_crate
|
//! ```no_run,standalone_crate
|
||||||
//! # #[derive(Default)]
|
|
||||||
//! # struct State;
|
//! # struct State;
|
||||||
//! use iced::window;
|
//! use iced::window;
|
||||||
//! use iced::{Size, Subscription};
|
//! use iced::{Size, Subscription};
|
||||||
|
|
@ -346,7 +349,7 @@
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! pub fn main() -> iced::Result {
|
//! pub fn main() -> iced::Result {
|
||||||
//! iced::application("A cool application", update, view)
|
//! iced::application(new, update, view)
|
||||||
//! .subscription(subscription)
|
//! .subscription(subscription)
|
||||||
//! .run()
|
//! .run()
|
||||||
//! }
|
//! }
|
||||||
|
|
@ -354,6 +357,7 @@
|
||||||
//! fn subscription(state: &State) -> Subscription<Message> {
|
//! fn subscription(state: &State) -> Subscription<Message> {
|
||||||
//! window::resize_events().map(|(_id, size)| Message::WindowResized(size))
|
//! window::resize_events().map(|(_id, size)| Message::WindowResized(size))
|
||||||
//! }
|
//! }
|
||||||
|
//! # fn new() -> State { State }
|
||||||
//! # fn update(state: &mut State, message: Message) {}
|
//! # fn update(state: &mut State, message: Message) {}
|
||||||
//! # fn view(state: &State) -> iced::Element<Message> { iced::widget::text("").into() }
|
//! # fn view(state: &State) -> iced::Element<Message> { iced::widget::text("").into() }
|
||||||
//! ```
|
//! ```
|
||||||
|
|
@ -475,6 +479,7 @@ use iced_widget::graphics;
|
||||||
use iced_widget::renderer;
|
use iced_widget::renderer;
|
||||||
use iced_winit as shell;
|
use iced_winit as shell;
|
||||||
use iced_winit::core;
|
use iced_winit::core;
|
||||||
|
use iced_winit::program;
|
||||||
use iced_winit::runtime;
|
use iced_winit::runtime;
|
||||||
|
|
||||||
pub use iced_futures::futures;
|
pub use iced_futures::futures;
|
||||||
|
|
@ -499,7 +504,6 @@ pub use iced_highlighter as highlighter;
|
||||||
pub use iced_renderer::wgpu::wgpu;
|
pub use iced_renderer::wgpu::wgpu;
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
mod program;
|
|
||||||
|
|
||||||
pub mod application;
|
pub mod application;
|
||||||
pub mod daemon;
|
pub mod daemon;
|
||||||
|
|
@ -660,7 +664,7 @@ pub type Result = std::result::Result<(), Error>;
|
||||||
/// use iced::widget::{button, column, text, Column};
|
/// use iced::widget::{button, column, text, Column};
|
||||||
///
|
///
|
||||||
/// pub fn main() -> iced::Result {
|
/// pub fn main() -> iced::Result {
|
||||||
/// iced::run("A counter", update, view)
|
/// iced::run(update, view)
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// #[derive(Debug, Clone)]
|
/// #[derive(Debug, Clone)]
|
||||||
|
|
@ -682,7 +686,6 @@ pub type Result = std::result::Result<(), Error>;
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn run<State, Message, Theme, Renderer>(
|
pub fn run<State, Message, Theme, Renderer>(
|
||||||
title: impl application::Title<State> + 'static,
|
|
||||||
update: impl application::Update<State, Message> + 'static,
|
update: impl application::Update<State, Message> + 'static,
|
||||||
view: impl for<'a> application::View<'a, State, Message, Theme, Renderer>
|
view: impl for<'a> application::View<'a, State, Message, Theme, Renderer>
|
||||||
+ 'static,
|
+ 'static,
|
||||||
|
|
@ -693,5 +696,5 @@ where
|
||||||
Theme: Default + theme::Base + 'static,
|
Theme: Default + theme::Base + 'static,
|
||||||
Renderer: program::Renderer + 'static,
|
Renderer: program::Renderer + 'static,
|
||||||
{
|
{
|
||||||
application(title, update, view).run()
|
application(State::default, update, view).run()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,47 +63,16 @@ impl Renderer {
|
||||||
self.layers.as_slice()
|
self.layers.as_slice()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw<T: AsRef<str>>(
|
pub fn draw(
|
||||||
&mut self,
|
&mut self,
|
||||||
pixels: &mut tiny_skia::PixmapMut<'_>,
|
pixels: &mut tiny_skia::PixmapMut<'_>,
|
||||||
clip_mask: &mut tiny_skia::Mask,
|
clip_mask: &mut tiny_skia::Mask,
|
||||||
viewport: &Viewport,
|
viewport: &Viewport,
|
||||||
damage: &[Rectangle],
|
damage: &[Rectangle],
|
||||||
background_color: Color,
|
background_color: Color,
|
||||||
overlay: &[T],
|
|
||||||
) {
|
) {
|
||||||
let physical_size = viewport.physical_size();
|
|
||||||
let scale_factor = viewport.scale_factor() as f32;
|
let scale_factor = viewport.scale_factor() as f32;
|
||||||
|
|
||||||
if !overlay.is_empty() {
|
|
||||||
let path = tiny_skia::PathBuilder::from_rect(
|
|
||||||
tiny_skia::Rect::from_xywh(
|
|
||||||
0.0,
|
|
||||||
0.0,
|
|
||||||
physical_size.width as f32,
|
|
||||||
physical_size.height as f32,
|
|
||||||
)
|
|
||||||
.expect("Create damage rectangle"),
|
|
||||||
);
|
|
||||||
|
|
||||||
pixels.fill_path(
|
|
||||||
&path,
|
|
||||||
&tiny_skia::Paint {
|
|
||||||
shader: tiny_skia::Shader::SolidColor(engine::into_color(
|
|
||||||
Color {
|
|
||||||
a: 0.1,
|
|
||||||
..background_color
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
anti_alias: false,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
tiny_skia::FillRule::default(),
|
|
||||||
tiny_skia::Transform::identity(),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.layers.flush();
|
self.layers.flush();
|
||||||
|
|
||||||
for ®ion in damage {
|
for ®ion in damage {
|
||||||
|
|
@ -201,25 +170,6 @@ impl Renderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !overlay.is_empty() {
|
|
||||||
pixels.stroke_path(
|
|
||||||
&path,
|
|
||||||
&tiny_skia::Paint {
|
|
||||||
shader: tiny_skia::Shader::SolidColor(
|
|
||||||
engine::into_color(Color::from_rgb(1.0, 0.0, 0.0)),
|
|
||||||
),
|
|
||||||
anti_alias: false,
|
|
||||||
..tiny_skia::Paint::default()
|
|
||||||
},
|
|
||||||
&tiny_skia::Stroke {
|
|
||||||
width: 1.0,
|
|
||||||
..tiny_skia::Stroke::default()
|
|
||||||
},
|
|
||||||
tiny_skia::Transform::identity(),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.engine.trim();
|
self.engine.trim();
|
||||||
|
|
|
||||||
|
|
@ -107,13 +107,12 @@ impl crate::graphics::Compositor for Compositor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn present<T: AsRef<str>>(
|
fn present(
|
||||||
&mut self,
|
&mut self,
|
||||||
renderer: &mut Self::Renderer,
|
renderer: &mut Self::Renderer,
|
||||||
surface: &mut Self::Surface,
|
surface: &mut Self::Surface,
|
||||||
viewport: &Viewport,
|
viewport: &Viewport,
|
||||||
background_color: Color,
|
background_color: Color,
|
||||||
overlay: &[T],
|
|
||||||
on_pre_present: impl FnOnce(),
|
on_pre_present: impl FnOnce(),
|
||||||
) -> Result<(), compositor::SurfaceError> {
|
) -> Result<(), compositor::SurfaceError> {
|
||||||
present(
|
present(
|
||||||
|
|
@ -121,7 +120,6 @@ impl crate::graphics::Compositor for Compositor {
|
||||||
surface,
|
surface,
|
||||||
viewport,
|
viewport,
|
||||||
background_color,
|
background_color,
|
||||||
overlay,
|
|
||||||
on_pre_present,
|
on_pre_present,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -147,12 +145,11 @@ pub fn new<W: compositor::Window>(
|
||||||
Compositor { context, settings }
|
Compositor { context, settings }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn present<T: AsRef<str>>(
|
pub fn present(
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
surface: &mut Surface,
|
surface: &mut Surface,
|
||||||
viewport: &Viewport,
|
viewport: &Viewport,
|
||||||
background_color: Color,
|
background_color: Color,
|
||||||
overlay: &[T],
|
|
||||||
on_pre_present: impl FnOnce(),
|
on_pre_present: impl FnOnce(),
|
||||||
) -> Result<(), compositor::SurfaceError> {
|
) -> Result<(), compositor::SurfaceError> {
|
||||||
let physical_size = viewport.physical_size();
|
let physical_size = viewport.physical_size();
|
||||||
|
|
@ -211,7 +208,6 @@ pub fn present<T: AsRef<str>>(
|
||||||
viewport,
|
viewport,
|
||||||
&damage,
|
&damage,
|
||||||
background_color,
|
background_color,
|
||||||
overlay,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
on_pre_present();
|
on_pre_present();
|
||||||
|
|
@ -231,7 +227,7 @@ pub fn screenshot(
|
||||||
let mut clip_mask = tiny_skia::Mask::new(size.width, size.height)
|
let mut clip_mask = tiny_skia::Mask::new(size.width, size.height)
|
||||||
.expect("Create clip mask");
|
.expect("Create clip mask");
|
||||||
|
|
||||||
renderer.draw::<&str>(
|
renderer.draw(
|
||||||
&mut tiny_skia::PixmapMut::from_bytes(
|
&mut tiny_skia::PixmapMut::from_bytes(
|
||||||
bytemuck::cast_slice_mut(&mut offscreen_buffer),
|
bytemuck::cast_slice_mut(&mut offscreen_buffer),
|
||||||
size.width,
|
size.width,
|
||||||
|
|
@ -245,7 +241,6 @@ pub fn screenshot(
|
||||||
size.height as f32,
|
size.height as f32,
|
||||||
))],
|
))],
|
||||||
background_color,
|
background_color,
|
||||||
&[],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
offscreen_buffer.iter().fold(
|
offscreen_buffer.iter().fold(
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,6 @@ pub use geometry::Geometry;
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
|
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
|
||||||
Vector,
|
|
||||||
};
|
};
|
||||||
use crate::graphics::Viewport;
|
use crate::graphics::Viewport;
|
||||||
use crate::graphics::text::{Editor, Paragraph};
|
use crate::graphics::text::{Editor, Paragraph};
|
||||||
|
|
@ -164,16 +163,13 @@ impl Renderer {
|
||||||
encoder
|
encoder
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn present<T: AsRef<str>>(
|
pub fn present(
|
||||||
&mut self,
|
&mut self,
|
||||||
clear_color: Option<Color>,
|
clear_color: Option<Color>,
|
||||||
_format: wgpu::TextureFormat,
|
_format: wgpu::TextureFormat,
|
||||||
frame: &wgpu::TextureView,
|
frame: &wgpu::TextureView,
|
||||||
viewport: &Viewport,
|
viewport: &Viewport,
|
||||||
overlay: &[T],
|
|
||||||
) -> wgpu::SubmissionIndex {
|
) -> wgpu::SubmissionIndex {
|
||||||
self.draw_overlay(overlay, viewport);
|
|
||||||
|
|
||||||
let encoder = self.draw(clear_color, frame, viewport);
|
let encoder = self.draw(clear_color, frame, viewport);
|
||||||
|
|
||||||
self.staging_belt.finish();
|
self.staging_belt.finish();
|
||||||
|
|
@ -577,50 +573,6 @@ impl Renderer {
|
||||||
|
|
||||||
let _ = ManuallyDrop::into_inner(render_pass);
|
let _ = ManuallyDrop::into_inner(render_pass);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_overlay(
|
|
||||||
&mut self,
|
|
||||||
overlay: &[impl AsRef<str>],
|
|
||||||
viewport: &Viewport,
|
|
||||||
) {
|
|
||||||
use crate::core::Renderer as _;
|
|
||||||
use crate::core::alignment;
|
|
||||||
use crate::core::text::Renderer as _;
|
|
||||||
|
|
||||||
self.with_layer(
|
|
||||||
Rectangle::with_size(viewport.logical_size()),
|
|
||||||
|renderer| {
|
|
||||||
for (i, line) in overlay.iter().enumerate() {
|
|
||||||
let text = crate::core::Text {
|
|
||||||
content: line.as_ref().to_owned(),
|
|
||||||
bounds: viewport.logical_size(),
|
|
||||||
size: Pixels(20.0),
|
|
||||||
line_height: core::text::LineHeight::default(),
|
|
||||||
font: Font::MONOSPACE,
|
|
||||||
align_x: core::text::Alignment::Default,
|
|
||||||
align_y: alignment::Vertical::Top,
|
|
||||||
shaping: core::text::Shaping::Basic,
|
|
||||||
wrapping: core::text::Wrapping::Word,
|
|
||||||
};
|
|
||||||
|
|
||||||
renderer.fill_text(
|
|
||||||
text.clone(),
|
|
||||||
Point::new(11.0, 11.0 + 25.0 * i as f32),
|
|
||||||
Color::from_rgba(0.9, 0.9, 0.9, 1.0),
|
|
||||||
Rectangle::with_size(Size::INFINITY),
|
|
||||||
);
|
|
||||||
|
|
||||||
renderer.fill_text(
|
|
||||||
text,
|
|
||||||
Point::new(11.0, 11.0 + 25.0 * i as f32)
|
|
||||||
+ Vector::new(-1.0, -1.0),
|
|
||||||
Color::BLACK,
|
|
||||||
Rectangle::with_size(Size::INFINITY),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl core::Renderer for Renderer {
|
impl core::Renderer for Renderer {
|
||||||
|
|
@ -716,7 +668,7 @@ impl core::text::Renderer for Renderer {
|
||||||
impl core::image::Renderer for Renderer {
|
impl core::image::Renderer for Renderer {
|
||||||
type Handle = core::image::Handle;
|
type Handle = core::image::Handle;
|
||||||
|
|
||||||
fn measure_image(&self, handle: &Self::Handle) -> Size<u32> {
|
fn measure_image(&self, handle: &Self::Handle) -> core::Size<u32> {
|
||||||
self.image_cache.borrow_mut().measure_image(handle)
|
self.image_cache.borrow_mut().measure_image(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -728,7 +680,7 @@ impl core::image::Renderer for Renderer {
|
||||||
|
|
||||||
#[cfg(feature = "svg")]
|
#[cfg(feature = "svg")]
|
||||||
impl core::svg::Renderer for Renderer {
|
impl core::svg::Renderer for Renderer {
|
||||||
fn measure_svg(&self, handle: &core::svg::Handle) -> Size<u32> {
|
fn measure_svg(&self, handle: &core::svg::Handle) -> core::Size<u32> {
|
||||||
self.image_cache.borrow_mut().measure_svg(handle)
|
self.image_cache.borrow_mut().measure_svg(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -760,7 +712,7 @@ impl graphics::geometry::Renderer for Renderer {
|
||||||
type Geometry = Geometry;
|
type Geometry = Geometry;
|
||||||
type Frame = geometry::Frame;
|
type Frame = geometry::Frame;
|
||||||
|
|
||||||
fn new_frame(&self, size: Size) -> Self::Frame {
|
fn new_frame(&self, size: core::Size) -> Self::Frame {
|
||||||
geometry::Frame::new(size)
|
geometry::Frame::new(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -210,12 +210,11 @@ pub async fn new<W: compositor::Window>(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Presents the given primitives with the given [`Compositor`].
|
/// Presents the given primitives with the given [`Compositor`].
|
||||||
pub fn present<T: AsRef<str>>(
|
pub fn present(
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
surface: &mut wgpu::Surface<'static>,
|
surface: &mut wgpu::Surface<'static>,
|
||||||
viewport: &Viewport,
|
viewport: &Viewport,
|
||||||
background_color: Color,
|
background_color: Color,
|
||||||
overlay: &[T],
|
|
||||||
on_pre_present: impl FnOnce(),
|
on_pre_present: impl FnOnce(),
|
||||||
) -> Result<(), compositor::SurfaceError> {
|
) -> Result<(), compositor::SurfaceError> {
|
||||||
match surface.get_current_texture() {
|
match surface.get_current_texture() {
|
||||||
|
|
@ -229,7 +228,6 @@ pub fn present<T: AsRef<str>>(
|
||||||
frame.texture.format(),
|
frame.texture.format(),
|
||||||
view,
|
view,
|
||||||
viewport,
|
viewport,
|
||||||
overlay,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Present the frame
|
// Present the frame
|
||||||
|
|
@ -342,13 +340,12 @@ impl graphics::Compositor for Compositor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn present<T: AsRef<str>>(
|
fn present(
|
||||||
&mut self,
|
&mut self,
|
||||||
renderer: &mut Self::Renderer,
|
renderer: &mut Self::Renderer,
|
||||||
surface: &mut Self::Surface,
|
surface: &mut Self::Surface,
|
||||||
viewport: &Viewport,
|
viewport: &Viewport,
|
||||||
background_color: Color,
|
background_color: Color,
|
||||||
overlay: &[T],
|
|
||||||
on_pre_present: impl FnOnce(),
|
on_pre_present: impl FnOnce(),
|
||||||
) -> Result<(), compositor::SurfaceError> {
|
) -> Result<(), compositor::SurfaceError> {
|
||||||
present(
|
present(
|
||||||
|
|
@ -356,7 +353,6 @@ impl graphics::Compositor for Compositor {
|
||||||
surface,
|
surface,
|
||||||
viewport,
|
viewport,
|
||||||
background_color,
|
background_color,
|
||||||
overlay,
|
|
||||||
on_pre_present,
|
on_pre_present,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2028,7 +2028,7 @@ pub fn focus_next<T>() -> Task<T> {
|
||||||
task::effect(Action::widget(operation::focusable::focus_next()))
|
task::effect(Action::widget(operation::focusable::focus_next()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A container intercepting mouse events.
|
/// Creates a new [`MouseArea`].
|
||||||
pub fn mouse_area<'a, Message, Theme, Renderer>(
|
pub fn mouse_area<'a, Message, Theme, Renderer>(
|
||||||
widget: impl Into<Element<'a, Message, Theme, Renderer>>,
|
widget: impl Into<Element<'a, Message, Theme, Renderer>>,
|
||||||
) -> MouseArea<'a, Message, Theme, Renderer>
|
) -> MouseArea<'a, Message, Theme, Renderer>
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
|
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
|
||||||
debug = ["iced_runtime/debug"]
|
debug = ["iced_debug/enable"]
|
||||||
system = ["sysinfo"]
|
system = ["sysinfo"]
|
||||||
program = []
|
program = []
|
||||||
x11 = ["winit/x11"]
|
x11 = ["winit/x11"]
|
||||||
|
|
@ -25,9 +25,8 @@ wayland-csd-adwaita = ["winit/wayland-csd-adwaita"]
|
||||||
unconditional-rendering = []
|
unconditional-rendering = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced_futures.workspace = true
|
iced_debug.workspace = true
|
||||||
iced_graphics.workspace = true
|
iced_program.workspace = true
|
||||||
iced_runtime.workspace = true
|
|
||||||
|
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
rustc-hash.workspace = true
|
rustc-hash.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ pub enum Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<graphics::Error> for Error {
|
impl From<graphics::Error> for Error {
|
||||||
fn from(error: iced_graphics::Error) -> Error {
|
fn from(error: graphics::Error) -> Error {
|
||||||
Error::GraphicsCreationFailed(error)
|
Error::GraphicsCreationFailed(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue