Merge branch 'master' into beacon

This commit is contained in:
Héctor Ramón Jiménez 2025-04-05 18:20:31 +02:00
commit 3f67044977
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
62 changed files with 713 additions and 780 deletions

View file

@ -23,5 +23,5 @@ jobs:
sudo apt-get install -y libxkbcommon-dev libgtk-3-dev sudo apt-get install -y libxkbcommon-dev libgtk-3-dev
- name: Run tests - name: Run tests
run: | run: |
cargo test --verbose --workspace cargo test --verbose --workspace -- --ignored
cargo test --verbose --workspace --all-features cargo test --verbose --workspace --all-features -- --ignored

753
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -22,7 +22,7 @@ all-features = true
maintenance = { status = "actively-developed" } maintenance = { status = "actively-developed" }
[features] [features]
default = ["wgpu", "tiny-skia", "auto-detect-theme"] default = ["wgpu", "tiny-skia", "web-colors", "auto-detect-theme", "thread-pool"]
# Enables the `wgpu` GPU-accelerated renderer backend # Enables the `wgpu` GPU-accelerated renderer backend
wgpu = ["iced_renderer/wgpu", "iced_widget/wgpu"] wgpu = ["iced_renderer/wgpu", "iced_widget/wgpu"]
# Enables the `tiny-skia` software renderer backend # Enables the `tiny-skia` software renderer backend
@ -43,10 +43,10 @@ markdown = ["iced_widget/markdown"]
lazy = ["iced_widget/lazy"] lazy = ["iced_widget/lazy"]
# Enables a debug view in native platforms (press F12) # Enables a debug view in native platforms (press F12)
debug = ["iced_winit/debug"] debug = ["iced_winit/debug"]
# 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 # Enables `tokio` as the `executor::Default` on native platforms
tokio = ["iced_futures/tokio"] tokio = ["iced_futures/tokio"]
# Enables `async-std` as the `executor::Default` on native platforms
async-std = ["iced_futures/async-std"]
# Enables `smol` as the `executor::Default` on native platforms # Enables `smol` as the `executor::Default` on native platforms
smol = ["iced_futures/smol"] smol = ["iced_futures/smol"]
# Enables querying system information # Enables querying system information
@ -67,12 +67,15 @@ auto-detect-theme = ["iced_core/auto-detect-theme"]
strict-assertions = ["iced_renderer/strict-assertions"] strict-assertions = ["iced_renderer/strict-assertions"]
# Redraws on every runtime event, and not only when a widget requests it # Redraws on every runtime event, and not only when a widget requests it
unconditional-rendering = ["iced_winit/unconditional-rendering"] unconditional-rendering = ["iced_winit/unconditional-rendering"]
# Enables support for the `sipper` library
sipper = ["iced_runtime/sipper"]
[dependencies] [dependencies]
iced_debug.workspace = true iced_debug.workspace = true
iced_core.workspace = true iced_core.workspace = true
iced_futures.workspace = true iced_futures.workspace = true
iced_renderer.workspace = true iced_renderer.workspace = true
iced_runtime.workspace = true
iced_widget.workspace = true iced_widget.workspace = true
iced_winit.features = ["program"] iced_winit.features = ["program"]
iced_winit.workspace = true iced_winit.workspace = true
@ -151,14 +154,13 @@ iced_wgpu = { version = "0.14.0-dev", path = "wgpu" }
iced_widget = { version = "0.14.0-dev", path = "widget" } iced_widget = { version = "0.14.0-dev", path = "widget" }
iced_winit = { version = "0.14.0-dev", path = "winit" } iced_winit = { version = "0.14.0-dev", path = "winit" }
async-std = "1.0"
bincode = "1.3" bincode = "1.3"
bitflags = "2.0" bitflags = "2.0"
bytemuck = { version = "1.0", features = ["derive"] } bytemuck = { version = "1.0", features = ["derive"] }
bytes = "1.6" bytes = "1.6"
cosmic-text = "0.13" cosmic-text = "0.13"
dark-light = "2.0" dark-light = "2.0"
futures = "0.3" futures = { version = "0.3", default-features = false }
glam = "0.25" glam = "0.25"
cryoglyph = { git = "https://github.com/iced-rs/cryoglyph.git", rev = "be2defe4a13fd7c97c6f4c81e8e085463eb578dc" } cryoglyph = { git = "https://github.com/iced-rs/cryoglyph.git", rev = "be2defe4a13fd7c97c6f4c81e8e085463eb578dc" }
guillotiere = "0.6" guillotiere = "0.6"
@ -172,7 +174,6 @@ lyon = "1.0"
lyon_path = "1.0" lyon_path = "1.0"
num-traits = "0.2" num-traits = "0.2"
ouroboros = "0.18" ouroboros = "0.18"
palette = "0.7"
png = "0.17" png = "0.17"
pulldown-cmark = "0.12" pulldown-cmark = "0.12"
qrcode = { version = "0.13", default-features = false } qrcode = { version = "0.13", default-features = false }
@ -183,7 +184,7 @@ serde = "1.0"
semver = "1.0" semver = "1.0"
sha2 = "0.10" sha2 = "0.10"
sipper = "0.1" sipper = "0.1"
smol = "1.0" smol = "2"
smol_str = "0.2" smol_str = "0.2"
softbuffer = "0.4" softbuffer = "0.4"
syntect = "5.1" syntect = "5.1"

View file

@ -24,7 +24,6 @@ glam.workspace = true
lilt.workspace = true lilt.workspace = true
log.workspace = true log.workspace = true
num-traits.workspace = true num-traits.workspace = true
palette.workspace = true
rustc-hash.workspace = true rustc-hash.workspace = true
smol_str.workspace = true smol_str.workspace = true
thiserror.workspace = true thiserror.workspace = true
@ -36,9 +35,3 @@ dark-light.optional = true
serde.workspace = true serde.workspace = true
serde.optional = true serde.optional = true
serde.features = ["derive"] serde.features = ["derive"]
[target.'cfg(windows)'.dependencies]
raw-window-handle.workspace = true
[dev-dependencies]
approx = "0.5"

View file

@ -1,5 +1,3 @@
use palette::rgb::{Srgb, Srgba};
/// A color in the `sRGB` color space. /// A color in the `sRGB` color space.
#[derive(Debug, Clone, Copy, PartialEq, Default)] #[derive(Debug, Clone, Copy, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
@ -243,70 +241,9 @@ macro_rules! color {
}}; }};
} }
/// Converts from palette's `Rgba` type to a [`Color`].
impl From<Srgba> for Color {
fn from(rgba: Srgba) -> Self {
Color::new(rgba.red, rgba.green, rgba.blue, rgba.alpha)
}
}
/// Converts from [`Color`] to palette's `Rgba` type.
impl From<Color> for Srgba {
fn from(c: Color) -> Self {
Srgba::new(c.r, c.g, c.b, c.a)
}
}
/// Converts from palette's `Rgb` type to a [`Color`].
impl From<Srgb> for Color {
fn from(rgb: Srgb) -> Self {
Color::new(rgb.red, rgb.green, rgb.blue, 1.0)
}
}
/// Converts from [`Color`] to palette's `Rgb` type.
impl From<Color> for Srgb {
fn from(c: Color) -> Self {
Srgb::new(c.r, c.g, c.b)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use palette::blend::Blend;
#[test]
fn srgba_traits() {
let c = Color::from_rgb(0.5, 0.4, 0.3);
// Round-trip conversion to the palette::Srgba type
let s: Srgba = c.into();
let r: Color = s.into();
assert_eq!(c, r);
}
#[test]
fn color_manipulation() {
use approx::assert_relative_eq;
let c1 = Color::from_rgb(0.5, 0.4, 0.3);
let c2 = Color::from_rgb(0.2, 0.5, 0.3);
// Convert to linear color for manipulation
let l1 = Srgba::from(c1).into_linear();
let l2 = Srgba::from(c2).into_linear();
// Take the lighter of each of the sRGB components
let lighter = l1.lighten(l2);
// Convert back to our Color
let result: Color = Srgba::from_linear(lighter).into();
assert_relative_eq!(result.r, 0.5);
assert_relative_eq!(result.g, 0.5);
assert_relative_eq!(result.b, 0.3);
assert_relative_eq!(result.a, 1.0);
}
#[test] #[test]
fn parse() { fn parse() {

View file

@ -151,39 +151,6 @@ impl<T> InputMethod<T> {
/// However, one couldn't possibly have a key for every single /// However, one couldn't possibly have a key for every single
/// unicode character that the user might want to type. The solution operating systems employ is /// unicode character that the user might want to type. The solution operating systems employ is
/// to allow the user to type these using _a sequence of keypresses_ instead. /// to allow the user to type these using _a sequence of keypresses_ instead.
///
/// A prominent example of this is accents—many keyboard layouts allow you to first click the
/// "accent key", and then the character you want to apply the accent to. In this case, some
/// platforms will generate the following event sequence:
///
/// ```ignore
/// // Press "`" key
/// Ime::Preedit("`", Some((0, 0)))
/// // Press "E" key
/// Ime::Preedit("", None) // Synthetic event generated to clear preedit.
/// Ime::Commit("é")
/// ```
///
/// Additionally, certain input devices are configured to display a candidate box that allow the
/// user to select the desired character interactively. (To properly position this box, you must use
/// [`Shell::request_input_method`](crate::Shell::request_input_method).)
///
/// An example of a keyboard layout which uses candidate boxes is pinyin. On a latin keyboard the
/// following event sequence could be obtained:
///
/// ```ignore
/// // Press "A" key
/// Ime::Preedit("a", Some((1, 1)))
/// // Press "B" key
/// Ime::Preedit("a b", Some((3, 3)))
/// // Press left arrow key
/// Ime::Preedit("a b", Some((1, 1)))
/// // Press space key
/// Ime::Preedit("啊b", Some((3, 3)))
/// // Press space key
/// Ime::Preedit("", None) // Synthetic event generated to clear preedit.
/// Ime::Commit("啊不")
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Event { pub enum Event {
/// Notifies when the IME was opened. /// Notifies when the IME was opened.

View file

@ -1,10 +1,6 @@
//! Define the colors of a theme. //! Define the colors of a theme.
use crate::{Color, color}; use crate::{Color, color};
use palette::color_difference::Wcag21RelativeContrast;
use palette::rgb::Rgb;
use palette::{FromColor, Hsl, Mix};
use std::sync::LazyLock; use std::sync::LazyLock;
/// A color palette. /// A color palette.
@ -608,13 +604,20 @@ impl Danger {
} }
} }
struct Hsl {
h: f32,
s: f32,
l: f32,
a: f32,
}
fn darken(color: Color, amount: f32) -> Color { fn darken(color: Color, amount: f32) -> Color {
let mut hsl = to_hsl(color); let mut hsl = to_hsl(color);
hsl.lightness = if hsl.lightness - amount < 0.0 { hsl.l = if hsl.l - amount < 0.0 {
0.0 0.0
} else { } else {
hsl.lightness - amount hsl.l - amount
}; };
from_hsl(hsl) from_hsl(hsl)
@ -623,10 +626,10 @@ fn darken(color: Color, amount: f32) -> Color {
fn lighten(color: Color, amount: f32) -> Color { fn lighten(color: Color, amount: f32) -> Color {
let mut hsl = to_hsl(color); let mut hsl = to_hsl(color);
hsl.lightness = if hsl.lightness + amount > 1.0 { hsl.l = if hsl.l + amount > 1.0 {
1.0 1.0
} else { } else {
hsl.lightness + amount hsl.l + amount
}; };
from_hsl(hsl) from_hsl(hsl)
@ -643,17 +646,24 @@ fn deviate(color: Color, amount: f32) -> Color {
fn muted(color: Color) -> Color { fn muted(color: Color) -> Color {
let mut hsl = to_hsl(color); let mut hsl = to_hsl(color);
hsl.saturation = hsl.saturation.min(0.5); hsl.s = hsl.s.min(0.5);
from_hsl(hsl) from_hsl(hsl)
} }
fn mix(a: Color, b: Color, factor: f32) -> Color { fn mix(a: Color, b: Color, factor: f32) -> Color {
let a_lin = Rgb::from(a).into_linear(); let b_amount = factor.clamp(0.0, 1.0);
let b_lin = Rgb::from(b).into_linear(); let a_amount = 1.0 - b_amount;
let mixed = a_lin.mix(b_lin, factor); let a_linear = a.into_linear().map(|c| c * a_amount);
Rgb::from_linear(mixed).into() let b_linear = b.into_linear().map(|c| c * b_amount);
Color::from_linear_rgba(
a_linear[0] + b_linear[0],
a_linear[1] + b_linear[1],
a_linear[2] + b_linear[2],
a_linear[3] + b_linear[3],
)
} }
fn readable(background: Color, text: Color) -> Color { fn readable(background: Color, text: Color) -> Color {
@ -681,27 +691,85 @@ fn readable(background: Color, text: Color) -> Color {
} }
fn is_dark(color: Color) -> bool { fn is_dark(color: Color) -> bool {
to_hsl(color).lightness < 0.6 to_hsl(color).l < 0.6
} }
fn is_readable(a: Color, b: Color) -> bool { fn is_readable(a: Color, b: Color) -> bool {
let a_srgb = Rgb::from(a); relative_contrast(a, b) >= 7.0
let b_srgb = Rgb::from(b);
a_srgb.has_enhanced_contrast_text(b_srgb)
} }
// https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio
fn relative_contrast(a: Color, b: Color) -> f32 { fn relative_contrast(a: Color, b: Color) -> f32 {
let a_srgb = Rgb::from(a); let lum_a = relative_luminance(a);
let b_srgb = Rgb::from(b); let lum_b = relative_luminance(b);
(lum_a.max(lum_b) + 0.05) / (lum_a.min(lum_b) + 0.05)
a_srgb.relative_contrast(b_srgb)
} }
// https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
fn relative_luminance(color: Color) -> f32 {
let linear = color.into_linear();
0.2126 * linear[0] + 0.7152 * linear[1] + 0.0722 * linear[2]
}
// https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB
fn to_hsl(color: Color) -> Hsl { fn to_hsl(color: Color) -> Hsl {
Hsl::from_color(Rgb::from(color)) let x_max = color.r.max(color.g).max(color.b);
let x_min = color.r.min(color.g).min(color.b);
let c = x_max - x_min;
let l = x_max.midpoint(x_min);
let h = if c == 0.0 {
0.0
} else if x_max == color.r {
60.0 * ((color.g - color.b) / c).rem_euclid(6.0)
} else if x_max == color.g {
60.0 * (((color.b - color.r) / c) + 2.0)
} else {
// x_max == color.b
60.0 * (((color.r - color.g) / c) + 4.0)
};
let s = if l == 0.0 || l == 1.0 {
0.0
} else {
(x_max - l) / l.min(1.0 - l)
};
Hsl {
h,
s,
l,
a: color.a,
}
} }
// https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB
fn from_hsl(hsl: Hsl) -> Color { fn from_hsl(hsl: Hsl) -> Color {
Rgb::from_color(hsl).into() let c = (1.0 - (2.0 * hsl.l - 1.0).abs()) * hsl.s;
let h = hsl.h / 60.0;
let x = c * (1.0 - (h.rem_euclid(2.0) - 1.0).abs());
let (r1, g1, b1) = if h < 1.0 {
(c, x, 0.0)
} else if h < 2.0 {
(x, c, 0.0)
} else if h < 3.0 {
(0.0, c, x)
} else if h < 4.0 {
(0.0, x, c)
} else if h < 5.0 {
(x, 0.0, c)
} else {
// h < 6.0
(c, 0.0, x)
};
let m = hsl.l - (c / 2.0);
Color {
r: r1 + m,
g: g1 + m,
b: b1 + m,
a: hsl.a,
}
} }

View file

@ -9,4 +9,4 @@ publish = false
iced.workspace = true iced.workspace = true
iced.features = ["canvas"] iced.features = ["canvas"]
palette.workspace = true palette = "0.7"

View file

@ -45,7 +45,7 @@ pub enum Message {
impl ColorPalette { impl ColorPalette {
fn update(&mut self, message: Message) { fn update(&mut self, message: Message) {
let srgb = match message { let srgb = match message {
Message::RgbColorChanged(rgb) => Rgb::from(rgb), Message::RgbColorChanged(rgb) => to_rgb(rgb),
Message::HslColorChanged(hsl) => Rgb::from_color(hsl), Message::HslColorChanged(hsl) => Rgb::from_color(hsl),
Message::HsvColorChanged(hsv) => Rgb::from_color(hsv), Message::HsvColorChanged(hsv) => Rgb::from_color(hsv),
Message::HwbColorChanged(hwb) => Rgb::from_color(hwb), Message::HwbColorChanged(hwb) => Rgb::from_color(hwb),
@ -53,13 +53,13 @@ impl ColorPalette {
Message::LchColorChanged(lch) => Rgb::from_color(lch), Message::LchColorChanged(lch) => Rgb::from_color(lch),
}; };
self.theme = Theme::new(srgb); self.theme = Theme::new(to_color(srgb));
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<Message> {
let base = self.theme.base; let base = self.theme.base;
let srgb = Rgb::from(base); let srgb = to_rgb(base);
let hsl = palette::Hsl::from_color(srgb); let hsl = palette::Hsl::from_color(srgb);
let hsv = palette::Hsv::from_color(srgb); let hsv = palette::Hsv::from_color(srgb);
let hwb = palette::Hwb::from_color(srgb); let hwb = palette::Hwb::from_color(srgb);
@ -108,7 +108,7 @@ impl Theme {
let base = base.into(); let base = base.into();
// Convert to HSL color for manipulation // Convert to HSL color for manipulation
let hsl = Hsl::from_color(Rgb::from(base)); let hsl = Hsl::from_color(to_rgb(base));
let lower = [ let lower = [
hsl.shift_hue(-135.0).lighten(0.075), hsl.shift_hue(-135.0).lighten(0.075),
@ -127,12 +127,12 @@ impl Theme {
Theme { Theme {
lower: lower lower: lower
.iter() .iter()
.map(|&color| Rgb::from_color(color).into()) .map(|&color| to_color(Rgb::from_color(color)))
.collect(), .collect(),
base, base,
higher: higher higher: higher
.iter() .iter()
.map(|&color| Rgb::from_color(color).into()) .map(|&color| to_color(Rgb::from_color(color)))
.collect(), .collect(),
canvas_cache: canvas::Cache::default(), canvas_cache: canvas::Cache::default(),
} }
@ -215,14 +215,14 @@ impl Theme {
text.align_y = alignment::Vertical::Bottom; text.align_y = alignment::Vertical::Bottom;
let hsl = Hsl::from_color(Rgb::from(self.base)); let hsl = Hsl::from_color(to_rgb(self.base));
for i in 0..self.len() { for i in 0..self.len() {
let pct = (i as f32 + 1.0) / (self.len() as f32 + 1.0); let pct = (i as f32 + 1.0) / (self.len() as f32 + 1.0);
let graded = Hsl { let graded = Hsl {
lightness: 1.0 - pct, lightness: 1.0 - pct,
..hsl ..hsl
}; };
let color: Color = Rgb::from_color(graded).into(); let color: Color = to_color(Rgb::from_color(graded));
let anchor = Point { let anchor = Point {
x: (i as f32) * box_size.width, x: (i as f32) * box_size.width,
@ -474,3 +474,21 @@ impl ColorSpace for palette::Lch {
) )
} }
} }
fn to_rgb(color: Color) -> Rgb {
Rgb {
red: color.r,
green: color.g,
blue: color.b,
..Rgb::default()
}
}
fn to_color(rgb: Rgb) -> Color {
Color {
r: rgb.red,
g: rgb.green,
b: rgb.blue,
a: 1.0,
}
}

View file

@ -7,7 +7,7 @@ publish = false
[dependencies] [dependencies]
iced.workspace = true iced.workspace = true
iced.features = ["tokio"] iced.features = ["tokio", "sipper"]
[dependencies.reqwest] [dependencies.reqwest]
version = "0.12" version = "0.12"

View file

@ -112,9 +112,7 @@ impl Download {
pub fn start(&mut self) -> Task<Update> { pub fn start(&mut self) -> Task<Update> {
match self.state { match self.state {
State::Idle { .. } State::Idle | State::Finished | State::Errored => {
| State::Finished { .. }
| State::Errored { .. } => {
let (task, handle) = Task::sip( let (task, handle) = Task::sip(
download( download(
"https://huggingface.co/\ "https://huggingface.co/\
@ -156,10 +154,10 @@ impl Download {
pub fn view(&self) -> Element<Message> { pub fn view(&self) -> Element<Message> {
let current_progress = match &self.state { let current_progress = match &self.state {
State::Idle { .. } => 0.0, State::Idle => 0.0,
State::Downloading { progress, .. } => *progress, State::Downloading { progress, .. } => *progress,
State::Finished { .. } => 100.0, State::Finished => 100.0,
State::Errored { .. } => 0.0, State::Errored => 0.0,
}; };
let progress_bar = progress_bar(0.0..=100.0, current_progress); let progress_bar = progress_bar(0.0..=100.0, current_progress);

View file

@ -7,7 +7,7 @@ publish = false
[dependencies] [dependencies]
iced.workspace = true iced.workspace = true
iced.features = ["tokio", "image", "web-colors", "debug"] iced.features = ["tokio", "sipper", "image", "web-colors", "debug"]
reqwest.version = "0.12" reqwest.version = "0.12"
reqwest.features = ["json"] reqwest.features = ["json"]

View file

@ -12,6 +12,9 @@ iced_wgpu.workspace = true
iced_widget.workspace = true iced_widget.workspace = true
iced_widget.features = ["wgpu"] iced_widget.features = ["wgpu"]
futures.workspace = true
futures.features = ["thread-pool"]
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tracing-subscriber = "0.3" tracing-subscriber = "0.3"

View file

@ -36,9 +36,10 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
Loading, Loading,
Ready { Ready {
window: Arc<winit::window::Window>, window: Arc<winit::window::Window>,
queue: wgpu::Queue,
device: wgpu::Device,
surface: wgpu::Surface<'static>, surface: wgpu::Surface<'static>,
format: wgpu::TextureFormat, format: wgpu::TextureFormat,
device: wgpu::Device,
renderer: Renderer, renderer: Renderer,
scene: Scene, scene: Scene,
controls: Controls, controls: Controls,
@ -144,11 +145,12 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
let controls = Controls::new(); let controls = Controls::new();
// Initialize iced // Initialize iced
let renderer = { let renderer = {
let engine = Engine::new( let engine = Engine::new(
&adapter, &adapter,
device.clone(), device.clone(),
queue, queue.clone(),
format, format,
None, None,
); );
@ -161,10 +163,11 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
*self = Self::Ready { *self = Self::Ready {
window, window,
device,
queue,
renderer,
surface, surface,
format, format,
device,
renderer,
scene, scene,
controls, controls,
events: Vec::new(), events: Vec::new(),
@ -187,6 +190,7 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
let Self::Ready { let Self::Ready {
window, window,
device, device,
queue,
surface, surface,
format, format,
renderer, renderer,
@ -233,16 +237,16 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
match surface.get_current_texture() { match surface.get_current_texture() {
Ok(frame) => { Ok(frame) => {
let mut encoder = device.create_command_encoder(
&wgpu::CommandEncoderDescriptor { label: None },
);
let view = frame.texture.create_view( let view = frame.texture.create_view(
&wgpu::TextureViewDescriptor::default(), &wgpu::TextureViewDescriptor::default(),
); );
let mut encoder = device.create_command_encoder(
&wgpu::CommandEncoderDescriptor { label: None },
);
{ {
// We clear the frame // Clear the frame
let mut render_pass = Scene::clear( let mut render_pass = Scene::clear(
&view, &view,
&mut encoder, &mut encoder,
@ -253,7 +257,10 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
scene.draw(&mut render_pass); scene.draw(&mut render_pass);
} }
// And then iced on top // Submit the scene
queue.submit([encoder.finish()]);
// Draw iced on top
let mut interface = UserInterface::build( let mut interface = UserInterface::build(
controls.view(), controls.view(),
viewport.logical_size(), viewport.logical_size(),
@ -288,7 +295,7 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
viewport, viewport,
); );
// Then we submit the work // Present the frame
frame.present(); frame.present();
// Update the mouse cursor // Update the mouse cursor

View file

@ -34,7 +34,7 @@ impl Pokedex {
let subtitle = match self { let subtitle = match self {
Pokedex::Loading => "Loading", Pokedex::Loading => "Loading",
Pokedex::Loaded { pokemon, .. } => &pokemon.name, Pokedex::Loaded { pokemon, .. } => &pokemon.name,
Pokedex::Errored { .. } => "Whoops!", Pokedex::Errored => "Whoops!",
}; };
format!("{subtitle} - Pokédex") format!("{subtitle} - Pokédex")

View file

@ -7,3 +7,7 @@ publish = false
[dependencies] [dependencies]
iced.workspace = true iced.workspace = true
[dev-dependencies]
iced_test.workspace = true
rayon = "1"

View file

@ -0,0 +1 @@
9e67e429f88fd5a64744d9cd4d42e123950bea4a45fea78581bc3d64b12e11f0

View file

@ -0,0 +1 @@
811a22238f3a40e3e3998f514c0a95f24f2b45449250682d86c8ec392fec5e28

View file

@ -0,0 +1 @@
6bf957efe807f87f38cfc672f9a05325aadee6256aacca87bbc3281b98160c8a

View file

@ -0,0 +1 @@
d3f4110fae78a3be3b7a4813e9a432b48b81fff1e982c0244e4ea394074bef55

View file

@ -0,0 +1 @@
578e7420de69d82906d284c59d81fcea0edf81098481fc4dd7b4c1fb577b7f1c

View file

@ -0,0 +1 @@
422e841113efaa86e9e37593d0d14f8dd36ad483a81c30a08588f48805e4f9f3

View file

@ -0,0 +1 @@
3d616a31842a29b4a3d31fbeef25f95c7b50f33360f1c05e069e0b29b3f7553e

View file

@ -0,0 +1 @@
9a21865bfc075669d368ccac69b975c3e1f6c22ba297dddfa003d4ee1a06641c

View file

@ -0,0 +1 @@
d5164fb10a92177afd0aab353557d1e3fdaa743962c6a901f05cbfcd0d91e9fb

View file

@ -0,0 +1 @@
3d5ba3b50f192f8700edbfbf54007e92dfd66997bce7342671afc2b60d556aca

View file

@ -0,0 +1 @@
90cb13c900d58a56ce170afeefbceb77410d024e7eae6030e181df1c929c3944

View file

@ -0,0 +1 @@
71b625bc39aaead7a1298e4b2dad51b266526c53deab139858774986395878db

View file

@ -0,0 +1 @@
f08f80a79959ef1c2fd8f8696a26555f2b2eab6618dd3653a042acab563bcced

View file

@ -0,0 +1 @@
3302b7934051ff8aa01d70c45e28c444bdc93e8a4da219931aa22465b51c6748

View file

@ -0,0 +1 @@
661ec43b66213f369ce5a3680e9e8ead56c98d94718da25b12fbb313386944e0

View file

@ -0,0 +1 @@
b5dd22b064220d5f2f90c6f0f381eff41d25f534587d1649ed59e25f878eda97

View file

@ -0,0 +1 @@
dd6e7e7ba125a2549143501c3de44427633f0bfa6c0d8b6f66aa95d503874065

View file

@ -0,0 +1 @@
44b5afe743b753a54f3e68da2fd2701837e7e8cff276ff1e8c349030c83ac72e

View file

@ -0,0 +1 @@
1b46820f12611b2759eb843688cd13b6024f2096b7986f205398bb029e0c60bd

View file

@ -0,0 +1 @@
27a9a8bb08cea7b0a9b9763e332a6833d4fead1a885cdd59a1f5161e3726d6b1

View file

@ -0,0 +1 @@
ab0dee2cc24f30eb51b376a0385302b45bfeddb373348525dab7f551c27ffec2

View file

@ -0,0 +1 @@
aef404c7e38aec5387c39cf3a2911688324c1b9b520e5f541b9fd3fd6eefd3a8

View file

@ -167,3 +167,42 @@ impl Styling {
self.theme.clone() self.theme.clone()
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use rayon::prelude::*;
use iced_test::{Error, simulator};
#[test]
#[ignore]
fn it_showcases_every_theme() -> Result<(), Error> {
Theme::ALL
.par_iter()
.cloned()
.map(|theme| {
let mut styling = Styling::default();
styling.update(Message::ThemeChanged(theme));
let theme = styling.theme();
let mut ui = simulator(styling.view());
let snapshot = ui.snapshot(&theme)?;
assert!(
snapshot.matches_hash(format!(
"snapshots/{theme}",
theme = theme
.to_string()
.to_ascii_lowercase()
.replace(" ", "_")
))?,
"snapshots for {theme} should match!"
);
Ok(())
})
.collect()
}
}

View file

@ -327,9 +327,10 @@ mod toast {
instants.truncate(new); instants.truncate(new);
} }
(old, new) if old < new => { (old, new) if old < new => {
instants.extend( instants.extend(std::iter::repeat_n(
std::iter::repeat(Some(Instant::now())).take(new - old), Some(Instant::now()),
); new - old,
));
} }
_ => {} _ => {}
} }

View file

@ -7,14 +7,15 @@ publish = false
[dependencies] [dependencies]
iced.workspace = true iced.workspace = true
iced.features = ["async-std", "debug"] iced.features = ["tokio", "debug"]
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
uuid = { version = "1.0", features = ["v4", "fast-rng", "serde"] } uuid = { version = "1.0", features = ["v4", "fast-rng", "serde"] }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
async-std.workspace = true tokio.workspace = true
tokio.features = ["fs", "time"]
directories = "6.0" directories = "6.0"
tracing-subscriber = "0.3" tracing-subscriber = "0.3"

View file

@ -483,7 +483,6 @@ enum LoadError {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
enum SaveError { enum SaveError {
File,
Write, Write,
Format, Format,
} }
@ -505,15 +504,7 @@ impl SavedState {
} }
async fn load() -> Result<SavedState, LoadError> { async fn load() -> Result<SavedState, LoadError> {
use async_std::prelude::*; let contents = tokio::fs::read_to_string(Self::path())
let mut contents = String::new();
let mut file = async_std::fs::File::open(Self::path())
.await
.map_err(|_| LoadError::File)?;
file.read_to_string(&mut contents)
.await .await
.map_err(|_| LoadError::File)?; .map_err(|_| LoadError::File)?;
@ -521,31 +512,25 @@ impl SavedState {
} }
async fn save(self) -> Result<(), SaveError> { async fn save(self) -> Result<(), SaveError> {
use async_std::prelude::*;
let json = serde_json::to_string_pretty(&self) let json = serde_json::to_string_pretty(&self)
.map_err(|_| SaveError::Format)?; .map_err(|_| SaveError::Format)?;
let path = Self::path(); let path = Self::path();
if let Some(dir) = path.parent() { if let Some(dir) = path.parent() {
async_std::fs::create_dir_all(dir) tokio::fs::create_dir_all(dir)
.await .await
.map_err(|_| SaveError::File)?; .map_err(|_| SaveError::Write)?;
} }
{ {
let mut file = async_std::fs::File::create(path) tokio::fs::write(path, json.as_bytes())
.await
.map_err(|_| SaveError::File)?;
file.write_all(json.as_bytes())
.await .await
.map_err(|_| SaveError::Write)?; .map_err(|_| SaveError::Write)?;
} }
// This is a simple way to save at most once every couple seconds // This is a simple way to save at most once every couple seconds
async_std::task::sleep(std::time::Duration::from_secs(2)).await; tokio::time::sleep(std::time::Duration::from_secs(2)).await;
Ok(()) Ok(())
} }
@ -571,7 +556,7 @@ impl SavedState {
} }
async fn save(self) -> Result<(), SaveError> { async fn save(self) -> Result<(), SaveError> {
let storage = Self::storage().ok_or(SaveError::File)?; let storage = Self::storage().ok_or(SaveError::Write)?;
let json = serde_json::to_string_pretty(&self) let json = serde_json::to_string_pretty(&self)
.map_err(|_| SaveError::Format)?; .map_err(|_| SaveError::Format)?;

View file

@ -7,7 +7,7 @@ publish = false
[dependencies] [dependencies]
iced.workspace = true iced.workspace = true
iced.features = ["debug", "tokio"] iced.features = ["debug", "tokio", "sipper"]
warp = "0.3" warp = "0.3"

View file

@ -24,14 +24,12 @@ thread-pool = ["futures/thread-pool"]
iced_core.workspace = true iced_core.workspace = true
futures.workspace = true futures.workspace = true
futures.features = ["std", "async-await"]
log.workspace = true log.workspace = true
rustc-hash.workspace = true rustc-hash.workspace = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
async-std.workspace = true
async-std.optional = true
async-std.features = ["unstable"]
smol.workspace = true smol.workspace = true
smol.optional = true smol.optional = true

View file

@ -2,10 +2,9 @@
//! //!
//! - On native platforms, it will use: //! - On native platforms, it will use:
//! - `backend::native::tokio` when the `tokio` feature is enabled. //! - `backend::native::tokio` when the `tokio` feature is enabled.
//! - `backend::native::async-std` when the `async-std` feature is
//! enabled.
//! - `backend::native::smol` when the `smol` feature is enabled. //! - `backend::native::smol` when the `smol` feature is enabled.
//! - `backend::native::thread_pool` otherwise. //! - `backend::native::thread_pool` when the `thread-pool` feature is enabled.
//! - `backend::null` otherwise.
//! //!
//! - On Wasm, it will use `backend::wasm::wasm_bindgen`. //! - On Wasm, it will use `backend::wasm::wasm_bindgen`.
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
@ -13,24 +12,17 @@ mod platform {
#[cfg(feature = "tokio")] #[cfg(feature = "tokio")]
pub use crate::backend::native::tokio::*; pub use crate::backend::native::tokio::*;
#[cfg(all(feature = "async-std", not(feature = "tokio"),))] #[cfg(all(feature = "smol", not(feature = "tokio"),))]
pub use crate::backend::native::async_std::*;
#[cfg(all(
feature = "smol",
not(any(feature = "tokio", feature = "async-std")),
))]
pub use crate::backend::native::smol::*; pub use crate::backend::native::smol::*;
#[cfg(all( #[cfg(all(
feature = "thread-pool", feature = "thread-pool",
not(any(feature = "tokio", feature = "async-std", feature = "smol")) not(any(feature = "tokio", feature = "smol"))
))] ))]
pub use crate::backend::native::thread_pool::*; pub use crate::backend::native::thread_pool::*;
#[cfg(not(any( #[cfg(not(any(
feature = "tokio", feature = "tokio",
feature = "async-std",
feature = "smol", feature = "smol",
feature = "thread-pool" feature = "thread-pool"
)))] )))]

View file

@ -2,9 +2,6 @@
#[cfg(feature = "tokio")] #[cfg(feature = "tokio")]
pub mod tokio; pub mod tokio;
#[cfg(feature = "async-std")]
pub mod async_std;
#[cfg(feature = "smol")] #[cfg(feature = "smol")]
pub mod smol; pub mod smol;

View file

@ -1,56 +0,0 @@
//! An `async-std` backend.
/// An `async-std` executor.
#[derive(Debug)]
pub struct Executor;
impl crate::Executor for Executor {
fn new() -> Result<Self, futures::io::Error> {
Ok(Self)
}
#[allow(clippy::let_underscore_future)]
fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) {
let _ = async_std::task::spawn(future);
}
}
pub mod time {
//! Listen and react to time.
use crate::subscription::{self, Hasher, Subscription};
/// Returns a [`Subscription`] that produces messages at a set interval.
///
/// The first message is produced after a `duration`, and then continues to
/// produce more messages every `duration` after that.
pub fn every(
duration: std::time::Duration,
) -> Subscription<std::time::Instant> {
subscription::from_recipe(Every(duration))
}
#[derive(Debug)]
struct Every(std::time::Duration);
impl subscription::Recipe for Every {
type Output = std::time::Instant;
fn hash(&self, state: &mut Hasher) {
use std::hash::Hash;
std::any::TypeId::of::<Self>().hash(state);
self.0.hash(state);
}
fn stream(
self: Box<Self>,
_input: subscription::EventStream,
) -> futures::stream::BoxStream<'static, Self::Output> {
use futures::stream::StreamExt;
async_std::stream::interval(self.0)
.map(|_| std::time::Instant::now())
.boxed()
}
}
}

View file

@ -12,6 +12,10 @@ impl crate::Executor for Executor {
fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) { fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) {
smol::spawn(future).detach(); smol::spawn(future).detach();
} }
fn block_on<T>(&self, future: impl Future<Output = T>) -> T {
smol::block_on(future)
}
} }
pub mod time { pub mod time {

View file

@ -11,6 +11,10 @@ impl crate::Executor for Executor {
fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) { fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) {
self.spawn_ok(future); self.spawn_ok(future);
} }
fn block_on<T>(&self, future: impl Future<Output = T>) -> T {
futures::executor::block_on(future)
}
} }
pub mod time { pub mod time {

View file

@ -17,6 +17,10 @@ impl crate::Executor for Executor {
let _guard = tokio::runtime::Runtime::enter(self); let _guard = tokio::runtime::Runtime::enter(self);
f() f()
} }
fn block_on<T>(&self, future: impl Future<Output = T>) -> T {
self.block_on(future)
}
} }
pub mod time { pub mod time {

View file

@ -1,4 +1,5 @@
//! A backend that does nothing! //! A backend that does nothing!
use crate::MaybeSend;
/// An executor that drops all the futures, instead of spawning them. /// An executor that drops all the futures, instead of spawning them.
#[derive(Debug)] #[derive(Debug)]
@ -9,11 +10,12 @@ impl crate::Executor for Executor {
Ok(Self) Ok(Self)
} }
#[cfg(not(target_arch = "wasm32"))] fn spawn(&self, _future: impl Future<Output = ()> + MaybeSend + 'static) {}
fn spawn(&self, _future: impl Future<Output = ()> + Send + 'static) {}
#[cfg(target_arch = "wasm32")] #[cfg(not(target_arch = "wasm32"))]
fn spawn(&self, _future: impl Future<Output = ()> + 'static) {} fn block_on<T>(&self, _future: impl Future<Output = T>) -> T {
unimplemented!()
}
} }
pub mod time { pub mod time {

View file

@ -11,6 +11,10 @@ pub trait Executor: Sized {
/// Spawns a future in the [`Executor`]. /// Spawns a future in the [`Executor`].
fn spawn(&self, future: impl Future<Output = ()> + MaybeSend + 'static); fn spawn(&self, future: impl Future<Output = ()> + MaybeSend + 'static);
/// Runs a future to completion in the current thread within the [`Executor`].
#[cfg(not(target_arch = "wasm32"))]
fn block_on<T>(&self, future: impl Future<Output = T>) -> T;
/// Runs the given closure inside the [`Executor`]. /// Runs the given closure inside the [`Executor`].
/// ///
/// Some executors, like `tokio`, require some global state to be in place /// Some executors, like `tokio`, require some global state to be in place

View file

@ -1,6 +1,6 @@
//! Run commands and keep track of subscriptions. //! Run commands and keep track of subscriptions.
use crate::subscription; use crate::subscription;
use crate::{BoxFuture, BoxStream, Executor, MaybeSend}; use crate::{BoxStream, Executor, MaybeSend};
use futures::{Sink, channel::mpsc}; use futures::{Sink, channel::mpsc};
use std::marker::PhantomData; use std::marker::PhantomData;
@ -50,22 +50,10 @@ where
self.executor.enter(f) self.executor.enter(f)
} }
/// Spawns a [`Future`] in the [`Runtime`]. /// Runs a future to completion in the current thread within the [`Runtime`].
/// #[cfg(not(target_arch = "wasm32"))]
/// The resulting `Message` will be forwarded to the `Sender` of the pub fn block_on<T>(&mut self, future: impl Future<Output = T>) -> T {
/// [`Runtime`]. self.executor.block_on(future)
///
/// [`Future`]: BoxFuture
pub fn spawn(&mut self, future: BoxFuture<Message>) {
use futures::{FutureExt, SinkExt};
let mut sender = self.sender.clone();
let future = future.then(|message| async move {
let _ = sender.send(message).await;
});
self.executor.spawn(future);
} }
/// Runs a [`Stream`] in the [`Runtime`] until completion. /// Runs a [`Stream`] in the [`Runtime`] until completion.

View file

@ -19,8 +19,9 @@ iced_core.workspace = true
iced_debug.workspace = true iced_debug.workspace = true
iced_futures.workspace = true iced_futures.workspace = true
iced_futures.features = ["thread-pool"]
raw-window-handle.workspace = true raw-window-handle.workspace = true
sipper.workspace = true
thiserror.workspace = true thiserror.workspace = true
sipper.workspace = true
sipper.optional = true

View file

@ -7,8 +7,10 @@ use crate::futures::futures::future::{self, FutureExt};
use crate::futures::futures::stream::{self, Stream, StreamExt}; use crate::futures::futures::stream::{self, Stream, StreamExt};
use crate::futures::{BoxStream, MaybeSend, boxed_stream}; use crate::futures::{BoxStream, MaybeSend, boxed_stream};
use std::convert::Infallible;
use std::sync::Arc; use std::sync::Arc;
#[cfg(feature = "sipper")]
#[doc(no_inline)] #[doc(no_inline)]
pub use sipper::{Never, Sender, Sipper, Straw, sipper, stream}; pub use sipper::{Never, Sender, Sipper, Straw, sipper, stream};
@ -66,6 +68,7 @@ impl<T> Task<T> {
/// Creates a [`Task`] that runs the given [`Sipper`] to completion, mapping /// Creates a [`Task`] that runs the given [`Sipper`] to completion, mapping
/// progress with the first closure and the output with the second one. /// progress with the first closure and the output with the second one.
#[cfg(feature = "sipper")]
pub fn sip<S>( pub fn sip<S>(
sipper: S, sipper: S,
on_progress: impl FnMut(S::Progress) -> T + MaybeSend + 'static, on_progress: impl FnMut(S::Progress) -> T + MaybeSend + 'static,
@ -430,7 +433,7 @@ where
} }
/// Creates a new [`Task`] that executes the given [`Action`] and produces no output. /// Creates a new [`Task`] that executes the given [`Action`] and produces no output.
pub fn effect<T>(action: impl Into<Action<Never>>) -> Task<T> { pub fn effect<T>(action: impl Into<Action<Infallible>>) -> Task<T> {
let action = action.into(); let action = action.into();
Task { Task {

View file

@ -1,7 +1,7 @@
//! Create and run iced applications step by step. //! Create and run iced applications step by step.
//! //!
//! # Example //! # Example
//! ```no_run //! ```no_run,standalone_crate
//! use iced::widget::{button, column, text, Column}; //! use iced::widget::{button, column, text, Column};
//! use iced::Theme; //! use iced::Theme;
//! //!
@ -43,7 +43,7 @@ use std::borrow::Cow;
/// Creates an iced [`Application`] given its boot, update, and view logic. /// Creates an iced [`Application`] given its boot, update, and view logic.
/// ///
/// # Example /// # Example
/// ```no_run /// ```no_run,standalone_crate
/// use iced::widget::{button, column, text, Column}; /// use iced::widget::{button, column, text, Column};
/// ///
/// pub fn main() -> iced::Result { /// pub fn main() -> iced::Result {

View file

@ -29,7 +29,7 @@
//! # The Pocket Guide //! # The Pocket Guide
//! Start by calling [`run`]: //! Start by calling [`run`]:
//! //!
//! ```rust,no_run //! ```no_run,standalone_crate
//! pub fn main() -> iced::Result { //! pub fn main() -> iced::Result {
//! iced::run(update, view) //! iced::run(update, view)
//! } //! }
@ -39,7 +39,7 @@
//! //!
//! Define an `update` function to __change__ your state: //! Define an `update` function to __change__ your state:
//! //!
//! ```rust //! ```standalone_crate
//! fn update(counter: &mut u64, message: Message) { //! fn update(counter: &mut u64, message: Message) {
//! match message { //! match message {
//! Message::Increment => *counter += 1, //! Message::Increment => *counter += 1,
@ -51,7 +51,7 @@
//! //!
//! Define a `view` function to __display__ your state: //! Define a `view` function to __display__ your state:
//! //!
//! ```rust //! ```standalone_crate
//! use iced::widget::{button, text}; //! use iced::widget::{button, text};
//! use iced::Element; //! use iced::Element;
//! //!
@ -64,7 +64,7 @@
//! //!
//! And create a `Message` enum to __connect__ `view` and `update` together: //! And create a `Message` enum to __connect__ `view` and `update` together:
//! //!
//! ```rust //! ```standalone_crate
//! #[derive(Debug, Clone)] //! #[derive(Debug, Clone)]
//! enum Message { //! enum Message {
//! Increment, //! Increment,
@ -74,7 +74,7 @@
//! ## Custom State //! ## Custom State
//! You can define your own struct for your state: //! You can define your own struct for your state:
//! //!
//! ```rust //! ```standalone_crate
//! #[derive(Default)] //! #[derive(Default)]
//! struct Counter { //! struct Counter {
//! value: u64, //! value: u64,
@ -83,7 +83,7 @@
//! //!
//! But you have to change `update` and `view` accordingly: //! But you have to change `update` and `view` accordingly:
//! //!
//! ```rust //! ```standalone_crate
//! # struct Counter { value: u64 } //! # struct Counter { value: u64 }
//! # #[derive(Clone)] //! # #[derive(Clone)]
//! # enum Message { Increment } //! # enum Message { Increment }
@ -108,7 +108,7 @@
//! //!
//! Widgets are configured using the builder pattern: //! Widgets are configured using the builder pattern:
//! //!
//! ```rust //! ```standalone_crate
//! # struct Counter { value: u64 } //! # struct Counter { value: u64 }
//! # #[derive(Clone)] //! # #[derive(Clone)]
//! # enum Message { Increment } //! # enum Message { Increment }
@ -138,7 +138,7 @@
//! Building your layout will often consist in using a combination of //! Building your layout will often consist in using a combination of
//! [rows], [columns], and [containers]: //! [rows], [columns], and [containers]:
//! //!
//! ```rust //! ```standalone_crate
//! # struct State; //! # struct State;
//! # enum Message {} //! # enum Message {}
//! use iced::widget::{column, container, row}; //! use iced::widget::{column, container, row};
@ -181,7 +181,7 @@
//! //!
//! A fixed numeric [`Length`] in [`Pixels`] can also be used: //! A fixed numeric [`Length`] in [`Pixels`] can also be used:
//! //!
//! ```rust //! ```standalone_crate
//! # struct State; //! # struct State;
//! # enum Message {} //! # enum Message {}
//! use iced::widget::container; //! use iced::widget::container;
@ -197,7 +197,7 @@
//! function and leveraging the [`Application`] builder, instead of directly //! function and leveraging the [`Application`] builder, instead of directly
//! calling [`run`]: //! calling [`run`]:
//! //!
//! ```rust,no_run //! ```no_run,standalone_crate
//! # struct State; //! # struct State;
//! use iced::Theme; //! use iced::Theme;
//! //!
@ -231,7 +231,7 @@
//! //!
//! The appearance of a widget can be changed by calling its `style` method: //! The appearance of a widget can be changed by calling its `style` method:
//! //!
//! ```rust //! ```standalone_crate
//! # struct State; //! # struct State;
//! # enum Message {} //! # enum Message {}
//! use iced::widget::container; //! use iced::widget::container;
@ -245,7 +245,7 @@
//! The `style` method of a widget takes a closure that, given the current active //! The `style` method of a widget takes a closure that, given the current active
//! [`Theme`], returns the widget style: //! [`Theme`], returns the widget style:
//! //!
//! ```rust //! ```standalone_crate
//! # struct State; //! # struct State;
//! # #[derive(Clone)] //! # #[derive(Clone)]
//! # enum Message {} //! # enum Message {}
@ -290,7 +290,7 @@
//! A [`Task`] can be leveraged to perform asynchronous work, like running a //! A [`Task`] can be leveraged to perform asynchronous work, like running a
//! future or a stream: //! future or a stream:
//! //!
//! ```rust //! ```standalone_crate
//! # #[derive(Clone)] //! # #[derive(Clone)]
//! # struct Weather; //! # struct Weather;
//! use iced::Task; //! use iced::Task;
@ -338,7 +338,7 @@
//! //!
//! You will need to define a `subscription` function and use the [`Application`] builder: //! You will need to define a `subscription` function and use the [`Application`] builder:
//! //!
//! ```rust,no_run //! ```no_run,standalone_crate
//! # struct State; //! # struct State;
//! use iced::window; //! use iced::window;
//! use iced::{Size, Subscription}; //! use iced::{Size, Subscription};
@ -379,7 +379,7 @@
//! A common pattern is to leverage this composability to split an //! A common pattern is to leverage this composability to split an
//! application into different screens: //! application into different screens:
//! //!
//! ```rust //! ```standalone_crate
//! # mod contacts { //! # mod contacts {
//! # use iced::{Element, Task}; //! # use iced::{Element, Task};
//! # pub struct Contacts; //! # pub struct Contacts;
@ -485,6 +485,18 @@ use iced_winit::runtime;
pub use iced_futures::futures; pub use iced_futures::futures;
pub use iced_futures::stream; pub use iced_futures::stream;
#[cfg(not(any(
target_arch = "wasm32",
feature = "thread-pool",
feature = "tokio",
feature = "smol"
)))]
compile_error!(
"No futures executor has been enabled! You must enable an \
executor feature.\n\
Available options: thread-pool, tokio, or smol."
);
#[cfg(feature = "highlighter")] #[cfg(feature = "highlighter")]
pub use iced_highlighter as highlighter; pub use iced_highlighter as highlighter;
@ -523,9 +535,10 @@ pub use alignment::Vertical::{Bottom, Top};
pub mod task { pub mod task {
//! Create runtime tasks. //! Create runtime tasks.
pub use crate::runtime::task::{ pub use crate::runtime::task::{Handle, Task};
Handle, Never, Sipper, Straw, Task, sipper, stream,
}; #[cfg(feature = "sipper")]
pub use crate::runtime::task::{Never, Sipper, Straw, sipper, stream};
} }
pub mod clipboard { pub mod clipboard {
@ -538,18 +551,7 @@ pub mod clipboard {
pub mod executor { pub mod executor {
//! Choose your preferred executor to power your application. //! Choose your preferred executor to power your application.
pub use iced_futures::Executor; pub use iced_futures::Executor;
pub use iced_futures::backend::default::Executor as Default;
/// A default cross-platform executor.
///
/// - On native platforms, it will use:
/// - `iced_futures::backend::native::tokio` when the `tokio` feature is enabled.
/// - `iced_futures::backend::native::async-std` when the `async-std` feature is
/// enabled.
/// - `iced_futures::backend::native::smol` when the `smol` feature is enabled.
/// - `iced_futures::backend::native::thread_pool` otherwise.
///
/// - On Wasm, it will use `iced_futures::backend::wasm::wasm_bindgen`.
pub type Default = iced_futures::backend::default::Executor;
} }
pub mod font { pub mod font {
@ -658,7 +660,7 @@ pub type Result = std::result::Result<(), Error>;
/// This is equivalent to chaining [`application()`] with [`Application::run`]. /// This is equivalent to chaining [`application()`] with [`Application::run`].
/// ///
/// # Example /// # Example
/// ```no_run /// ```no_run,standalone_crate
/// use iced::widget::{button, column, text, Column}; /// use iced::widget::{button, column, text, Column};
/// ///
/// pub fn main() -> iced::Result { /// pub fn main() -> iced::Result {

View file

@ -6,7 +6,6 @@ pub use crate::core::time::*;
docsrs, docsrs,
doc(cfg(any( doc(cfg(any(
feature = "tokio", feature = "tokio",
feature = "async-std",
feature = "smol", feature = "smol",
target_arch = "wasm32" target_arch = "wasm32"
))) )))

View file

@ -4,9 +4,8 @@ use std::ops::RangeBounds;
pub const MAX_WRITE_SIZE: usize = 100 * 1024; pub const MAX_WRITE_SIZE: usize = 100 * 1024;
#[allow(unsafe_code)] const MAX_WRITE_SIZE_U64: NonZeroU64 = NonZeroU64::new(MAX_WRITE_SIZE as u64)
const MAX_WRITE_SIZE_U64: NonZeroU64 = .expect("MAX_WRITE_SIZE must be non-zero");
unsafe { NonZeroU64::new_unchecked(MAX_WRITE_SIZE as u64) };
#[derive(Debug)] #[derive(Debug)]
pub struct Buffer<T> { pub struct Buffer<T> {

View file

@ -120,9 +120,11 @@ impl Value {
/// dot ('•') character. /// dot ('•') character.
pub fn secure(&self) -> Self { pub fn secure(&self) -> Self {
Self { Self {
graphemes: std::iter::repeat(String::from("")) graphemes: std::iter::repeat_n(
.take(self.graphemes.len()) String::from(""),
.collect(), self.graphemes.len(),
)
.collect(),
} }
} }
} }

View file

@ -386,9 +386,11 @@ where
renderer, renderer,
&layout::Limits::new( &layout::Limits::new(
Size::ZERO, Size::ZERO,
self.snap_within_viewport if self.snap_within_viewport {
.then(|| viewport.size()) viewport.size()
.unwrap_or(Size::INFINITY), } else {
Size::INFINITY
},
) )
.shrink(Padding::new(self.padding)), .shrink(Padding::new(self.padding)),
); );

View file

@ -503,33 +503,10 @@ async fn run_instance<P>(
let mut ui_caches = FxHashMap::default(); let mut ui_caches = FxHashMap::default();
let mut user_interfaces = ManuallyDrop::new(FxHashMap::default()); let mut user_interfaces = ManuallyDrop::new(FxHashMap::default());
let mut clipboard = Clipboard::unconnected(); let mut clipboard = Clipboard::unconnected();
let mut compositor_receiver: Option<oneshot::Receiver<_>> = None;
loop { loop {
let event = if compositor_receiver.is_some() {
let compositor_receiver =
compositor_receiver.take().expect("Waiting for compositor");
match compositor_receiver.await {
Ok(Ok((new_compositor, event))) => {
compositor = Some(new_compositor);
Some(event)
}
Ok(Err(error)) => {
control_sender
.start_send(Control::Crash(
Error::GraphicsCreationFailed(error),
))
.expect("Send control action");
break;
}
Err(error) => {
panic!("Compositor initialization failed: {error}")
}
}
// Empty the queue if possible // Empty the queue if possible
} else if let Ok(event) = event_receiver.try_next() { let event = if let Ok(event) = event_receiver.try_next() {
event event
} else { } else {
event_receiver.next().await event_receiver.next().await
@ -548,17 +525,17 @@ async fn run_instance<P>(
on_open, on_open,
} => { } => {
if compositor.is_none() { if compositor.is_none() {
let (compositor_sender, new_compositor_receiver) = let (compositor_sender, compositor_receiver) =
oneshot::channel(); oneshot::channel();
compositor_receiver = Some(new_compositor_receiver);
let create_compositor = { let create_compositor = {
let window = window.clone();
let mut proxy = proxy.clone();
let default_fonts = default_fonts.clone(); let default_fonts = default_fonts.clone();
async move { async move {
let mut compositor = let mut compositor =
<P::Renderer as compositor::Default>::Compositor::new(graphics_settings, window.clone()).await; <P::Renderer as compositor::Default>::Compositor::new(graphics_settings, window).await;
if let Ok(compositor) = &mut compositor { if let Ok(compositor) = &mut compositor {
for font in default_fonts { for font in default_fonts {
@ -567,34 +544,42 @@ async fn run_instance<P>(
} }
compositor_sender compositor_sender
.send(compositor.map(|compositor| { .send(compositor)
(
compositor,
Event::WindowCreated {
id,
window,
exit_on_close_request,
make_visible,
on_open,
},
)
}))
.ok() .ok()
.expect("Send compositor"); .expect("Send compositor");
// HACK! Send a proxy event on completion to trigger
// a runtime re-poll
// TODO: Send compositor through proxy (?)
{
let (sender, _receiver) = oneshot::channel();
proxy.send_action(Action::Window(
runtime::window::Action::GetLatest(sender),
));
}
} }
}; };
#[cfg(not(target_arch = "wasm32"))]
crate::futures::futures::executor::block_on(
create_compositor,
);
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
{ wasm_bindgen_futures::spawn_local(create_compositor);
wasm_bindgen_futures::spawn_local(create_compositor);
}
continue; #[cfg(not(target_arch = "wasm32"))]
runtime.block_on(create_compositor);
match compositor_receiver
.await
.expect("Wait for compositor")
{
Ok(new_compositor) => {
compositor = Some(new_compositor);
}
Err(error) => {
let _ = control_sender
.start_send(Control::Crash(error.into()));
continue;
}
}
} }
debug::theme_changed(|| { debug::theme_changed(|| {