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 = [
|
||||
"criterion",
|
||||
"iced_core",
|
||||
"iced_debug",
|
||||
"iced_devtools",
|
||||
"iced_futures",
|
||||
"iced_highlighter",
|
||||
"iced_renderer",
|
||||
|
|
@ -2413,6 +2415,20 @@ dependencies = [
|
|||
"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]]
|
||||
name = "iced_core"
|
||||
version = "0.14.0-dev"
|
||||
|
|
@ -2425,11 +2441,30 @@ dependencies = [
|
|||
"log",
|
||||
"num-traits",
|
||||
"rustc-hash 2.1.1",
|
||||
"serde",
|
||||
"smol_str",
|
||||
"thiserror 1.0.69",
|
||||
"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]]
|
||||
name = "iced_futures"
|
||||
version = "0.14.0-dev"
|
||||
|
|
@ -2472,6 +2507,14 @@ dependencies = [
|
|||
"syntect",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iced_program"
|
||||
version = "0.14.0-dev"
|
||||
dependencies = [
|
||||
"iced_graphics",
|
||||
"iced_runtime",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iced_renderer"
|
||||
version = "0.14.0-dev"
|
||||
|
|
@ -2489,6 +2532,7 @@ version = "0.14.0-dev"
|
|||
dependencies = [
|
||||
"bytes",
|
||||
"iced_core",
|
||||
"iced_debug",
|
||||
"iced_futures",
|
||||
"raw-window-handle 0.6.2",
|
||||
"sipper",
|
||||
|
|
@ -2562,9 +2606,8 @@ dependencies = [
|
|||
name = "iced_winit"
|
||||
version = "0.14.0-dev"
|
||||
dependencies = [
|
||||
"iced_futures",
|
||||
"iced_graphics",
|
||||
"iced_runtime",
|
||||
"iced_debug",
|
||||
"iced_program",
|
||||
"log",
|
||||
"rustc-hash 2.1.1",
|
||||
"sysinfo",
|
||||
|
|
@ -4985,6 +5028,15 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2fdfc24bc566f839a2da4c4295b82db7d25a24253867d5c64355abb5799bdbe"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
|
|
@ -5768,9 +5820,21 @@ dependencies = [
|
|||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"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]]
|
||||
name = "tokio-native-tls"
|
||||
version = "0.3.1"
|
||||
|
|
|
|||
17
Cargo.toml
17
Cargo.toml
|
|
@ -42,7 +42,7 @@ markdown = ["iced_widget/markdown"]
|
|||
# Enables lazy widgets
|
||||
lazy = ["iced_widget/lazy"]
|
||||
# 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
|
||||
thread-pool = ["iced_futures/thread-pool"]
|
||||
# Enables `tokio` as the `executor::Default` on native platforms
|
||||
|
|
@ -71,6 +71,7 @@ unconditional-rendering = ["iced_winit/unconditional-rendering"]
|
|||
sipper = ["iced_runtime/sipper"]
|
||||
|
||||
[dependencies]
|
||||
iced_debug.workspace = true
|
||||
iced_core.workspace = true
|
||||
iced_futures.workspace = true
|
||||
iced_renderer.workspace = true
|
||||
|
|
@ -79,6 +80,9 @@ iced_widget.workspace = true
|
|||
iced_winit.features = ["program"]
|
||||
iced_winit.workspace = true
|
||||
|
||||
iced_devtools.workspace = true
|
||||
iced_devtools.optional = true
|
||||
|
||||
iced_highlighter.workspace = true
|
||||
iced_highlighter.optional = true
|
||||
|
||||
|
|
@ -108,10 +112,14 @@ strip = "debuginfo"
|
|||
|
||||
[workspace]
|
||||
members = [
|
||||
"beacon",
|
||||
"core",
|
||||
"debug",
|
||||
"devtools",
|
||||
"futures",
|
||||
"graphics",
|
||||
"highlighter",
|
||||
"program",
|
||||
"renderer",
|
||||
"runtime",
|
||||
"test",
|
||||
|
|
@ -135,10 +143,14 @@ rust-version = "1.85"
|
|||
|
||||
[workspace.dependencies]
|
||||
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_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_graphics = { version = "0.14.0-dev", path = "graphics" }
|
||||
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_runtime = { version = "0.14.0-dev", path = "runtime" }
|
||||
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_winit = { version = "0.14.0-dev", path = "winit" }
|
||||
|
||||
bincode = "1.3"
|
||||
bitflags = "2.0"
|
||||
bytemuck = { version = "1.0", features = ["derive"] }
|
||||
bytes = "1.6"
|
||||
|
|
@ -172,6 +185,8 @@ qrcode = { version = "0.13", default-features = false }
|
|||
raw-window-handle = "0.6"
|
||||
resvg = "0.42"
|
||||
rustc-hash = "2.0"
|
||||
serde = "1.0"
|
||||
semver = "1.0"
|
||||
sha2 = "0.10"
|
||||
sipper = "0.1"
|
||||
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());
|
||||
|
||||
let submission = renderer.present::<&str>(
|
||||
let submission = renderer.present(
|
||||
Some(Color::BLACK),
|
||||
format,
|
||||
&texture_view,
|
||||
&viewport,
|
||||
&[],
|
||||
);
|
||||
|
||||
let _ = device.poll(wgpu::Maintain::WaitForSubmissionIndex(submission));
|
||||
|
|
|
|||
|
|
@ -31,3 +31,7 @@ web-time.workspace = true
|
|||
|
||||
dark-light.workspace = 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.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Color {
|
||||
/// Red component, 0.0 - 1.0
|
||||
pub r: f32,
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ pub struct Settings {
|
|||
/// Enabling it can produce a smoother result in some widgets, like the
|
||||
/// `canvas` widget, at a performance cost.
|
||||
///
|
||||
/// By default, it is disabled.
|
||||
/// By default, it is enabled.
|
||||
pub antialiasing: bool,
|
||||
}
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ impl Default for Settings {
|
|||
fonts: Vec::new(),
|
||||
default_font: Font::default(),
|
||||
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)]
|
||||
pub struct Style {
|
||||
/// The background [`Color`] of the application.
|
||||
|
|
@ -262,16 +262,27 @@ pub struct Style {
|
|||
pub text_color: Color,
|
||||
}
|
||||
|
||||
/// The default blank style of a [`Theme`].
|
||||
/// The default blank style of a theme.
|
||||
pub trait Base {
|
||||
/// Returns the default base [`Style`] of a [`Theme`].
|
||||
/// Returns the default base [`Style`] of a theme.
|
||||
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 {
|
||||
fn base(&self) -> Style {
|
||||
default(self)
|
||||
}
|
||||
|
||||
fn palette(&self) -> Option<Palette> {
|
||||
Some(self.palette())
|
||||
}
|
||||
}
|
||||
|
||||
/// The default [`Style`] of a built-in [`Theme`].
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use std::sync::LazyLock;
|
|||
|
||||
/// A color palette.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Palette {
|
||||
/// The background [`Color`] of the [`Palette`].
|
||||
pub background: Color,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
pub use web_time::Duration;
|
||||
pub use web_time::Instant;
|
||||
pub use web_time::SystemTime;
|
||||
|
||||
/// Creates a [`Duration`] representing the given amount of milliseconds.
|
||||
pub fn milliseconds(milliseconds: u64) -> Duration {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use std::sync::atomic::{self, AtomicU64};
|
|||
|
||||
/// The id of the window.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Id(u64);
|
||||
|
||||
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};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::application("Arc - Iced", Arc::update, Arc::view)
|
||||
iced::application(Arc::default, Arc::update, Arc::view)
|
||||
.subscription(Arc::subscription)
|
||||
.theme(|_| Theme::Dark)
|
||||
.antialiasing(true)
|
||||
.run()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,8 @@ use iced::widget::{button, container, horizontal_space, hover, right};
|
|||
use iced::{Element, Theme};
|
||||
|
||||
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)
|
||||
.antialiasing(true)
|
||||
.run()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ use iced::{Center, Element, Fill, FillPortion, Font, Task, Theme};
|
|||
pub fn main() -> iced::Result {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
iced::application("Changelog Generator", Generator::update, Generator::view)
|
||||
iced::application(Generator::new, Generator::update, Generator::view)
|
||||
.theme(Generator::theme)
|
||||
.run_with(Generator::new)
|
||||
.run()
|
||||
}
|
||||
|
||||
enum Generator {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use iced::{Element, Font};
|
|||
const ICON_FONT: Font = Font::with_name("icons");
|
||||
|
||||
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())
|
||||
.run()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,10 +11,9 @@ use iced::{
|
|||
pub fn main() -> iced::Result {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
iced::application("Clock - Iced", Clock::update, Clock::view)
|
||||
iced::application(Clock::default, Clock::update, Clock::view)
|
||||
.subscription(Clock::subscription)
|
||||
.theme(Clock::theme)
|
||||
.antialiasing(true)
|
||||
.run()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,13 +12,12 @@ use std::ops::RangeInclusive;
|
|||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::application(
|
||||
"Color Palette - Iced",
|
||||
ColorPalette::default,
|
||||
ColorPalette::update,
|
||||
ColorPalette::view,
|
||||
)
|
||||
.theme(ColorPalette::theme)
|
||||
.default_font(Font::MONOSPACE)
|
||||
.antialiasing(true)
|
||||
.run()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use iced::widget::{
|
|||
use iced::{Center, Element, Fill};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::run("Combo Box - Iced", Example::update, Example::view)
|
||||
iced::run(Example::update, Example::view)
|
||||
}
|
||||
|
||||
struct Example {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use iced::Center;
|
|||
use iced::widget::{Column, button, column, text};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::run("A cool counter", Counter::update, Counter::view)
|
||||
iced::run(Counter::update, Counter::view)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ use iced::widget::{center, column, slider, text};
|
|||
use iced::{Center, Color, Element, Shadow, Vector};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::run("Custom Quad - Iced", Example::update, Example::view)
|
||||
iced::run(Example::update, Example::view)
|
||||
}
|
||||
|
||||
struct Example {
|
||||
|
|
|
|||
|
|
@ -9,11 +9,7 @@ use iced::window;
|
|||
use iced::{Center, Color, Element, Fill, Subscription};
|
||||
|
||||
fn main() -> iced::Result {
|
||||
iced::application(
|
||||
"Custom Shader - Iced",
|
||||
IcedCubes::update,
|
||||
IcedCubes::view,
|
||||
)
|
||||
iced::application(IcedCubes::default, IcedCubes::update, IcedCubes::view)
|
||||
.subscription(IcedCubes::subscription)
|
||||
.run()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ use iced::widget::{center, column, slider, text};
|
|||
use iced::{Center, Element};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::run("Custom Widget - Iced", Example::update, Example::view)
|
||||
iced::run(Example::update, Example::view)
|
||||
}
|
||||
|
||||
struct Example {
|
||||
|
|
|
|||
|
|
@ -7,12 +7,7 @@ use iced::widget::{Column, button, center, column, progress_bar, text};
|
|||
use iced::{Center, Element, Function, Right, Task};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::application(
|
||||
"Download Progress - Iced",
|
||||
Example::update,
|
||||
Example::view,
|
||||
)
|
||||
.run()
|
||||
iced::application(Example::default, Example::update, Example::view).run()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
|||
|
|
@ -12,11 +12,11 @@ use std::path::{Path, PathBuf};
|
|||
use std::sync::Arc;
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::application("Editor - Iced", Editor::update, Editor::view)
|
||||
iced::application(Editor::new, Editor::update, Editor::view)
|
||||
.theme(Editor::theme)
|
||||
.font(include_bytes!("../fonts/icons.ttf").as_slice())
|
||||
.default_font(Font::MONOSPACE)
|
||||
.run_with(Editor::new)
|
||||
.run()
|
||||
}
|
||||
|
||||
struct Editor {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use iced::window;
|
|||
use iced::{Center, Element, Fill, Subscription, Task};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::application("Events - Iced", Events::update, Events::view)
|
||||
iced::application(Events::default, Events::update, Events::view)
|
||||
.subscription(Events::subscription)
|
||||
.exit_on_close_request(false)
|
||||
.run()
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use iced::window;
|
|||
use iced::{Center, Element, Task};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::application("Exit - Iced", Exit::update, Exit::view).run()
|
||||
iced::run(Exit::update, Exit::view)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use iced::{
|
|||
};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::application("Ferris - Iced", Image::update, Image::view)
|
||||
iced::application(Image::default, Image::update, Image::view)
|
||||
.subscription(Image::subscription)
|
||||
.theme(|_| Theme::TokyoNight)
|
||||
.run()
|
||||
|
|
|
|||
|
|
@ -21,10 +21,10 @@ use iced::{
|
|||
use std::collections::HashMap;
|
||||
|
||||
fn main() -> iced::Result {
|
||||
iced::application("Gallery - Iced", Gallery::update, Gallery::view)
|
||||
iced::application(Gallery::new, Gallery::update, Gallery::view)
|
||||
.subscription(Gallery::subscription)
|
||||
.theme(Gallery::theme)
|
||||
.run_with(Gallery::new)
|
||||
.run()
|
||||
}
|
||||
|
||||
struct Gallery {
|
||||
|
|
|
|||
|
|
@ -14,14 +14,9 @@ use iced::{Center, Element, Fill, Function, Subscription, Task, Theme};
|
|||
pub fn main() -> iced::Result {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
iced::application(
|
||||
"Game of Life - Iced",
|
||||
GameOfLife::update,
|
||||
GameOfLife::view,
|
||||
)
|
||||
iced::application(GameOfLife::default, GameOfLife::update, GameOfLife::view)
|
||||
.subscription(GameOfLife::subscription)
|
||||
.theme(|_| Theme::Dark)
|
||||
.antialiasing(true)
|
||||
.centered()
|
||||
.run()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ use iced::widget::{center_x, center_y, column, scrollable};
|
|||
use rainbow::rainbow;
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::run("Custom 2D Geometry - Iced", |_: &mut _, _| {}, view)
|
||||
iced::run((), view)
|
||||
}
|
||||
|
||||
fn view(_state: &()) -> Element<'_, ()> {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use iced::{Center, Color, Element, Fill, Radians, Theme, color};
|
|||
pub fn main() -> iced::Result {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
iced::application("Gradient - Iced", Gradient::update, Gradient::view)
|
||||
iced::application(Gradient::default, Gradient::update, Gradient::view)
|
||||
.style(Gradient::style)
|
||||
.transparent(true)
|
||||
.run()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
use iced_wgpu::Renderer;
|
||||
use iced_widget::{bottom, column, row, slider, text, text_input};
|
||||
use iced_winit::core::{Color, Element, Theme};
|
||||
use iced_winit::runtime::{Program, Task};
|
||||
|
||||
pub struct Controls {
|
||||
background_color: Color,
|
||||
|
|
@ -27,12 +26,8 @@ impl Controls {
|
|||
}
|
||||
}
|
||||
|
||||
impl Program for Controls {
|
||||
type Theme = Theme;
|
||||
type Message = Message;
|
||||
type Renderer = Renderer;
|
||||
|
||||
fn update(&mut self, message: Message) -> Task<Message> {
|
||||
impl Controls {
|
||||
pub fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::BackgroundColorChanged(color) => {
|
||||
self.background_color = color;
|
||||
|
|
@ -41,11 +36,9 @@ impl Program for Controls {
|
|||
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 sliders = row![
|
||||
|
|
|
|||
|
|
@ -10,10 +10,11 @@ use iced_winit::Clipboard;
|
|||
use iced_winit::conversion;
|
||||
use iced_winit::core::mouse;
|
||||
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::runtime::Debug;
|
||||
use iced_winit::runtime::program;
|
||||
use iced_winit::runtime::user_interface::{self, UserInterface};
|
||||
use iced_winit::winit;
|
||||
|
||||
use winit::{
|
||||
|
|
@ -41,13 +42,14 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
|||
format: wgpu::TextureFormat,
|
||||
renderer: Renderer,
|
||||
scene: Scene,
|
||||
state: program::State<Controls>,
|
||||
cursor_position: Option<winit::dpi::PhysicalPosition<f64>>,
|
||||
controls: Controls,
|
||||
events: Vec<Event>,
|
||||
cursor: mouse::Cursor,
|
||||
cache: user_interface::Cache,
|
||||
clipboard: Clipboard,
|
||||
viewport: Viewport,
|
||||
modifiers: ModifiersState,
|
||||
resized: bool,
|
||||
debug: Debug,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -143,9 +145,8 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
|||
let controls = Controls::new();
|
||||
|
||||
// Initialize iced
|
||||
let mut debug = Debug::new();
|
||||
|
||||
let mut renderer = {
|
||||
let renderer = {
|
||||
let engine = Engine::new(
|
||||
&adapter,
|
||||
device.clone(),
|
||||
|
|
@ -157,13 +158,6 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
|||
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
|
||||
event_loop.set_control_flow(ControlFlow::Wait);
|
||||
|
||||
|
|
@ -175,13 +169,14 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
|||
surface,
|
||||
format,
|
||||
scene,
|
||||
state,
|
||||
cursor_position: None,
|
||||
controls,
|
||||
events: Vec::new(),
|
||||
cursor: mouse::Cursor::Unavailable,
|
||||
modifiers: ModifiersState::default(),
|
||||
cache: user_interface::Cache::new(),
|
||||
clipboard,
|
||||
viewport,
|
||||
resized: false,
|
||||
debug,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -200,13 +195,14 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
|||
format,
|
||||
renderer,
|
||||
scene,
|
||||
state,
|
||||
controls,
|
||||
events,
|
||||
viewport,
|
||||
cursor_position,
|
||||
cursor,
|
||||
modifiers,
|
||||
clipboard,
|
||||
cache,
|
||||
resized,
|
||||
debug,
|
||||
} = self
|
||||
else {
|
||||
return;
|
||||
|
|
@ -241,8 +237,6 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
|||
|
||||
match surface.get_current_texture() {
|
||||
Ok(frame) => {
|
||||
let program = state.program();
|
||||
|
||||
let view = frame.texture.create_view(
|
||||
&wgpu::TextureViewDescriptor::default(),
|
||||
);
|
||||
|
|
@ -256,7 +250,7 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
|||
let mut render_pass = Scene::clear(
|
||||
&view,
|
||||
&mut encoder,
|
||||
program.background_color(),
|
||||
controls.background_color(),
|
||||
);
|
||||
|
||||
// Draw the scene
|
||||
|
|
@ -267,23 +261,47 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
|||
queue.submit([encoder.finish()]);
|
||||
|
||||
// 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(
|
||||
None,
|
||||
frame.texture.format(),
|
||||
&view,
|
||||
viewport,
|
||||
&debug.overlay(),
|
||||
);
|
||||
|
||||
// Present the frame
|
||||
frame.present();
|
||||
|
||||
// Update the mouse cursor
|
||||
window.set_cursor(
|
||||
iced_winit::conversion::mouse_interaction(
|
||||
state.mouse_interaction(),
|
||||
),
|
||||
);
|
||||
window.set_cursor(conversion::mouse_interaction(
|
||||
mouse_interaction,
|
||||
));
|
||||
}
|
||||
Err(error) => match error {
|
||||
wgpu::SurfaceError::OutOfMemory => {
|
||||
|
|
@ -300,7 +318,11 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
|||
}
|
||||
}
|
||||
WindowEvent::CursorMoved { position, .. } => {
|
||||
*cursor_position = Some(position);
|
||||
*cursor =
|
||||
mouse::Cursor::Available(conversion::cursor_position(
|
||||
position,
|
||||
viewport.scale_factor(),
|
||||
));
|
||||
}
|
||||
WindowEvent::ModifiersChanged(new_modifiers) => {
|
||||
*modifiers = new_modifiers.state();
|
||||
|
|
@ -315,37 +337,42 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
|||
}
|
||||
|
||||
// Map window event to iced event
|
||||
if let Some(event) = iced_winit::conversion::window_event(
|
||||
if let Some(event) = conversion::window_event(
|
||||
event,
|
||||
window.scale_factor(),
|
||||
*modifiers,
|
||||
) {
|
||||
state.queue_event(event);
|
||||
events.push(event);
|
||||
}
|
||||
|
||||
// If there are events pending
|
||||
if !state.is_queue_empty() {
|
||||
// We update iced
|
||||
let _ = state.update(
|
||||
if !events.is_empty() {
|
||||
// We process them
|
||||
let mut interface = UserInterface::build(
|
||||
controls.view(),
|
||||
viewport.logical_size(),
|
||||
cursor_position
|
||||
.map(|p| {
|
||||
conversion::cursor_position(
|
||||
p,
|
||||
viewport.scale_factor(),
|
||||
)
|
||||
})
|
||||
.map(mouse::Cursor::Available)
|
||||
.unwrap_or(mouse::Cursor::Unavailable),
|
||||
std::mem::take(cache),
|
||||
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
|
||||
window.request_redraw();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,9 +12,10 @@ use iced::{
|
|||
};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::application(Layout::title, Layout::update, Layout::view)
|
||||
iced::application(Layout::default, Layout::update, Layout::view)
|
||||
.subscription(Layout::subscription)
|
||||
.theme(Layout::theme)
|
||||
.title(Layout::title)
|
||||
.run()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use std::collections::HashSet;
|
|||
use std::hash::Hash;
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::run("Lazy - Iced", App::update, App::view)
|
||||
iced::run(App::update, App::view)
|
||||
}
|
||||
|
||||
struct App {
|
||||
|
|
|
|||
|
|
@ -12,11 +12,10 @@ use linear::Linear;
|
|||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::application(
|
||||
"Loading Spinners - Iced",
|
||||
LoadingSpinners::default,
|
||||
LoadingSpinners::update,
|
||||
LoadingSpinners::view,
|
||||
)
|
||||
.antialiasing(true)
|
||||
.run()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use iced::{Center, Element};
|
|||
use loupe::loupe;
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::run("Loupe - Iced", Loupe::update, Loupe::view)
|
||||
iced::run(Loupe::update, Loupe::view)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@ use std::io;
|
|||
use std::sync::Arc;
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::application("Markdown - Iced", Markdown::update, Markdown::view)
|
||||
iced::application(Markdown::new, Markdown::update, Markdown::view)
|
||||
.font(icon::FONT)
|
||||
.subscription(Markdown::subscription)
|
||||
.theme(Markdown::theme)
|
||||
.run_with(Markdown::new)
|
||||
.run()
|
||||
}
|
||||
|
||||
struct Markdown {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use iced::{Bottom, Color, Element, Fill, Subscription, Task};
|
|||
use std::fmt;
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::application("Modal - Iced", App::update, App::view)
|
||||
iced::application(App::default, App::update, App::view)
|
||||
.subscription(App::subscription)
|
||||
.run()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,11 +10,12 @@ use iced::{
|
|||
use std::collections::BTreeMap;
|
||||
|
||||
fn main() -> iced::Result {
|
||||
iced::daemon(Example::title, Example::update, Example::view)
|
||||
iced::daemon(Example::new, Example::update, Example::view)
|
||||
.subscription(Example::subscription)
|
||||
.title(Example::title)
|
||||
.theme(Example::theme)
|
||||
.scale_factor(Example::scale_factor)
|
||||
.run_with(Example::new)
|
||||
.run()
|
||||
}
|
||||
|
||||
struct Example {
|
||||
|
|
|
|||
|
|
@ -12,8 +12,7 @@ use std::collections::HashMap;
|
|||
pub fn main() -> iced::Result {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
iced::application("Multitouch - Iced", Multitouch::update, Multitouch::view)
|
||||
.antialiasing(true)
|
||||
iced::application(Multitouch::default, Multitouch::update, Multitouch::view)
|
||||
.centered()
|
||||
.run()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use iced::widget::{
|
|||
use iced::{Center, Color, Element, Fill, Size, Subscription};
|
||||
|
||||
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)
|
||||
.run()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use iced::widget::{column, pick_list, scrollable, vertical_space};
|
|||
use iced::{Center, Element, Fill};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::run("Pick List - Iced", Example::update, Example::view)
|
||||
iced::run(Example::update, Example::view)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@ use iced::widget::{self, center, column, image, row, text};
|
|||
use iced::{Center, Element, Fill, Right, Task};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::application(Pokedex::title, Pokedex::update, Pokedex::view)
|
||||
.run_with(Pokedex::new)
|
||||
iced::application(Pokedex::new, Pokedex::update, Pokedex::view)
|
||||
.title(Pokedex::title)
|
||||
.run()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use iced::widget::{
|
|||
};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::run("Progress Bar - Iced", Progress::update, Progress::view)
|
||||
iced::run(Progress::update, Progress::view)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use std::ops::RangeInclusive;
|
|||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::application(
|
||||
"QR Code Generator - Iced",
|
||||
QRGenerator::default,
|
||||
QRGenerator::update,
|
||||
QRGenerator::view,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ use ::image::ColorType;
|
|||
fn main() -> iced::Result {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
iced::application("Screenshot - Iced", Example::update, Example::view)
|
||||
iced::application(Example::default, Example::update, Example::view)
|
||||
.subscription(Example::subscription)
|
||||
.run()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ static SCROLLABLE_ID: LazyLock<scrollable::Id> =
|
|||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::application(
|
||||
"Scrollable - Iced",
|
||||
ScrollableDemo::default,
|
||||
ScrollableDemo::update,
|
||||
ScrollableDemo::view,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -8,11 +8,10 @@ use std::fmt::Debug;
|
|||
|
||||
fn main() -> iced::Result {
|
||||
iced::application(
|
||||
"Sierpinski Triangle - Iced",
|
||||
SierpinskiEmulator::default,
|
||||
SierpinskiEmulator::update,
|
||||
SierpinskiEmulator::view,
|
||||
)
|
||||
.antialiasing(true)
|
||||
.run()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use iced::widget::{column, container, iced, slider, text, vertical_slider};
|
|||
use iced::{Center, Element, Fill};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::run("Slider - Iced", Slider::update, Slider::view)
|
||||
iced::run(Slider::update, Slider::view)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ pub fn main() -> iced::Result {
|
|||
tracing_subscriber::fmt::init();
|
||||
|
||||
iced::application(
|
||||
"Solar System - Iced",
|
||||
SolarSystem::default,
|
||||
SolarSystem::update,
|
||||
SolarSystem::view,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use iced::widget::{button, center, column, row, text};
|
|||
use iced::{Center, Element, Subscription, Theme};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::application("Stopwatch - Iced", Stopwatch::update, Stopwatch::view)
|
||||
iced::application(Stopwatch::default, Stopwatch::update, Stopwatch::view)
|
||||
.subscription(Stopwatch::subscription)
|
||||
.theme(Stopwatch::theme)
|
||||
.run()
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use iced::widget::{
|
|||
use iced::{Center, Element, Fill, Subscription, Theme};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::application("Styling - Iced", Styling::update, Styling::view)
|
||||
iced::application(Styling::default, Styling::update, Styling::view)
|
||||
.subscription(Styling::subscription)
|
||||
.theme(Styling::theme)
|
||||
.run()
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use iced::widget::{center, center_x, checkbox, column, svg};
|
|||
use iced::{Element, Fill, color};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::run("SVG - Iced", Tiger::update, Tiger::view)
|
||||
iced::run(Tiger::update, Tiger::view)
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
|
|
|
|||
|
|
@ -2,12 +2,7 @@ use iced::widget::{button, center, column, text};
|
|||
use iced::{Element, Task, system};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::application(
|
||||
"System Information - Iced",
|
||||
Example::update,
|
||||
Example::view,
|
||||
)
|
||||
.run_with(Example::new)
|
||||
iced::application(Example::new, Example::update, Example::view).run()
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
|
|||
|
|
@ -10,9 +10,8 @@ use std::cell::RefCell;
|
|||
pub fn main() -> iced::Result {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
iced::application("The Matrix - Iced", TheMatrix::update, TheMatrix::view)
|
||||
iced::application(TheMatrix::default, TheMatrix::update, TheMatrix::view)
|
||||
.subscription(TheMatrix::subscription)
|
||||
.antialiasing(true)
|
||||
.run()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use iced::{Center, Element, Fill, Subscription, Task};
|
|||
use toast::{Status, Toast};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::application("Toast - Iced", App::update, App::view)
|
||||
iced::application(App::default, App::update, App::view)
|
||||
.subscription(App::subscription)
|
||||
.run()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,11 +15,12 @@ pub fn main() -> iced::Result {
|
|||
#[cfg(not(target_arch = "wasm32"))]
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
iced::application(Todos::title, Todos::update, Todos::view)
|
||||
iced::application(Todos::new, Todos::update, Todos::view)
|
||||
.subscription(Todos::subscription)
|
||||
.title(Todos::title)
|
||||
.font(Todos::ICON_FONT)
|
||||
.window_size((500.0, 800.0))
|
||||
.run_with(Todos::new)
|
||||
.run()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use iced::widget::tooltip::Position;
|
|||
use iced::widget::{button, center, container, tooltip};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::run("Tooltip - Iced", Tooltip::update, Tooltip::view)
|
||||
iced::run(Tooltip::update, Tooltip::view)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ pub fn main() -> iced::Result {
|
|||
#[cfg(not(target_arch = "wasm32"))]
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
iced::application(Tour::title, Tour::update, Tour::view)
|
||||
iced::application(Tour::default, Tour::update, Tour::view)
|
||||
.title(Tour::title)
|
||||
.centered()
|
||||
.run()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use iced::widget::{center, text};
|
|||
use iced::{Element, Subscription};
|
||||
|
||||
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)
|
||||
.run()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,12 +7,11 @@ use iced::{Center, Element, Fill, Point, Rectangle, Renderer, Theme, Vector};
|
|||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::application(
|
||||
"Vectorial Text - Iced",
|
||||
VectorialText::default,
|
||||
VectorialText::update,
|
||||
VectorialText::view,
|
||||
)
|
||||
.theme(|_| Theme::Dark)
|
||||
.antialiasing(true)
|
||||
.run()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use iced::{
|
|||
};
|
||||
|
||||
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)
|
||||
.theme(|_| Theme::Dark)
|
||||
.run()
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ use iced::{Center, Element, Fill, Subscription, Task, color};
|
|||
use std::sync::LazyLock;
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::application("WebSocket - Iced", WebSocket::update, WebSocket::view)
|
||||
iced::application(WebSocket::new, WebSocket::update, WebSocket::view)
|
||||
.subscription(WebSocket::subscription)
|
||||
.run_with(WebSocket::new)
|
||||
.run()
|
||||
}
|
||||
|
||||
struct WebSocket {
|
||||
|
|
|
|||
|
|
@ -73,13 +73,12 @@ pub trait Compositor: Sized {
|
|||
///
|
||||
/// [`Renderer`]: Self::Renderer
|
||||
/// [`Surface`]: Self::Surface
|
||||
fn present<T: AsRef<str>>(
|
||||
fn present(
|
||||
&mut self,
|
||||
renderer: &mut Self::Renderer,
|
||||
surface: &mut Self::Surface,
|
||||
viewport: &Viewport,
|
||||
background_color: Color,
|
||||
overlay: &[T],
|
||||
on_pre_present: impl FnOnce(),
|
||||
) -> Result<(), SurfaceError>;
|
||||
|
||||
|
|
@ -186,13 +185,12 @@ impl Compositor for () {
|
|||
}
|
||||
}
|
||||
|
||||
fn present<T: AsRef<str>>(
|
||||
fn present(
|
||||
&mut self,
|
||||
_renderer: &mut Self::Renderer,
|
||||
_surface: &mut Self::Surface,
|
||||
_viewport: &Viewport,
|
||||
_background_color: Color,
|
||||
_overlay: &[T],
|
||||
_on_pre_present: impl FnOnce(),
|
||||
) -> Result<(), SurfaceError> {
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::Antialiasing;
|
||||
use crate::core::{Font, Pixels};
|
||||
use crate::core::{self, Font, Pixels};
|
||||
|
||||
/// The settings of a renderer.
|
||||
#[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;
|
||||
use crate::graphics::compositor;
|
||||
use crate::shell;
|
||||
use crate::theme;
|
||||
use crate::window;
|
||||
use crate::{Element, Executor, Result, Settings, Subscription, Task};
|
||||
//! The definition of an iced program.
|
||||
pub use iced_graphics as graphics;
|
||||
pub use iced_runtime as runtime;
|
||||
pub use iced_runtime::core;
|
||||
pub use iced_runtime::futures;
|
||||
|
||||
/// 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
|
||||
/// methods available in the [`Program`] struct.
|
||||
/// A [`Program`] can execute asynchronous actions by returning a
|
||||
/// [`Task`] in some of its methods.
|
||||
#[allow(missing_docs)]
|
||||
pub trait Program: Sized {
|
||||
/// The state of the program.
|
||||
|
|
@ -26,6 +33,11 @@ pub trait Program: Sized {
|
|||
/// The executor of the program.
|
||||
type Executor: Executor;
|
||||
|
||||
/// Returns the unique name of the [`Program`].
|
||||
fn name() -> &'static str;
|
||||
|
||||
fn boot(&self) -> (Self::State, Task<Self::Message>);
|
||||
|
||||
fn update(
|
||||
&self,
|
||||
state: &mut Self::State,
|
||||
|
|
@ -39,7 +51,32 @@ pub trait Program: Sized {
|
|||
) -> Element<'a, Self::Message, Self::Theme, Self::Renderer>;
|
||||
|
||||
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(
|
||||
|
|
@ -60,138 +97,9 @@ pub trait Program: Sized {
|
|||
fn scale_factor(&self, _state: &Self::State, _window: window::Id) -> f64 {
|
||||
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>(
|
||||
program: P,
|
||||
title: impl Fn(&P::State, window::Id) -> String,
|
||||
|
|
@ -216,6 +124,14 @@ pub fn with_title<P: Program>(
|
|||
(self.title)(state, window)
|
||||
}
|
||||
|
||||
fn name() -> &'static str {
|
||||
P::name()
|
||||
}
|
||||
|
||||
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
||||
self.program.boot()
|
||||
}
|
||||
|
||||
fn update(
|
||||
&self,
|
||||
state: &mut Self::State,
|
||||
|
|
@ -263,6 +179,7 @@ pub fn with_title<P: Program>(
|
|||
WithTitle { program, title }
|
||||
}
|
||||
|
||||
/// Decorates a [`Program`] with the given subscription function.
|
||||
pub fn with_subscription<P: Program>(
|
||||
program: P,
|
||||
f: impl Fn(&P::State) -> Subscription<P::Message>,
|
||||
|
|
@ -289,6 +206,14 @@ pub fn with_subscription<P: Program>(
|
|||
(self.subscription)(state)
|
||||
}
|
||||
|
||||
fn name() -> &'static str {
|
||||
P::name()
|
||||
}
|
||||
|
||||
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
||||
self.program.boot()
|
||||
}
|
||||
|
||||
fn update(
|
||||
&self,
|
||||
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>(
|
||||
program: P,
|
||||
f: impl Fn(&P::State, window::Id) -> P::Theme,
|
||||
|
|
@ -363,6 +289,14 @@ pub fn with_theme<P: Program>(
|
|||
(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 {
|
||||
self.program.title(state, window)
|
||||
}
|
||||
|
|
@ -406,6 +340,7 @@ pub fn with_theme<P: Program>(
|
|||
WithTheme { program, theme: f }
|
||||
}
|
||||
|
||||
/// Decorates a [`Program`] with the given style function.
|
||||
pub fn with_style<P: Program>(
|
||||
program: P,
|
||||
f: impl Fn(&P::State, &P::Theme) -> theme::Style,
|
||||
|
|
@ -433,6 +368,14 @@ pub fn with_style<P: Program>(
|
|||
(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 {
|
||||
self.program.title(state, window)
|
||||
}
|
||||
|
|
@ -476,6 +419,7 @@ pub fn with_style<P: Program>(
|
|||
WithStyle { program, style: f }
|
||||
}
|
||||
|
||||
/// Decorates a [`Program`] with the given scale factor function.
|
||||
pub fn with_scale_factor<P: Program>(
|
||||
program: P,
|
||||
f: impl Fn(&P::State, window::Id) -> f64,
|
||||
|
|
@ -499,6 +443,14 @@ pub fn with_scale_factor<P: Program>(
|
|||
self.program.title(state, window)
|
||||
}
|
||||
|
||||
fn name() -> &'static str {
|
||||
P::name()
|
||||
}
|
||||
|
||||
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
||||
self.program.boot()
|
||||
}
|
||||
|
||||
fn update(
|
||||
&self,
|
||||
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>(
|
||||
program: P,
|
||||
) -> 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)
|
||||
}
|
||||
|
||||
fn name() -> &'static str {
|
||||
P::name()
|
||||
}
|
||||
|
||||
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
||||
self.program.boot()
|
||||
}
|
||||
|
||||
fn update(
|
||||
&self,
|
||||
state: &mut Self::State,
|
||||
|
|
@ -627,3 +588,57 @@ pub fn with_executor<P: Program, E: Executor>(
|
|||
pub trait Renderer: 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())
|
||||
}
|
||||
|
||||
fn present<T: AsRef<str>>(
|
||||
fn present(
|
||||
&mut self,
|
||||
renderer: &mut Self::Renderer,
|
||||
surface: &mut Self::Surface,
|
||||
viewport: &graphics::Viewport,
|
||||
background_color: Color,
|
||||
overlay: &[T],
|
||||
on_pre_present: impl FnOnce(),
|
||||
) -> Result<(), compositor::SurfaceError> {
|
||||
match (self, renderer, surface) {
|
||||
|
|
@ -335,7 +334,6 @@ where
|
|||
surface,
|
||||
viewport,
|
||||
background_color,
|
||||
overlay,
|
||||
on_pre_present,
|
||||
),
|
||||
(
|
||||
|
|
@ -347,7 +345,6 @@ where
|
|||
surface,
|
||||
viewport,
|
||||
background_color,
|
||||
overlay,
|
||||
on_pre_present,
|
||||
),
|
||||
_ => unreachable!(),
|
||||
|
|
|
|||
|
|
@ -13,13 +13,11 @@ keywords.workspace = true
|
|||
[lints]
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
debug = []
|
||||
multi-window = []
|
||||
|
||||
[dependencies]
|
||||
bytes.workspace = true
|
||||
iced_core.workspace = true
|
||||
iced_debug.workspace = true
|
||||
|
||||
iced_futures.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 keyboard;
|
||||
pub mod overlay;
|
||||
pub mod program;
|
||||
pub mod system;
|
||||
pub mod task;
|
||||
pub mod user_interface;
|
||||
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_debug as debug;
|
||||
pub use iced_futures as futures;
|
||||
|
||||
pub use debug::Debug;
|
||||
pub use program::Program;
|
||||
pub use task::Task;
|
||||
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`.
|
||||
#[allow(missing_debug_implementations)]
|
||||
#[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> {
|
||||
/// Creates a [`Task`] that does nothing.
|
||||
pub fn none() -> Self {
|
||||
Self(None)
|
||||
Self {
|
||||
stream: None,
|
||||
units: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [`Task`] that instantly produces the given value.
|
||||
|
|
@ -83,9 +89,16 @@ impl<T> Task<T> {
|
|||
where
|
||||
T: 'static,
|
||||
{
|
||||
Self(Some(boxed_stream(stream::select_all(
|
||||
tasks.into_iter().filter_map(|task| task.0),
|
||||
))))
|
||||
let select_all = stream::select_all(
|
||||
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.
|
||||
|
|
@ -113,21 +126,26 @@ impl<T> Task<T> {
|
|||
T: MaybeSend + 'static,
|
||||
O: MaybeSend + 'static,
|
||||
{
|
||||
Task(match self.0 {
|
||||
Task {
|
||||
stream: match self.stream {
|
||||
None => None,
|
||||
Some(stream) => {
|
||||
Some(boxed_stream(stream.flat_map(move |action| {
|
||||
match action.output() {
|
||||
Ok(output) => f(output)
|
||||
.0
|
||||
.unwrap_or_else(|| boxed_stream(stream::empty())),
|
||||
Err(action) => {
|
||||
boxed_stream(stream::once(async move { action }))
|
||||
Ok(output) => {
|
||||
f(output).stream.unwrap_or_else(|| {
|
||||
boxed_stream(stream::empty())
|
||||
})
|
||||
}
|
||||
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.
|
||||
|
|
@ -135,11 +153,17 @@ impl<T> Task<T> {
|
|||
where
|
||||
T: 'static,
|
||||
{
|
||||
match self.0 {
|
||||
match self.stream {
|
||||
None => task,
|
||||
Some(first) => match task.0 {
|
||||
None => Task(Some(first)),
|
||||
Some(second) => Task(Some(boxed_stream(first.chain(second)))),
|
||||
Some(first) => match task.stream {
|
||||
None => Self {
|
||||
stream: Some(first),
|
||||
units: self.units,
|
||||
},
|
||||
Some(second) => Self {
|
||||
stream: Some(boxed_stream(first.chain(second))),
|
||||
units: self.units + task.units,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -149,9 +173,10 @@ impl<T> Task<T> {
|
|||
where
|
||||
T: MaybeSend + 'static,
|
||||
{
|
||||
match self.0 {
|
||||
match self.stream {
|
||||
None => Task::done(Vec::new()),
|
||||
Some(stream) => Task(Some(boxed_stream(
|
||||
Some(stream) => Task {
|
||||
stream: Some(boxed_stream(
|
||||
stream::unfold(
|
||||
(stream, Some(Vec::new())),
|
||||
move |(mut stream, outputs)| async move {
|
||||
|
|
@ -170,14 +195,17 @@ impl<T> Task<T> {
|
|||
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -197,26 +225,25 @@ impl<T> Task<T> {
|
|||
where
|
||||
T: 'static,
|
||||
{
|
||||
match self.0 {
|
||||
let (stream, handle) = match self.stream {
|
||||
Some(stream) => {
|
||||
let (stream, handle) = stream::abortable(stream);
|
||||
|
||||
(
|
||||
Self(Some(boxed_stream(stream))),
|
||||
Handle {
|
||||
internal: InternalHandle::Manual(handle),
|
||||
},
|
||||
)
|
||||
(Some(boxed_stream(stream)), InternalHandle::Manual(handle))
|
||||
}
|
||||
None => (
|
||||
Self(None),
|
||||
Handle {
|
||||
internal: InternalHandle::Manual(
|
||||
stream::AbortHandle::new_pair().0,
|
||||
None,
|
||||
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
|
||||
|
|
@ -234,7 +261,15 @@ impl<T> Task<T> {
|
|||
where
|
||||
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);
|
||||
|
||||
Task(Some(boxed_stream(
|
||||
stream::once(async move { action }).chain(
|
||||
Task {
|
||||
stream: Some(boxed_stream(stream::once(async move { action }).chain(
|
||||
receiver.into_stream().filter_map(|result| async move {
|
||||
Some(Action::Output(result.ok()?))
|
||||
}),
|
||||
),
|
||||
)))
|
||||
))),
|
||||
units: 1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [`Task`] that executes the [`Action`] returned by the closure and
|
||||
|
|
@ -387,22 +423,28 @@ where
|
|||
|
||||
let action = f(sender);
|
||||
|
||||
Task(Some(boxed_stream(
|
||||
Task {
|
||||
stream: Some(boxed_stream(
|
||||
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.
|
||||
pub fn effect<T>(action: impl Into<Action<Infallible>>) -> Task<T> {
|
||||
let action = action.into();
|
||||
|
||||
Task(Some(boxed_stream(stream::once(async move {
|
||||
Task {
|
||||
stream: Some(boxed_stream(stream::once(async move {
|
||||
action.output().expect_err("no output")
|
||||
}))))
|
||||
}))),
|
||||
units: 1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the underlying [`Stream`] of the [`Task`].
|
||||
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::text::{self, Text};
|
||||
pub use crate::renderer::graphics;
|
||||
|
||||
pub use iced_debug as debug;
|
||||
|
||||
pub use widget::Widget;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
//! use iced::Theme;
|
||||
//!
|
||||
//! pub fn main() -> iced::Result {
|
||||
//! iced::application("A counter", update, view)
|
||||
//! iced::application(u64::default, update, view)
|
||||
//! .theme(|_| Theme::Dark)
|
||||
//! .centered()
|
||||
//! .run()
|
||||
|
|
@ -31,6 +31,7 @@
|
|||
//! }
|
||||
//! ```
|
||||
use crate::program::{self, Program};
|
||||
use crate::shell;
|
||||
use crate::theme;
|
||||
use crate::window;
|
||||
use crate::{
|
||||
|
|
@ -39,14 +40,14 @@ use crate::{
|
|||
|
||||
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
|
||||
/// ```no_run,standalone_crate
|
||||
/// use iced::widget::{button, column, text, Column};
|
||||
///
|
||||
/// pub fn main() -> iced::Result {
|
||||
/// iced::application("A counter", update, view).run()
|
||||
/// iced::application(u64::default, update, view).run()
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Debug, Clone)]
|
||||
|
|
@ -68,7 +69,7 @@ use std::borrow::Cow;
|
|||
/// }
|
||||
/// ```
|
||||
pub fn application<State, Message, Theme, Renderer>(
|
||||
title: impl Title<State>,
|
||||
boot: impl Boot<State, Message>,
|
||||
update: impl Update<State, Message>,
|
||||
view: impl for<'a> self::View<'a, State, Message, Theme, Renderer>,
|
||||
) -> Application<impl Program<State = State, Message = Message, Theme = Theme>>
|
||||
|
|
@ -80,7 +81,8 @@ where
|
|||
{
|
||||
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,
|
||||
view: View,
|
||||
_state: PhantomData<State>,
|
||||
|
|
@ -89,12 +91,13 @@ where
|
|||
_renderer: PhantomData<Renderer>,
|
||||
}
|
||||
|
||||
impl<State, Message, Theme, Renderer, Update, View> Program
|
||||
for Instance<State, Message, Theme, Renderer, Update, View>
|
||||
impl<State, Message, Theme, Renderer, Boot, Update, View> Program
|
||||
for Instance<State, Message, Theme, Renderer, Boot, Update, View>
|
||||
where
|
||||
Message: Send + std::fmt::Debug + 'static,
|
||||
Theme: Default + theme::Base,
|
||||
Renderer: program::Renderer,
|
||||
Boot: self::Boot<State, Message>,
|
||||
Update: self::Update<State, Message>,
|
||||
View: for<'a> self::View<'a, State, Message, Theme, Renderer>,
|
||||
{
|
||||
|
|
@ -104,6 +107,16 @@ where
|
|||
type Renderer = Renderer;
|
||||
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(
|
||||
&self,
|
||||
state: &mut Self::State,
|
||||
|
|
@ -123,6 +136,7 @@ where
|
|||
|
||||
Application {
|
||||
raw: Instance {
|
||||
boot,
|
||||
update,
|
||||
view,
|
||||
_state: PhantomData,
|
||||
|
|
@ -133,7 +147,6 @@ where
|
|||
settings: Settings::default(),
|
||||
window: window::Settings::default(),
|
||||
}
|
||||
.title(title)
|
||||
}
|
||||
|
||||
/// The underlying definition and configuration of an iced application.
|
||||
|
|
@ -152,28 +165,17 @@ pub struct Application<P: Program> {
|
|||
|
||||
impl<P: Program> Application<P> {
|
||||
/// 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
|
||||
where
|
||||
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.
|
||||
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, Some(self.window), initialize)
|
||||
#[cfg(not(feature = "debug"))]
|
||||
let program = self.raw;
|
||||
|
||||
Ok(shell::run(program, self.settings, Some(self.window))?)
|
||||
}
|
||||
|
||||
/// 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`].
|
||||
pub(crate) fn title(
|
||||
pub fn title(
|
||||
self,
|
||||
title: impl Title<P::State>,
|
||||
) -> 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`].
|
||||
///
|
||||
/// This trait is implemented both for `&static str` and
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
//! Create and run daemons that run in the background.
|
||||
use crate::application;
|
||||
use crate::program::{self, Program};
|
||||
use crate::shell;
|
||||
use crate::theme;
|
||||
use crate::window;
|
||||
use crate::{Element, Executor, Font, Result, Settings, Subscription, Task};
|
||||
|
||||
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
|
||||
/// instead until a [`Task`] from [`window::open`] is returned by its update logic.
|
||||
|
|
@ -18,7 +19,7 @@ use std::borrow::Cow;
|
|||
///
|
||||
/// [`exit`]: crate::exit
|
||||
pub fn daemon<State, Message, Theme, Renderer>(
|
||||
title: impl Title<State>,
|
||||
boot: impl application::Boot<State, Message>,
|
||||
update: impl application::Update<State, Message>,
|
||||
view: impl for<'a> self::View<'a, State, Message, Theme, Renderer>,
|
||||
) -> Daemon<impl Program<State = State, Message = Message, Theme = Theme>>
|
||||
|
|
@ -30,7 +31,8 @@ where
|
|||
{
|
||||
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,
|
||||
view: View,
|
||||
_state: PhantomData<State>,
|
||||
|
|
@ -39,12 +41,13 @@ where
|
|||
_renderer: PhantomData<Renderer>,
|
||||
}
|
||||
|
||||
impl<State, Message, Theme, Renderer, Update, View> Program
|
||||
for Instance<State, Message, Theme, Renderer, Update, View>
|
||||
impl<State, Message, Theme, Renderer, Boot, Update, View> Program
|
||||
for Instance<State, Message, Theme, Renderer, Boot, Update, View>
|
||||
where
|
||||
Message: Send + std::fmt::Debug + 'static,
|
||||
Theme: Default + theme::Base,
|
||||
Renderer: program::Renderer,
|
||||
Boot: application::Boot<State, Message>,
|
||||
Update: application::Update<State, Message>,
|
||||
View: for<'a> self::View<'a, State, Message, Theme, Renderer>,
|
||||
{
|
||||
|
|
@ -54,6 +57,16 @@ where
|
|||
type Renderer = Renderer;
|
||||
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(
|
||||
&self,
|
||||
state: &mut Self::State,
|
||||
|
|
@ -73,6 +86,7 @@ where
|
|||
|
||||
Daemon {
|
||||
raw: Instance {
|
||||
boot,
|
||||
update,
|
||||
view,
|
||||
_state: PhantomData,
|
||||
|
|
@ -82,7 +96,6 @@ where
|
|||
},
|
||||
settings: Settings::default(),
|
||||
}
|
||||
.title(title)
|
||||
}
|
||||
|
||||
/// The underlying definition and configuration of an iced daemon.
|
||||
|
|
@ -100,27 +113,11 @@ pub struct Daemon<P: Program> {
|
|||
|
||||
impl<P: Program> Daemon<P> {
|
||||
/// 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
|
||||
where
|
||||
Self: 'static,
|
||||
P::State: Default,
|
||||
{
|
||||
self.raw.run(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)
|
||||
Ok(shell::run(self.raw, self.settings, None)?)
|
||||
}
|
||||
|
||||
/// 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`].
|
||||
pub(crate) fn title(
|
||||
pub fn title(
|
||||
self,
|
||||
title: impl Title<P::State>,
|
||||
) -> Daemon<
|
||||
|
|
|
|||
21
src/lib.rs
21
src/lib.rs
|
|
@ -31,7 +31,7 @@
|
|||
//!
|
||||
//! ```no_run,standalone_crate
|
||||
//! pub fn main() -> iced::Result {
|
||||
//! iced::run("A cool counter", update, view)
|
||||
//! iced::run(update, view)
|
||||
//! }
|
||||
//! # fn update(state: &mut (), message: ()) {}
|
||||
//! # fn view(state: &()) -> iced::Element<()> { iced::widget::text("").into() }
|
||||
|
|
@ -198,16 +198,20 @@
|
|||
//! calling [`run`]:
|
||||
//!
|
||||
//! ```no_run,standalone_crate
|
||||
//! # #[derive(Default)]
|
||||
//! # struct State;
|
||||
//! use iced::Theme;
|
||||
//!
|
||||
//! pub fn main() -> iced::Result {
|
||||
//! iced::application("A cool application", update, view)
|
||||
//! iced::application(new, update, view)
|
||||
//! .theme(theme)
|
||||
//! .run()
|
||||
//! }
|
||||
//!
|
||||
//! fn new() -> State {
|
||||
//! // ...
|
||||
//! # State
|
||||
//! }
|
||||
//!
|
||||
//! fn theme(state: &State) -> Theme {
|
||||
//! Theme::TokyoNight
|
||||
//! }
|
||||
|
|
@ -335,7 +339,6 @@
|
|||
//! You will need to define a `subscription` function and use the [`Application`] builder:
|
||||
//!
|
||||
//! ```no_run,standalone_crate
|
||||
//! # #[derive(Default)]
|
||||
//! # struct State;
|
||||
//! use iced::window;
|
||||
//! use iced::{Size, Subscription};
|
||||
|
|
@ -346,7 +349,7 @@
|
|||
//! }
|
||||
//!
|
||||
//! pub fn main() -> iced::Result {
|
||||
//! iced::application("A cool application", update, view)
|
||||
//! iced::application(new, update, view)
|
||||
//! .subscription(subscription)
|
||||
//! .run()
|
||||
//! }
|
||||
|
|
@ -354,6 +357,7 @@
|
|||
//! fn subscription(state: &State) -> Subscription<Message> {
|
||||
//! window::resize_events().map(|(_id, size)| Message::WindowResized(size))
|
||||
//! }
|
||||
//! # fn new() -> State { State }
|
||||
//! # fn update(state: &mut State, message: Message) {}
|
||||
//! # 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_winit as shell;
|
||||
use iced_winit::core;
|
||||
use iced_winit::program;
|
||||
use iced_winit::runtime;
|
||||
|
||||
pub use iced_futures::futures;
|
||||
|
|
@ -499,7 +504,6 @@ pub use iced_highlighter as highlighter;
|
|||
pub use iced_renderer::wgpu::wgpu;
|
||||
|
||||
mod error;
|
||||
mod program;
|
||||
|
||||
pub mod application;
|
||||
pub mod daemon;
|
||||
|
|
@ -660,7 +664,7 @@ pub type Result = std::result::Result<(), Error>;
|
|||
/// use iced::widget::{button, column, text, Column};
|
||||
///
|
||||
/// pub fn main() -> iced::Result {
|
||||
/// iced::run("A counter", update, view)
|
||||
/// iced::run(update, view)
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Debug, Clone)]
|
||||
|
|
@ -682,7 +686,6 @@ pub type Result = std::result::Result<(), Error>;
|
|||
/// }
|
||||
/// ```
|
||||
pub fn run<State, Message, Theme, Renderer>(
|
||||
title: impl application::Title<State> + 'static,
|
||||
update: impl application::Update<State, Message> + 'static,
|
||||
view: impl for<'a> application::View<'a, State, Message, Theme, Renderer>
|
||||
+ 'static,
|
||||
|
|
@ -693,5 +696,5 @@ where
|
|||
Theme: Default + theme::Base + '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()
|
||||
}
|
||||
|
||||
pub fn draw<T: AsRef<str>>(
|
||||
pub fn draw(
|
||||
&mut self,
|
||||
pixels: &mut tiny_skia::PixmapMut<'_>,
|
||||
clip_mask: &mut tiny_skia::Mask,
|
||||
viewport: &Viewport,
|
||||
damage: &[Rectangle],
|
||||
background_color: Color,
|
||||
overlay: &[T],
|
||||
) {
|
||||
let physical_size = viewport.physical_size();
|
||||
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();
|
||||
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -107,13 +107,12 @@ impl crate::graphics::Compositor for Compositor {
|
|||
}
|
||||
}
|
||||
|
||||
fn present<T: AsRef<str>>(
|
||||
fn present(
|
||||
&mut self,
|
||||
renderer: &mut Self::Renderer,
|
||||
surface: &mut Self::Surface,
|
||||
viewport: &Viewport,
|
||||
background_color: Color,
|
||||
overlay: &[T],
|
||||
on_pre_present: impl FnOnce(),
|
||||
) -> Result<(), compositor::SurfaceError> {
|
||||
present(
|
||||
|
|
@ -121,7 +120,6 @@ impl crate::graphics::Compositor for Compositor {
|
|||
surface,
|
||||
viewport,
|
||||
background_color,
|
||||
overlay,
|
||||
on_pre_present,
|
||||
)
|
||||
}
|
||||
|
|
@ -147,12 +145,11 @@ pub fn new<W: compositor::Window>(
|
|||
Compositor { context, settings }
|
||||
}
|
||||
|
||||
pub fn present<T: AsRef<str>>(
|
||||
pub fn present(
|
||||
renderer: &mut Renderer,
|
||||
surface: &mut Surface,
|
||||
viewport: &Viewport,
|
||||
background_color: Color,
|
||||
overlay: &[T],
|
||||
on_pre_present: impl FnOnce(),
|
||||
) -> Result<(), compositor::SurfaceError> {
|
||||
let physical_size = viewport.physical_size();
|
||||
|
|
@ -211,7 +208,6 @@ pub fn present<T: AsRef<str>>(
|
|||
viewport,
|
||||
&damage,
|
||||
background_color,
|
||||
overlay,
|
||||
);
|
||||
|
||||
on_pre_present();
|
||||
|
|
@ -231,7 +227,7 @@ pub fn screenshot(
|
|||
let mut clip_mask = tiny_skia::Mask::new(size.width, size.height)
|
||||
.expect("Create clip mask");
|
||||
|
||||
renderer.draw::<&str>(
|
||||
renderer.draw(
|
||||
&mut tiny_skia::PixmapMut::from_bytes(
|
||||
bytemuck::cast_slice_mut(&mut offscreen_buffer),
|
||||
size.width,
|
||||
|
|
@ -245,7 +241,6 @@ pub fn screenshot(
|
|||
size.height as f32,
|
||||
))],
|
||||
background_color,
|
||||
&[],
|
||||
);
|
||||
|
||||
offscreen_buffer.iter().fold(
|
||||
|
|
|
|||
|
|
@ -63,7 +63,6 @@ pub use geometry::Geometry;
|
|||
use crate::core::renderer;
|
||||
use crate::core::{
|
||||
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
|
||||
Vector,
|
||||
};
|
||||
use crate::graphics::Viewport;
|
||||
use crate::graphics::text::{Editor, Paragraph};
|
||||
|
|
@ -164,16 +163,13 @@ impl Renderer {
|
|||
encoder
|
||||
}
|
||||
|
||||
pub fn present<T: AsRef<str>>(
|
||||
pub fn present(
|
||||
&mut self,
|
||||
clear_color: Option<Color>,
|
||||
_format: wgpu::TextureFormat,
|
||||
frame: &wgpu::TextureView,
|
||||
viewport: &Viewport,
|
||||
overlay: &[T],
|
||||
) -> wgpu::SubmissionIndex {
|
||||
self.draw_overlay(overlay, viewport);
|
||||
|
||||
let encoder = self.draw(clear_color, frame, viewport);
|
||||
|
||||
self.staging_belt.finish();
|
||||
|
|
@ -577,50 +573,6 @@ impl Renderer {
|
|||
|
||||
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 {
|
||||
|
|
@ -716,7 +668,7 @@ impl core::text::Renderer for Renderer {
|
|||
impl core::image::Renderer for Renderer {
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
@ -728,7 +680,7 @@ impl core::image::Renderer for Renderer {
|
|||
|
||||
#[cfg(feature = "svg")]
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
@ -760,7 +712,7 @@ impl graphics::geometry::Renderer for Renderer {
|
|||
type Geometry = Geometry;
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -210,12 +210,11 @@ pub async fn new<W: compositor::Window>(
|
|||
}
|
||||
|
||||
/// Presents the given primitives with the given [`Compositor`].
|
||||
pub fn present<T: AsRef<str>>(
|
||||
pub fn present(
|
||||
renderer: &mut Renderer,
|
||||
surface: &mut wgpu::Surface<'static>,
|
||||
viewport: &Viewport,
|
||||
background_color: Color,
|
||||
overlay: &[T],
|
||||
on_pre_present: impl FnOnce(),
|
||||
) -> Result<(), compositor::SurfaceError> {
|
||||
match surface.get_current_texture() {
|
||||
|
|
@ -229,7 +228,6 @@ pub fn present<T: AsRef<str>>(
|
|||
frame.texture.format(),
|
||||
view,
|
||||
viewport,
|
||||
overlay,
|
||||
);
|
||||
|
||||
// Present the frame
|
||||
|
|
@ -342,13 +340,12 @@ impl graphics::Compositor for Compositor {
|
|||
}
|
||||
}
|
||||
|
||||
fn present<T: AsRef<str>>(
|
||||
fn present(
|
||||
&mut self,
|
||||
renderer: &mut Self::Renderer,
|
||||
surface: &mut Self::Surface,
|
||||
viewport: &Viewport,
|
||||
background_color: Color,
|
||||
overlay: &[T],
|
||||
on_pre_present: impl FnOnce(),
|
||||
) -> Result<(), compositor::SurfaceError> {
|
||||
present(
|
||||
|
|
@ -356,7 +353,6 @@ impl graphics::Compositor for Compositor {
|
|||
surface,
|
||||
viewport,
|
||||
background_color,
|
||||
overlay,
|
||||
on_pre_present,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2028,7 +2028,7 @@ pub fn focus_next<T>() -> Task<T> {
|
|||
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>(
|
||||
widget: impl Into<Element<'a, Message, Theme, Renderer>>,
|
||||
) -> MouseArea<'a, Message, Theme, Renderer>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ workspace = true
|
|||
|
||||
[features]
|
||||
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
|
||||
debug = ["iced_runtime/debug"]
|
||||
debug = ["iced_debug/enable"]
|
||||
system = ["sysinfo"]
|
||||
program = []
|
||||
x11 = ["winit/x11"]
|
||||
|
|
@ -25,9 +25,8 @@ wayland-csd-adwaita = ["winit/wayland-csd-adwaita"]
|
|||
unconditional-rendering = []
|
||||
|
||||
[dependencies]
|
||||
iced_futures.workspace = true
|
||||
iced_graphics.workspace = true
|
||||
iced_runtime.workspace = true
|
||||
iced_debug.workspace = true
|
||||
iced_program.workspace = true
|
||||
|
||||
log.workspace = true
|
||||
rustc-hash.workspace = true
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ pub enum Error {
|
|||
}
|
||||
|
||||
impl From<graphics::Error> for Error {
|
||||
fn from(error: iced_graphics::Error) -> Error {
|
||||
fn from(error: graphics::Error) -> 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