Merge pull request #2879 from iced-rs/beacon

`comet` debugger and `devtools` foundations
This commit is contained in:
Héctor 2025-04-08 13:29:27 +02:00 committed by GitHub
commit 97498aaddc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
105 changed files with 3462 additions and 3097 deletions

70
Cargo.lock generated
View file

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

View file

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

View file

@ -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));

View file

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

View file

@ -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,

View file

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

View file

@ -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`].

View file

@ -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,

View file

@ -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 {

View file

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

View file

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

View file

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

View file

@ -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 {

View file

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

View file

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

View file

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

View file

@ -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 {

View file

@ -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)]

View file

@ -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 {

View file

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

View file

@ -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 {

View file

@ -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)]

View file

@ -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 {

View file

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

View file

@ -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)]

View file

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

View file

@ -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 {

View file

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

View file

@ -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<'_, ()> {

View file

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

View file

@ -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![

View file

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

View file

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

View file

@ -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 {

View file

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

View file

@ -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)]

View file

@ -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 {

View file

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

View file

@ -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 {

View file

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

View file

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

View file

@ -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)]

View file

@ -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)]

View file

@ -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)]

View file

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

View file

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

View file

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

View file

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

View file

@ -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)]

View file

@ -22,7 +22,7 @@ pub fn main() -> iced::Result {
tracing_subscriber::fmt::init();
iced::application(
"Solar System - Iced",
SolarSystem::default,
SolarSystem::update,
SolarSystem::view,
)

View file

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

View file

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

View file

@ -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)]

View file

@ -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)]

View file

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

View file

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

View file

@ -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)]

View file

@ -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)]

View file

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

View file

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

View file

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

View file

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

View file

@ -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 {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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;

View file

@ -1,6 +0,0 @@
//! A multi-window application.
pub mod program;
pub mod state;
pub use program::Program;
pub use state::State;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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;

View file

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

View file

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

View file

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

View file

@ -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 &region 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();

View file

@ -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(

View file

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

View file

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

View file

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

View file

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

View file

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