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
|
||||
- name: Run tests
|
||||
run: |
|
||||
cargo test --verbose --workspace
|
||||
cargo test --verbose --workspace --all-features
|
||||
cargo test --verbose --workspace -- --ignored
|
||||
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" }
|
||||
|
||||
[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
|
||||
wgpu = ["iced_renderer/wgpu", "iced_widget/wgpu"]
|
||||
# Enables the `tiny-skia` software renderer backend
|
||||
|
|
@ -43,10 +43,10 @@ markdown = ["iced_widget/markdown"]
|
|||
lazy = ["iced_widget/lazy"]
|
||||
# Enables a debug view in native platforms (press F12)
|
||||
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
|
||||
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
|
||||
smol = ["iced_futures/smol"]
|
||||
# Enables querying system information
|
||||
|
|
@ -67,12 +67,15 @@ auto-detect-theme = ["iced_core/auto-detect-theme"]
|
|||
strict-assertions = ["iced_renderer/strict-assertions"]
|
||||
# Redraws on every runtime event, and not only when a widget requests it
|
||||
unconditional-rendering = ["iced_winit/unconditional-rendering"]
|
||||
# Enables support for the `sipper` library
|
||||
sipper = ["iced_runtime/sipper"]
|
||||
|
||||
[dependencies]
|
||||
iced_debug.workspace = true
|
||||
iced_core.workspace = true
|
||||
iced_futures.workspace = true
|
||||
iced_renderer.workspace = true
|
||||
iced_runtime.workspace = true
|
||||
iced_widget.workspace = true
|
||||
iced_winit.features = ["program"]
|
||||
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_winit = { version = "0.14.0-dev", path = "winit" }
|
||||
|
||||
async-std = "1.0"
|
||||
bincode = "1.3"
|
||||
bitflags = "2.0"
|
||||
bytemuck = { version = "1.0", features = ["derive"] }
|
||||
bytes = "1.6"
|
||||
cosmic-text = "0.13"
|
||||
dark-light = "2.0"
|
||||
futures = "0.3"
|
||||
futures = { version = "0.3", default-features = false }
|
||||
glam = "0.25"
|
||||
cryoglyph = { git = "https://github.com/iced-rs/cryoglyph.git", rev = "be2defe4a13fd7c97c6f4c81e8e085463eb578dc" }
|
||||
guillotiere = "0.6"
|
||||
|
|
@ -172,7 +174,6 @@ lyon = "1.0"
|
|||
lyon_path = "1.0"
|
||||
num-traits = "0.2"
|
||||
ouroboros = "0.18"
|
||||
palette = "0.7"
|
||||
png = "0.17"
|
||||
pulldown-cmark = "0.12"
|
||||
qrcode = { version = "0.13", default-features = false }
|
||||
|
|
@ -183,7 +184,7 @@ serde = "1.0"
|
|||
semver = "1.0"
|
||||
sha2 = "0.10"
|
||||
sipper = "0.1"
|
||||
smol = "1.0"
|
||||
smol = "2"
|
||||
smol_str = "0.2"
|
||||
softbuffer = "0.4"
|
||||
syntect = "5.1"
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ glam.workspace = true
|
|||
lilt.workspace = true
|
||||
log.workspace = true
|
||||
num-traits.workspace = true
|
||||
palette.workspace = true
|
||||
rustc-hash.workspace = true
|
||||
smol_str.workspace = true
|
||||
thiserror.workspace = true
|
||||
|
|
@ -36,9 +35,3 @@ dark-light.optional = true
|
|||
serde.workspace = true
|
||||
serde.optional = true
|
||||
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.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
#[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)]
|
||||
mod tests {
|
||||
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]
|
||||
fn parse() {
|
||||
|
|
|
|||
|
|
@ -151,39 +151,6 @@ impl<T> InputMethod<T> {
|
|||
/// 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
|
||||
/// 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)]
|
||||
pub enum Event {
|
||||
/// Notifies when the IME was opened.
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
//! Define the colors of a theme.
|
||||
use crate::{Color, color};
|
||||
|
||||
use palette::color_difference::Wcag21RelativeContrast;
|
||||
use palette::rgb::Rgb;
|
||||
use palette::{FromColor, Hsl, Mix};
|
||||
|
||||
use std::sync::LazyLock;
|
||||
|
||||
/// 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 {
|
||||
let mut hsl = to_hsl(color);
|
||||
|
||||
hsl.lightness = if hsl.lightness - amount < 0.0 {
|
||||
hsl.l = if hsl.l - amount < 0.0 {
|
||||
0.0
|
||||
} else {
|
||||
hsl.lightness - amount
|
||||
hsl.l - amount
|
||||
};
|
||||
|
||||
from_hsl(hsl)
|
||||
|
|
@ -623,10 +626,10 @@ fn darken(color: Color, amount: f32) -> Color {
|
|||
fn lighten(color: Color, amount: f32) -> 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
|
||||
} else {
|
||||
hsl.lightness + amount
|
||||
hsl.l + amount
|
||||
};
|
||||
|
||||
from_hsl(hsl)
|
||||
|
|
@ -643,17 +646,24 @@ fn deviate(color: Color, amount: f32) -> Color {
|
|||
fn muted(color: Color) -> Color {
|
||||
let mut hsl = to_hsl(color);
|
||||
|
||||
hsl.saturation = hsl.saturation.min(0.5);
|
||||
hsl.s = hsl.s.min(0.5);
|
||||
|
||||
from_hsl(hsl)
|
||||
}
|
||||
|
||||
fn mix(a: Color, b: Color, factor: f32) -> Color {
|
||||
let a_lin = Rgb::from(a).into_linear();
|
||||
let b_lin = Rgb::from(b).into_linear();
|
||||
let b_amount = factor.clamp(0.0, 1.0);
|
||||
let a_amount = 1.0 - b_amount;
|
||||
|
||||
let mixed = a_lin.mix(b_lin, factor);
|
||||
Rgb::from_linear(mixed).into()
|
||||
let a_linear = a.into_linear().map(|c| c * a_amount);
|
||||
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 {
|
||||
|
|
@ -681,27 +691,85 @@ fn readable(background: Color, text: Color) -> Color {
|
|||
}
|
||||
|
||||
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 {
|
||||
let a_srgb = Rgb::from(a);
|
||||
let b_srgb = Rgb::from(b);
|
||||
|
||||
a_srgb.has_enhanced_contrast_text(b_srgb)
|
||||
relative_contrast(a, b) >= 7.0
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio
|
||||
fn relative_contrast(a: Color, b: Color) -> f32 {
|
||||
let a_srgb = Rgb::from(a);
|
||||
let b_srgb = Rgb::from(b);
|
||||
|
||||
a_srgb.relative_contrast(b_srgb)
|
||||
let lum_a = relative_luminance(a);
|
||||
let lum_b = relative_luminance(b);
|
||||
(lum_a.max(lum_b) + 0.05) / (lum_a.min(lum_b) + 0.05)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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 {
|
||||
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.features = ["canvas"]
|
||||
|
||||
palette.workspace = true
|
||||
palette = "0.7"
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ pub enum Message {
|
|||
impl ColorPalette {
|
||||
fn update(&mut self, message: 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::HsvColorChanged(hsv) => Rgb::from_color(hsv),
|
||||
Message::HwbColorChanged(hwb) => Rgb::from_color(hwb),
|
||||
|
|
@ -53,13 +53,13 @@ impl ColorPalette {
|
|||
Message::LchColorChanged(lch) => Rgb::from_color(lch),
|
||||
};
|
||||
|
||||
self.theme = Theme::new(srgb);
|
||||
self.theme = Theme::new(to_color(srgb));
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Message> {
|
||||
let base = self.theme.base;
|
||||
|
||||
let srgb = Rgb::from(base);
|
||||
let srgb = to_rgb(base);
|
||||
let hsl = palette::Hsl::from_color(srgb);
|
||||
let hsv = palette::Hsv::from_color(srgb);
|
||||
let hwb = palette::Hwb::from_color(srgb);
|
||||
|
|
@ -108,7 +108,7 @@ impl Theme {
|
|||
let base = base.into();
|
||||
|
||||
// Convert to HSL color for manipulation
|
||||
let hsl = Hsl::from_color(Rgb::from(base));
|
||||
let hsl = Hsl::from_color(to_rgb(base));
|
||||
|
||||
let lower = [
|
||||
hsl.shift_hue(-135.0).lighten(0.075),
|
||||
|
|
@ -127,12 +127,12 @@ impl Theme {
|
|||
Theme {
|
||||
lower: lower
|
||||
.iter()
|
||||
.map(|&color| Rgb::from_color(color).into())
|
||||
.map(|&color| to_color(Rgb::from_color(color)))
|
||||
.collect(),
|
||||
base,
|
||||
higher: higher
|
||||
.iter()
|
||||
.map(|&color| Rgb::from_color(color).into())
|
||||
.map(|&color| to_color(Rgb::from_color(color)))
|
||||
.collect(),
|
||||
canvas_cache: canvas::Cache::default(),
|
||||
}
|
||||
|
|
@ -215,14 +215,14 @@ impl Theme {
|
|||
|
||||
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() {
|
||||
let pct = (i as f32 + 1.0) / (self.len() as f32 + 1.0);
|
||||
let graded = Hsl {
|
||||
lightness: 1.0 - pct,
|
||||
..hsl
|
||||
};
|
||||
let color: Color = Rgb::from_color(graded).into();
|
||||
let color: Color = to_color(Rgb::from_color(graded));
|
||||
|
||||
let anchor = Point {
|
||||
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]
|
||||
iced.workspace = true
|
||||
iced.features = ["tokio"]
|
||||
iced.features = ["tokio", "sipper"]
|
||||
|
||||
[dependencies.reqwest]
|
||||
version = "0.12"
|
||||
|
|
|
|||
|
|
@ -112,9 +112,7 @@ impl Download {
|
|||
|
||||
pub fn start(&mut self) -> Task<Update> {
|
||||
match self.state {
|
||||
State::Idle { .. }
|
||||
| State::Finished { .. }
|
||||
| State::Errored { .. } => {
|
||||
State::Idle | State::Finished | State::Errored => {
|
||||
let (task, handle) = Task::sip(
|
||||
download(
|
||||
"https://huggingface.co/\
|
||||
|
|
@ -156,10 +154,10 @@ impl Download {
|
|||
|
||||
pub fn view(&self) -> Element<Message> {
|
||||
let current_progress = match &self.state {
|
||||
State::Idle { .. } => 0.0,
|
||||
State::Idle => 0.0,
|
||||
State::Downloading { progress, .. } => *progress,
|
||||
State::Finished { .. } => 100.0,
|
||||
State::Errored { .. } => 0.0,
|
||||
State::Finished => 100.0,
|
||||
State::Errored => 0.0,
|
||||
};
|
||||
|
||||
let progress_bar = progress_bar(0.0..=100.0, current_progress);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
iced.workspace = true
|
||||
iced.features = ["tokio", "image", "web-colors", "debug"]
|
||||
iced.features = ["tokio", "sipper", "image", "web-colors", "debug"]
|
||||
|
||||
reqwest.version = "0.12"
|
||||
reqwest.features = ["json"]
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@ iced_wgpu.workspace = true
|
|||
iced_widget.workspace = true
|
||||
iced_widget.features = ["wgpu"]
|
||||
|
||||
futures.workspace = true
|
||||
futures.features = ["thread-pool"]
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
tracing-subscriber = "0.3"
|
||||
|
||||
|
|
|
|||
|
|
@ -36,9 +36,10 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
|||
Loading,
|
||||
Ready {
|
||||
window: Arc<winit::window::Window>,
|
||||
queue: wgpu::Queue,
|
||||
device: wgpu::Device,
|
||||
surface: wgpu::Surface<'static>,
|
||||
format: wgpu::TextureFormat,
|
||||
device: wgpu::Device,
|
||||
renderer: Renderer,
|
||||
scene: Scene,
|
||||
controls: Controls,
|
||||
|
|
@ -144,11 +145,12 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
|||
let controls = Controls::new();
|
||||
|
||||
// Initialize iced
|
||||
|
||||
let renderer = {
|
||||
let engine = Engine::new(
|
||||
&adapter,
|
||||
device.clone(),
|
||||
queue,
|
||||
queue.clone(),
|
||||
format,
|
||||
None,
|
||||
);
|
||||
|
|
@ -161,10 +163,11 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
|||
|
||||
*self = Self::Ready {
|
||||
window,
|
||||
device,
|
||||
queue,
|
||||
renderer,
|
||||
surface,
|
||||
format,
|
||||
device,
|
||||
renderer,
|
||||
scene,
|
||||
controls,
|
||||
events: Vec::new(),
|
||||
|
|
@ -187,6 +190,7 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
|||
let Self::Ready {
|
||||
window,
|
||||
device,
|
||||
queue,
|
||||
surface,
|
||||
format,
|
||||
renderer,
|
||||
|
|
@ -233,16 +237,16 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
|||
|
||||
match surface.get_current_texture() {
|
||||
Ok(frame) => {
|
||||
let mut encoder = device.create_command_encoder(
|
||||
&wgpu::CommandEncoderDescriptor { label: None },
|
||||
);
|
||||
|
||||
let view = frame.texture.create_view(
|
||||
&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(
|
||||
&view,
|
||||
&mut encoder,
|
||||
|
|
@ -253,7 +257,10 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
|||
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(
|
||||
controls.view(),
|
||||
viewport.logical_size(),
|
||||
|
|
@ -288,7 +295,7 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
|||
viewport,
|
||||
);
|
||||
|
||||
// Then we submit the work
|
||||
// Present the frame
|
||||
frame.present();
|
||||
|
||||
// Update the mouse cursor
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ impl Pokedex {
|
|||
let subtitle = match self {
|
||||
Pokedex::Loading => "Loading",
|
||||
Pokedex::Loaded { pokemon, .. } => &pokemon.name,
|
||||
Pokedex::Errored { .. } => "Whoops!",
|
||||
Pokedex::Errored => "Whoops!",
|
||||
};
|
||||
|
||||
format!("{subtitle} - Pokédex")
|
||||
|
|
|
|||
|
|
@ -7,3 +7,7 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
(old, new) if old < new => {
|
||||
instants.extend(
|
||||
std::iter::repeat(Some(Instant::now())).take(new - old),
|
||||
);
|
||||
instants.extend(std::iter::repeat_n(
|
||||
Some(Instant::now()),
|
||||
new - old,
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,14 +7,15 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
iced.workspace = true
|
||||
iced.features = ["async-std", "debug"]
|
||||
iced.features = ["tokio", "debug"]
|
||||
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
uuid = { version = "1.0", features = ["v4", "fast-rng", "serde"] }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
async-std.workspace = true
|
||||
tokio.workspace = true
|
||||
tokio.features = ["fs", "time"]
|
||||
directories = "6.0"
|
||||
tracing-subscriber = "0.3"
|
||||
|
||||
|
|
|
|||
|
|
@ -483,7 +483,6 @@ enum LoadError {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
enum SaveError {
|
||||
File,
|
||||
Write,
|
||||
Format,
|
||||
}
|
||||
|
|
@ -505,15 +504,7 @@ impl SavedState {
|
|||
}
|
||||
|
||||
async fn load() -> Result<SavedState, LoadError> {
|
||||
use async_std::prelude::*;
|
||||
|
||||
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)
|
||||
let contents = tokio::fs::read_to_string(Self::path())
|
||||
.await
|
||||
.map_err(|_| LoadError::File)?;
|
||||
|
||||
|
|
@ -521,31 +512,25 @@ impl SavedState {
|
|||
}
|
||||
|
||||
async fn save(self) -> Result<(), SaveError> {
|
||||
use async_std::prelude::*;
|
||||
|
||||
let json = serde_json::to_string_pretty(&self)
|
||||
.map_err(|_| SaveError::Format)?;
|
||||
|
||||
let path = Self::path();
|
||||
|
||||
if let Some(dir) = path.parent() {
|
||||
async_std::fs::create_dir_all(dir)
|
||||
tokio::fs::create_dir_all(dir)
|
||||
.await
|
||||
.map_err(|_| SaveError::File)?;
|
||||
.map_err(|_| SaveError::Write)?;
|
||||
}
|
||||
|
||||
{
|
||||
let mut file = async_std::fs::File::create(path)
|
||||
.await
|
||||
.map_err(|_| SaveError::File)?;
|
||||
|
||||
file.write_all(json.as_bytes())
|
||||
tokio::fs::write(path, json.as_bytes())
|
||||
.await
|
||||
.map_err(|_| SaveError::Write)?;
|
||||
}
|
||||
|
||||
// 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(())
|
||||
}
|
||||
|
|
@ -571,7 +556,7 @@ impl SavedState {
|
|||
}
|
||||
|
||||
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)
|
||||
.map_err(|_| SaveError::Format)?;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
iced.workspace = true
|
||||
iced.features = ["debug", "tokio"]
|
||||
iced.features = ["debug", "tokio", "sipper"]
|
||||
|
||||
warp = "0.3"
|
||||
|
||||
|
|
|
|||
|
|
@ -24,14 +24,12 @@ thread-pool = ["futures/thread-pool"]
|
|||
iced_core.workspace = true
|
||||
|
||||
futures.workspace = true
|
||||
futures.features = ["std", "async-await"]
|
||||
|
||||
log.workspace = true
|
||||
rustc-hash.workspace = true
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
async-std.workspace = true
|
||||
async-std.optional = true
|
||||
async-std.features = ["unstable"]
|
||||
|
||||
smol.workspace = true
|
||||
smol.optional = true
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,9 @@
|
|||
//!
|
||||
//! - On native platforms, it will use:
|
||||
//! - `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::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`.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
|
|
@ -13,24 +12,17 @@ mod platform {
|
|||
#[cfg(feature = "tokio")]
|
||||
pub use crate::backend::native::tokio::*;
|
||||
|
||||
#[cfg(all(feature = "async-std", not(feature = "tokio"),))]
|
||||
pub use crate::backend::native::async_std::*;
|
||||
|
||||
#[cfg(all(
|
||||
feature = "smol",
|
||||
not(any(feature = "tokio", feature = "async-std")),
|
||||
))]
|
||||
#[cfg(all(feature = "smol", not(feature = "tokio"),))]
|
||||
pub use crate::backend::native::smol::*;
|
||||
|
||||
#[cfg(all(
|
||||
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::*;
|
||||
|
||||
#[cfg(not(any(
|
||||
feature = "tokio",
|
||||
feature = "async-std",
|
||||
feature = "smol",
|
||||
feature = "thread-pool"
|
||||
)))]
|
||||
|
|
|
|||
|
|
@ -2,9 +2,6 @@
|
|||
#[cfg(feature = "tokio")]
|
||||
pub mod tokio;
|
||||
|
||||
#[cfg(feature = "async-std")]
|
||||
pub mod async_std;
|
||||
|
||||
#[cfg(feature = "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) {
|
||||
smol::spawn(future).detach();
|
||||
}
|
||||
|
||||
fn block_on<T>(&self, future: impl Future<Output = T>) -> T {
|
||||
smol::block_on(future)
|
||||
}
|
||||
}
|
||||
|
||||
pub mod time {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@ impl crate::Executor for Executor {
|
|||
fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) {
|
||||
self.spawn_ok(future);
|
||||
}
|
||||
|
||||
fn block_on<T>(&self, future: impl Future<Output = T>) -> T {
|
||||
futures::executor::block_on(future)
|
||||
}
|
||||
}
|
||||
|
||||
pub mod time {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,10 @@ impl crate::Executor for Executor {
|
|||
let _guard = tokio::runtime::Runtime::enter(self);
|
||||
f()
|
||||
}
|
||||
|
||||
fn block_on<T>(&self, future: impl Future<Output = T>) -> T {
|
||||
self.block_on(future)
|
||||
}
|
||||
}
|
||||
|
||||
pub mod time {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
//! A backend that does nothing!
|
||||
use crate::MaybeSend;
|
||||
|
||||
/// An executor that drops all the futures, instead of spawning them.
|
||||
#[derive(Debug)]
|
||||
|
|
@ -9,11 +10,12 @@ impl crate::Executor for Executor {
|
|||
Ok(Self)
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn spawn(&self, _future: impl Future<Output = ()> + Send + 'static) {}
|
||||
fn spawn(&self, _future: impl Future<Output = ()> + MaybeSend + 'static) {}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn spawn(&self, _future: impl Future<Output = ()> + 'static) {}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn block_on<T>(&self, _future: impl Future<Output = T>) -> T {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
pub mod time {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@ pub trait Executor: Sized {
|
|||
/// Spawns a future in the [`Executor`].
|
||||
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`].
|
||||
///
|
||||
/// Some executors, like `tokio`, require some global state to be in place
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//! Run commands and keep track of subscriptions.
|
||||
use crate::subscription;
|
||||
use crate::{BoxFuture, BoxStream, Executor, MaybeSend};
|
||||
use crate::{BoxStream, Executor, MaybeSend};
|
||||
|
||||
use futures::{Sink, channel::mpsc};
|
||||
use std::marker::PhantomData;
|
||||
|
|
@ -50,22 +50,10 @@ where
|
|||
self.executor.enter(f)
|
||||
}
|
||||
|
||||
/// Spawns a [`Future`] in the [`Runtime`].
|
||||
///
|
||||
/// The resulting `Message` will be forwarded to the `Sender` of the
|
||||
/// [`Runtime`].
|
||||
///
|
||||
/// [`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 future to completion in the current thread within the [`Runtime`].
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn block_on<T>(&mut self, future: impl Future<Output = T>) -> T {
|
||||
self.executor.block_on(future)
|
||||
}
|
||||
|
||||
/// Runs a [`Stream`] in the [`Runtime`] until completion.
|
||||
|
|
|
|||
|
|
@ -19,8 +19,9 @@ iced_core.workspace = true
|
|||
iced_debug.workspace = true
|
||||
|
||||
iced_futures.workspace = true
|
||||
iced_futures.features = ["thread-pool"]
|
||||
|
||||
raw-window-handle.workspace = true
|
||||
sipper.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::{BoxStream, MaybeSend, boxed_stream};
|
||||
|
||||
use std::convert::Infallible;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[cfg(feature = "sipper")]
|
||||
#[doc(no_inline)]
|
||||
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
|
||||
/// progress with the first closure and the output with the second one.
|
||||
#[cfg(feature = "sipper")]
|
||||
pub fn sip<S>(
|
||||
sipper: S,
|
||||
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.
|
||||
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();
|
||||
|
||||
Task {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//! Create and run iced applications step by step.
|
||||
//!
|
||||
//! # Example
|
||||
//! ```no_run
|
||||
//! ```no_run,standalone_crate
|
||||
//! use iced::widget::{button, column, text, Column};
|
||||
//! use iced::Theme;
|
||||
//!
|
||||
|
|
@ -43,7 +43,7 @@ use std::borrow::Cow;
|
|||
/// Creates an iced [`Application`] given its boot, update, and view logic.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// ```no_run,standalone_crate
|
||||
/// use iced::widget::{button, column, text, Column};
|
||||
///
|
||||
/// pub fn main() -> iced::Result {
|
||||
|
|
|
|||
64
src/lib.rs
64
src/lib.rs
|
|
@ -29,7 +29,7 @@
|
|||
//! # The Pocket Guide
|
||||
//! Start by calling [`run`]:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! ```no_run,standalone_crate
|
||||
//! pub fn main() -> iced::Result {
|
||||
//! iced::run(update, view)
|
||||
//! }
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
//!
|
||||
//! Define an `update` function to __change__ your state:
|
||||
//!
|
||||
//! ```rust
|
||||
//! ```standalone_crate
|
||||
//! fn update(counter: &mut u64, message: Message) {
|
||||
//! match message {
|
||||
//! Message::Increment => *counter += 1,
|
||||
|
|
@ -51,7 +51,7 @@
|
|||
//!
|
||||
//! Define a `view` function to __display__ your state:
|
||||
//!
|
||||
//! ```rust
|
||||
//! ```standalone_crate
|
||||
//! use iced::widget::{button, text};
|
||||
//! use iced::Element;
|
||||
//!
|
||||
|
|
@ -64,7 +64,7 @@
|
|||
//!
|
||||
//! And create a `Message` enum to __connect__ `view` and `update` together:
|
||||
//!
|
||||
//! ```rust
|
||||
//! ```standalone_crate
|
||||
//! #[derive(Debug, Clone)]
|
||||
//! enum Message {
|
||||
//! Increment,
|
||||
|
|
@ -74,7 +74,7 @@
|
|||
//! ## Custom State
|
||||
//! You can define your own struct for your state:
|
||||
//!
|
||||
//! ```rust
|
||||
//! ```standalone_crate
|
||||
//! #[derive(Default)]
|
||||
//! struct Counter {
|
||||
//! value: u64,
|
||||
|
|
@ -83,7 +83,7 @@
|
|||
//!
|
||||
//! But you have to change `update` and `view` accordingly:
|
||||
//!
|
||||
//! ```rust
|
||||
//! ```standalone_crate
|
||||
//! # struct Counter { value: u64 }
|
||||
//! # #[derive(Clone)]
|
||||
//! # enum Message { Increment }
|
||||
|
|
@ -108,7 +108,7 @@
|
|||
//!
|
||||
//! Widgets are configured using the builder pattern:
|
||||
//!
|
||||
//! ```rust
|
||||
//! ```standalone_crate
|
||||
//! # struct Counter { value: u64 }
|
||||
//! # #[derive(Clone)]
|
||||
//! # enum Message { Increment }
|
||||
|
|
@ -138,7 +138,7 @@
|
|||
//! Building your layout will often consist in using a combination of
|
||||
//! [rows], [columns], and [containers]:
|
||||
//!
|
||||
//! ```rust
|
||||
//! ```standalone_crate
|
||||
//! # struct State;
|
||||
//! # enum Message {}
|
||||
//! use iced::widget::{column, container, row};
|
||||
|
|
@ -181,7 +181,7 @@
|
|||
//!
|
||||
//! A fixed numeric [`Length`] in [`Pixels`] can also be used:
|
||||
//!
|
||||
//! ```rust
|
||||
//! ```standalone_crate
|
||||
//! # struct State;
|
||||
//! # enum Message {}
|
||||
//! use iced::widget::container;
|
||||
|
|
@ -197,7 +197,7 @@
|
|||
//! function and leveraging the [`Application`] builder, instead of directly
|
||||
//! calling [`run`]:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! ```no_run,standalone_crate
|
||||
//! # struct State;
|
||||
//! use iced::Theme;
|
||||
//!
|
||||
|
|
@ -231,7 +231,7 @@
|
|||
//!
|
||||
//! The appearance of a widget can be changed by calling its `style` method:
|
||||
//!
|
||||
//! ```rust
|
||||
//! ```standalone_crate
|
||||
//! # struct State;
|
||||
//! # enum Message {}
|
||||
//! use iced::widget::container;
|
||||
|
|
@ -245,7 +245,7 @@
|
|||
//! The `style` method of a widget takes a closure that, given the current active
|
||||
//! [`Theme`], returns the widget style:
|
||||
//!
|
||||
//! ```rust
|
||||
//! ```standalone_crate
|
||||
//! # struct State;
|
||||
//! # #[derive(Clone)]
|
||||
//! # enum Message {}
|
||||
|
|
@ -290,7 +290,7 @@
|
|||
//! A [`Task`] can be leveraged to perform asynchronous work, like running a
|
||||
//! future or a stream:
|
||||
//!
|
||||
//! ```rust
|
||||
//! ```standalone_crate
|
||||
//! # #[derive(Clone)]
|
||||
//! # struct Weather;
|
||||
//! use iced::Task;
|
||||
|
|
@ -338,7 +338,7 @@
|
|||
//!
|
||||
//! You will need to define a `subscription` function and use the [`Application`] builder:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! ```no_run,standalone_crate
|
||||
//! # struct State;
|
||||
//! use iced::window;
|
||||
//! use iced::{Size, Subscription};
|
||||
|
|
@ -379,7 +379,7 @@
|
|||
//! A common pattern is to leverage this composability to split an
|
||||
//! application into different screens:
|
||||
//!
|
||||
//! ```rust
|
||||
//! ```standalone_crate
|
||||
//! # mod contacts {
|
||||
//! # use iced::{Element, Task};
|
||||
//! # pub struct Contacts;
|
||||
|
|
@ -485,6 +485,18 @@ use iced_winit::runtime;
|
|||
pub use iced_futures::futures;
|
||||
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")]
|
||||
pub use iced_highlighter as highlighter;
|
||||
|
||||
|
|
@ -523,9 +535,10 @@ pub use alignment::Vertical::{Bottom, Top};
|
|||
|
||||
pub mod task {
|
||||
//! Create runtime tasks.
|
||||
pub use crate::runtime::task::{
|
||||
Handle, Never, Sipper, Straw, Task, sipper, stream,
|
||||
};
|
||||
pub use crate::runtime::task::{Handle, Task};
|
||||
|
||||
#[cfg(feature = "sipper")]
|
||||
pub use crate::runtime::task::{Never, Sipper, Straw, sipper, stream};
|
||||
}
|
||||
|
||||
pub mod clipboard {
|
||||
|
|
@ -538,18 +551,7 @@ pub mod clipboard {
|
|||
pub mod executor {
|
||||
//! Choose your preferred executor to power your application.
|
||||
pub use iced_futures::Executor;
|
||||
|
||||
/// 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 use iced_futures::backend::default::Executor as Default;
|
||||
}
|
||||
|
||||
pub mod font {
|
||||
|
|
@ -658,7 +660,7 @@ pub type Result = std::result::Result<(), Error>;
|
|||
/// This is equivalent to chaining [`application()`] with [`Application::run`].
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// ```no_run,standalone_crate
|
||||
/// use iced::widget::{button, column, text, Column};
|
||||
///
|
||||
/// pub fn main() -> iced::Result {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ pub use crate::core::time::*;
|
|||
docsrs,
|
||||
doc(cfg(any(
|
||||
feature = "tokio",
|
||||
feature = "async-std",
|
||||
feature = "smol",
|
||||
target_arch = "wasm32"
|
||||
)))
|
||||
|
|
|
|||
|
|
@ -4,9 +4,8 @@ use std::ops::RangeBounds;
|
|||
|
||||
pub const MAX_WRITE_SIZE: usize = 100 * 1024;
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
const MAX_WRITE_SIZE_U64: NonZeroU64 =
|
||||
unsafe { NonZeroU64::new_unchecked(MAX_WRITE_SIZE as u64) };
|
||||
const MAX_WRITE_SIZE_U64: NonZeroU64 = NonZeroU64::new(MAX_WRITE_SIZE as u64)
|
||||
.expect("MAX_WRITE_SIZE must be non-zero");
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Buffer<T> {
|
||||
|
|
|
|||
|
|
@ -120,9 +120,11 @@ impl Value {
|
|||
/// dot ('•') character.
|
||||
pub fn secure(&self) -> Self {
|
||||
Self {
|
||||
graphemes: std::iter::repeat(String::from("•"))
|
||||
.take(self.graphemes.len())
|
||||
.collect(),
|
||||
graphemes: std::iter::repeat_n(
|
||||
String::from("•"),
|
||||
self.graphemes.len(),
|
||||
)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -386,9 +386,11 @@ where
|
|||
renderer,
|
||||
&layout::Limits::new(
|
||||
Size::ZERO,
|
||||
self.snap_within_viewport
|
||||
.then(|| viewport.size())
|
||||
.unwrap_or(Size::INFINITY),
|
||||
if self.snap_within_viewport {
|
||||
viewport.size()
|
||||
} else {
|
||||
Size::INFINITY
|
||||
},
|
||||
)
|
||||
.shrink(Padding::new(self.padding)),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -503,33 +503,10 @@ async fn run_instance<P>(
|
|||
let mut ui_caches = FxHashMap::default();
|
||||
let mut user_interfaces = ManuallyDrop::new(FxHashMap::default());
|
||||
let mut clipboard = Clipboard::unconnected();
|
||||
let mut compositor_receiver: Option<oneshot::Receiver<_>> = None;
|
||||
|
||||
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
|
||||
} else if let Ok(event) = event_receiver.try_next() {
|
||||
let event = if let Ok(event) = event_receiver.try_next() {
|
||||
event
|
||||
} else {
|
||||
event_receiver.next().await
|
||||
|
|
@ -548,17 +525,17 @@ async fn run_instance<P>(
|
|||
on_open,
|
||||
} => {
|
||||
if compositor.is_none() {
|
||||
let (compositor_sender, new_compositor_receiver) =
|
||||
let (compositor_sender, compositor_receiver) =
|
||||
oneshot::channel();
|
||||
|
||||
compositor_receiver = Some(new_compositor_receiver);
|
||||
|
||||
let create_compositor = {
|
||||
let window = window.clone();
|
||||
let mut proxy = proxy.clone();
|
||||
let default_fonts = default_fonts.clone();
|
||||
|
||||
async move {
|
||||
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 {
|
||||
for font in default_fonts {
|
||||
|
|
@ -567,34 +544,42 @@ async fn run_instance<P>(
|
|||
}
|
||||
|
||||
compositor_sender
|
||||
.send(compositor.map(|compositor| {
|
||||
(
|
||||
compositor,
|
||||
Event::WindowCreated {
|
||||
id,
|
||||
window,
|
||||
exit_on_close_request,
|
||||
make_visible,
|
||||
on_open,
|
||||
},
|
||||
)
|
||||
}))
|
||||
.send(compositor)
|
||||
.ok()
|
||||
.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")]
|
||||
{
|
||||
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(|| {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue