Merge branch 'master' into beacon
This commit is contained in:
commit
3f67044977
62 changed files with 713 additions and 780 deletions
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
|
|
@ -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
753
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
15
Cargo.toml
15
Cargo.toml
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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"
|
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,4 +9,4 @@ publish = false
|
||||||
iced.workspace = true
|
iced.workspace = true
|
||||||
iced.features = ["canvas"]
|
iced.features = ["canvas"]
|
||||||
|
|
||||||
palette.workspace = true
|
palette = "0.7"
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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"]
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -7,3 +7,7 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced.workspace = true
|
iced.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
iced_test.workspace = true
|
||||||
|
rayon = "1"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
9e67e429f88fd5a64744d9cd4d42e123950bea4a45fea78581bc3d64b12e11f0
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
811a22238f3a40e3e3998f514c0a95f24f2b45449250682d86c8ec392fec5e28
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
6bf957efe807f87f38cfc672f9a05325aadee6256aacca87bbc3281b98160c8a
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
d3f4110fae78a3be3b7a4813e9a432b48b81fff1e982c0244e4ea394074bef55
|
||||||
1
examples/styling/snapshots/dark-tiny-skia.sha256
Normal file
1
examples/styling/snapshots/dark-tiny-skia.sha256
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
578e7420de69d82906d284c59d81fcea0edf81098481fc4dd7b4c1fb577b7f1c
|
||||||
1
examples/styling/snapshots/dracula-tiny-skia.sha256
Normal file
1
examples/styling/snapshots/dracula-tiny-skia.sha256
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
422e841113efaa86e9e37593d0d14f8dd36ad483a81c30a08588f48805e4f9f3
|
||||||
1
examples/styling/snapshots/ferra-tiny-skia.sha256
Normal file
1
examples/styling/snapshots/ferra-tiny-skia.sha256
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
3d616a31842a29b4a3d31fbeef25f95c7b50f33360f1c05e069e0b29b3f7553e
|
||||||
1
examples/styling/snapshots/gruvbox_dark-tiny-skia.sha256
Normal file
1
examples/styling/snapshots/gruvbox_dark-tiny-skia.sha256
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
9a21865bfc075669d368ccac69b975c3e1f6c22ba297dddfa003d4ee1a06641c
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
d5164fb10a92177afd0aab353557d1e3fdaa743962c6a901f05cbfcd0d91e9fb
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
3d5ba3b50f192f8700edbfbf54007e92dfd66997bce7342671afc2b60d556aca
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
90cb13c900d58a56ce170afeefbceb77410d024e7eae6030e181df1c929c3944
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
71b625bc39aaead7a1298e4b2dad51b266526c53deab139858774986395878db
|
||||||
1
examples/styling/snapshots/light-tiny-skia.sha256
Normal file
1
examples/styling/snapshots/light-tiny-skia.sha256
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
f08f80a79959ef1c2fd8f8696a26555f2b2eab6618dd3653a042acab563bcced
|
||||||
1
examples/styling/snapshots/moonfly-tiny-skia.sha256
Normal file
1
examples/styling/snapshots/moonfly-tiny-skia.sha256
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
3302b7934051ff8aa01d70c45e28c444bdc93e8a4da219931aa22465b51c6748
|
||||||
1
examples/styling/snapshots/nightfly-tiny-skia.sha256
Normal file
1
examples/styling/snapshots/nightfly-tiny-skia.sha256
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
661ec43b66213f369ce5a3680e9e8ead56c98d94718da25b12fbb313386944e0
|
||||||
1
examples/styling/snapshots/nord-tiny-skia.sha256
Normal file
1
examples/styling/snapshots/nord-tiny-skia.sha256
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
b5dd22b064220d5f2f90c6f0f381eff41d25f534587d1649ed59e25f878eda97
|
||||||
1
examples/styling/snapshots/oxocarbon-tiny-skia.sha256
Normal file
1
examples/styling/snapshots/oxocarbon-tiny-skia.sha256
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
dd6e7e7ba125a2549143501c3de44427633f0bfa6c0d8b6f66aa95d503874065
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
44b5afe743b753a54f3e68da2fd2701837e7e8cff276ff1e8c349030c83ac72e
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
1b46820f12611b2759eb843688cd13b6024f2096b7986f205398bb029e0c60bd
|
||||||
1
examples/styling/snapshots/tokyo_night-tiny-skia.sha256
Normal file
1
examples/styling/snapshots/tokyo_night-tiny-skia.sha256
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
27a9a8bb08cea7b0a9b9763e332a6833d4fead1a885cdd59a1f5161e3726d6b1
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
ab0dee2cc24f30eb51b376a0385302b45bfeddb373348525dab7f551c27ffec2
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
aef404c7e38aec5387c39cf3a2911688324c1b9b520e5f541b9fd3fd6eefd3a8
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)?;
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
)))]
|
)))]
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
64
src/lib.rs
64
src/lib.rs
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
)))
|
)))
|
||||||
|
|
|
||||||
|
|
@ -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> {
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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(|| {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue