Merge branch 'master' into feature/test-recorder

This commit is contained in:
Héctor Ramón Jiménez 2025-07-08 00:29:55 +02:00
commit 98d8f466bb
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
98 changed files with 643 additions and 204 deletions

85
Cargo.lock generated
View file

@ -538,6 +538,26 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "bincode"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740"
dependencies = [
"bincode_derive",
"serde",
"unty",
]
[[package]]
name = "bincode_derive"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09"
dependencies = [
"virtue",
]
[[package]] [[package]]
name = "bit-set" name = "bit-set"
version = "0.8.0" version = "0.8.0"
@ -730,6 +750,17 @@ dependencies = [
"wayland-client", "wayland-client",
] ]
[[package]]
name = "cargo-hot-protocol"
version = "0.1.0"
source = "git+https://github.com/hecrj/cargo-hot.git?rev=b8dc518b8640928178a501257e353b73bc06cf47#b8dc518b8640928178a501257e353b73bc06cf47"
dependencies = [
"anyhow",
"bincode 2.0.1",
"log",
"subsecond",
]
[[package]] [[package]]
name = "cast" name = "cast"
version = "0.3.0" version = "0.3.0"
@ -2464,7 +2495,7 @@ dependencies = [
name = "iced_beacon" name = "iced_beacon"
version = "0.14.0-dev" version = "0.14.0-dev"
dependencies = [ dependencies = [
"bincode", "bincode 1.3.3",
"futures", "futures",
"iced_core", "iced_core",
"log", "log",
@ -2496,6 +2527,7 @@ dependencies = [
name = "iced_debug" name = "iced_debug"
version = "0.14.0-dev" version = "0.14.0-dev"
dependencies = [ dependencies = [
"cargo-hot-protocol",
"iced_beacon", "iced_beacon",
"iced_core", "iced_core",
"iced_futures", "iced_futures",
@ -3293,6 +3325,15 @@ version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "memfd"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64"
dependencies = [
"rustix 0.38.44",
]
[[package]] [[package]]
name = "memmap2" name = "memmap2"
version = "0.9.5" version = "0.9.5"
@ -5518,6 +5559,34 @@ dependencies = [
"rayon", "rayon",
] ]
[[package]]
name = "subsecond"
version = "0.7.0-alpha.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43c5b40acd555d02d9a0b5bf4080dbf2cd085d5e2eb2ae7851cb14b9bf5af15c"
dependencies = [
"js-sys",
"libc",
"libloading",
"memfd",
"memmap2",
"serde",
"subsecond-types",
"thiserror 2.0.12",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "subsecond-types"
version = "0.7.0-alpha.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bedadae58a56e137ac970c38c44bff38cee24400fef64c37d5a188a065b1ec1f"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "subtle" name = "subtle"
version = "2.6.1" version = "2.6.1"
@ -5595,7 +5664,7 @@ version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1" checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1"
dependencies = [ dependencies = [
"bincode", "bincode 1.3.3",
"bitflags 1.3.2", "bitflags 1.3.2",
"flate2", "flate2",
"fnv", "fnv",
@ -6281,6 +6350,12 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "unty"
version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
[[package]] [[package]]
name = "url" name = "url"
version = "2.5.4" version = "2.5.4"
@ -6406,6 +6481,12 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "virtue"
version = "0.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1"
[[package]] [[package]]
name = "visible_bounds" name = "visible_bounds"
version = "0.1.0" version = "0.1.0"

View file

@ -41,10 +41,12 @@ qr_code = ["iced_widget/qr_code"]
markdown = ["iced_widget/markdown"] markdown = ["iced_widget/markdown"]
# Enables lazy widgets # Enables lazy widgets
lazy = ["iced_widget/lazy"] lazy = ["iced_widget/lazy"]
# Enables a debug view in native platforms (press F12) # Enables debug metrics in native platforms (press F12)
debug = ["iced_winit/debug", "iced_devtools"] debug = ["iced_winit/debug", "iced_devtools"]
# Enables time-travel debugging (very experimental!) # Enables time-travel debugging (very experimental!)
time-travel = ["debug", "iced_devtools/time-travel"] time-travel = ["debug", "iced_devtools/time-travel"]
# Enables hot reloading (very experimental!)
hot = ["debug", "iced_debug/hot"]
# Enables the tester developer tool for recording and playing tests (press F12) # Enables the tester developer tool for recording and playing tests (press F12)
tester = ["debug", "test", "iced_devtools/tester"] tester = ["debug", "test", "iced_devtools/tester"]
# Enables testing features (e.g. application presets) # Enables testing features (e.g. application presets)
@ -171,6 +173,7 @@ 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"
cargo-hot = { package = "cargo-hot-protocol", git = "https://github.com/hecrj/cargo-hot.git", rev = "b8dc518b8640928178a501257e353b73bc06cf47" }
cosmic-text = "0.14" cosmic-text = "0.14"
dark-light = "2.0" dark-light = "2.0"
futures = { version = "0.3", default-features = false } futures = { version = "0.3", default-features = false }

View file

@ -137,16 +137,24 @@ where
// //
// We use the maximum cross length obtained in the first pass as the maximum // We use the maximum cross length obtained in the first pass as the maximum
// cross limit. // cross limit.
//
// We can defer the layout of any elements that have a fixed size in the main axis,
// allowing them to use the cross calculations of the next pass.
if cross_compress && some_fill_cross { if cross_compress && some_fill_cross {
for (i, (child, tree)) in items.iter().zip(trees.iter_mut()).enumerate() for (i, (child, tree)) in items.iter().zip(trees.iter_mut()).enumerate()
{ {
let (fill_main_factor, fill_cross_factor) = { let (main_size, cross_size) = {
let size = child.as_widget().size(); let size = child.as_widget().size();
axis.pack(size.width.fill_factor(), size.height.fill_factor()) axis.pack(size.width, size.height)
}; };
if fill_main_factor == 0 && fill_cross_factor != 0 { if main_size.fill_factor() == 0 && cross_size.fill_factor() != 0 {
if let Length::Fixed(main) = main_size {
available -= main;
continue;
}
let (max_width, max_height) = axis.pack(available, cross); let (max_width, max_height) = axis.pack(available, cross);
let child_limits = let child_limits =
@ -176,9 +184,9 @@ where
}; };
// THIRD PASS // THIRD PASS
// We only have the elements that are fluid in the main axis left. // We lay out the elements that are fluid in the main axis.
// We use the remaining space to evenly allocate space based on fill factors. // We use the remaining space to evenly allocate space based on fill factors.
for (i, (child, tree)) in items.iter().zip(trees).enumerate() { for (i, (child, tree)) in items.iter().zip(trees.iter_mut()).enumerate() {
let (fill_main_factor, fill_cross_factor) = { let (fill_main_factor, fill_cross_factor) = {
let size = child.as_widget().size(); let size = child.as_widget().size();
@ -224,10 +232,43 @@ where
} }
} }
// FOURTH PASS (conditional)
// We lay out any elements that were deferred in the second pass.
// These are elements that must be compressed in their cross axis and have
// a fixed length in the main axis.
if cross_compress && some_fill_cross {
for (i, (child, tree)) in items.iter().zip(trees).enumerate() {
let (main_size, cross_size) = {
let size = child.as_widget().size();
axis.pack(size.width, size.height)
};
if cross_size.fill_factor() != 0 {
let Length::Fixed(main) = main_size else {
continue;
};
let (max_width, max_height) = axis.pack(main, cross);
let child_limits =
Limits::new(Size::ZERO, Size::new(max_width, max_height));
let layout =
child.as_widget().layout(tree, renderer, &child_limits);
let size = layout.size();
cross = cross.max(axis.cross(size));
nodes[i] = layout;
}
}
}
let pad = axis.pack(padding.left, padding.top); let pad = axis.pack(padding.left, padding.top);
let mut main = pad.0; let mut main = pad.0;
// FOURTH PASS // FIFTH PASS
// We align all the laid out nodes in the cross axis, if needed. // We align all the laid out nodes in the cross axis, if needed.
for (i, node) in nodes.iter_mut().enumerate() { for (i, node) in nodes.iter_mut().enumerate() {
if i > 0 { if i > 0 {

View file

@ -107,3 +107,13 @@ where
write!(f, "Point {{ x: {}, y: {} }}", self.x, self.y) write!(f, "Point {{ x: {}, y: {} }}", self.x, self.y)
} }
} }
impl Point<f32> {
/// Snaps the [`Point`] to __unsigned__ integer coordinates.
pub fn snap(self) -> Point<u32> {
Point {
x: self.x.round() as u32,
y: self.y.round() as u32,
}
}
}

View file

@ -244,16 +244,19 @@ impl Rectangle<f32> {
/// Snaps the [`Rectangle`] to __unsigned__ integer coordinates. /// Snaps the [`Rectangle`] to __unsigned__ integer coordinates.
pub fn snap(self) -> Option<Rectangle<u32>> { pub fn snap(self) -> Option<Rectangle<u32>> {
let width = self.width as u32; let top_left = self.position().snap();
let height = self.height as u32; let bottom_right = (self.position() + Vector::from(self.size())).snap();
let width = bottom_right.x.checked_sub(top_left.x)?;
let height = bottom_right.y.checked_sub(top_left.y)?;
if width < 1 || height < 1 { if width < 1 || height < 1 {
return None; return None;
} }
Some(Rectangle { Some(Rectangle {
x: self.x as u32, x: top_left.x,
y: self.y as u32, y: top_left.y,
width, width,
height, height,
}) })

View file

@ -32,6 +32,7 @@ impl<'a, Message> Shell<'a, Message> {
} }
/// Returns true if the [`Shell`] contains no published messages /// Returns true if the [`Shell`] contains no published messages
#[must_use]
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.messages.is_empty() self.messages.is_empty()
} }
@ -50,11 +51,13 @@ impl<'a, Message> Shell<'a, Message> {
} }
/// Returns the current [`event::Status`] of the [`Shell`]. /// Returns the current [`event::Status`] of the [`Shell`].
#[must_use]
pub fn event_status(&self) -> event::Status { pub fn event_status(&self) -> event::Status {
self.event_status self.event_status
} }
/// Returns whether the current event has been captured. /// Returns whether the current event has been captured.
#[must_use]
pub fn is_event_captured(&self) -> bool { pub fn is_event_captured(&self) -> bool {
self.event_status == event::Status::Captured self.event_status == event::Status::Captured
} }
@ -73,6 +76,7 @@ impl<'a, Message> Shell<'a, Message> {
} }
/// Returns the request a redraw should happen, if any. /// Returns the request a redraw should happen, if any.
#[must_use]
pub fn redraw_request(&self) -> window::RedrawRequest { pub fn redraw_request(&self) -> window::RedrawRequest {
self.redraw_request self.redraw_request
} }
@ -101,16 +105,19 @@ impl<'a, Message> Shell<'a, Message> {
} }
/// Returns the current [`InputMethod`] strategy. /// Returns the current [`InputMethod`] strategy.
#[must_use]
pub fn input_method(&self) -> &InputMethod { pub fn input_method(&self) -> &InputMethod {
&self.input_method &self.input_method
} }
/// Returns the current [`InputMethod`] strategy. /// Returns the current [`InputMethod`] strategy.
#[must_use]
pub fn input_method_mut(&mut self) -> &mut InputMethod { pub fn input_method_mut(&mut self) -> &mut InputMethod {
&mut self.input_method &mut self.input_method
} }
/// Returns whether the current layout is invalid or not. /// Returns whether the current layout is invalid or not.
#[must_use]
pub fn is_layout_invalid(&self) -> bool { pub fn is_layout_invalid(&self) -> bool {
self.is_layout_invalid self.is_layout_invalid
} }
@ -134,6 +141,7 @@ impl<'a, Message> Shell<'a, Message> {
/// Returns whether the widgets of the current application have been /// Returns whether the widgets of the current application have been
/// invalidated. /// invalidated.
#[must_use]
pub fn are_widgets_invalid(&self) -> bool { pub fn are_widgets_invalid(&self) -> bool {
self.are_widgets_invalid self.are_widgets_invalid
} }

View file

@ -1,7 +1,7 @@
use crate::{Point, Size}; use crate::{Point, Size};
/// The position of a window in a given screen. /// The position of a window in a given screen.
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy)]
pub enum Position { pub enum Position {
/// The platform-specific default position for a new window. /// The platform-specific default position for a new window.
Default, Default,

View file

@ -12,6 +12,7 @@ keywords.workspace = true
[features] [features]
enable = ["dep:iced_beacon"] enable = ["dep:iced_beacon"]
hot = ["enable", "dep:cargo-hot"]
[dependencies] [dependencies]
iced_core.workspace = true iced_core.workspace = true
@ -21,3 +22,6 @@ log.workspace = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
iced_beacon.workspace = true iced_beacon.workspace = true
iced_beacon.optional = true iced_beacon.optional = true
cargo-hot.workspace = true
cargo-hot.optional = true

View file

@ -39,6 +39,7 @@ pub fn disable() {
pub fn init(metadata: Metadata) { pub fn init(metadata: Metadata) {
internal::init(metadata); internal::init(metadata);
hot::init();
} }
pub fn quit() -> bool { pub fn quit() -> bool {
@ -113,6 +114,18 @@ pub fn commands() -> Subscription<Command> {
internal::commands() internal::commands()
} }
pub fn hot<O>(f: impl FnOnce() -> O) -> O {
hot::call(f)
}
pub fn on_hotpatch(f: impl Fn() + Send + Sync + 'static) {
hot::on_hotpatch(f)
}
pub fn is_stale() -> bool {
hot::is_stale()
}
#[cfg(all(feature = "enable", not(target_arch = "wasm32")))] #[cfg(all(feature = "enable", not(target_arch = "wasm32")))]
mod internal { mod internal {
use crate::core::theme; use crate::core::theme;
@ -399,3 +412,83 @@ mod internal {
pub fn finish(self) {} pub fn finish(self) {}
} }
} }
#[cfg(feature = "hot")]
mod hot {
use std::collections::BTreeSet;
use std::sync::atomic::{self, AtomicBool};
use std::sync::{Arc, Mutex, OnceLock};
static IS_STALE: AtomicBool = AtomicBool::new(false);
static HOT_FUNCTIONS_PENDING: Mutex<BTreeSet<u64>> =
Mutex::new(BTreeSet::new());
static HOT_FUNCTIONS: OnceLock<BTreeSet<u64>> = OnceLock::new();
pub fn init() {
cargo_hot::connect();
cargo_hot::subsecond::register_handler(Arc::new(|| {
if HOT_FUNCTIONS.get().is_none() {
HOT_FUNCTIONS
.set(std::mem::take(
&mut HOT_FUNCTIONS_PENDING
.lock()
.expect("Lock hot functions"),
))
.expect("Set hot functions");
}
IS_STALE.store(false, atomic::Ordering::Relaxed);
}));
}
pub fn call<O>(f: impl FnOnce() -> O) -> O {
let mut f = Some(f);
// The `move` here is important. Hotpatching will not work
// otherwise.
let mut f = cargo_hot::subsecond::HotFn::current(move || {
f.take().expect("Hot function is stale")()
});
let address = f.ptr_address().0;
if let Some(hot_functions) = HOT_FUNCTIONS.get() {
if hot_functions.contains(&address) {
IS_STALE.store(true, atomic::Ordering::Relaxed);
}
} else {
let _ = HOT_FUNCTIONS_PENDING
.lock()
.expect("Lock hot functions")
.insert(address);
}
f.call(())
}
pub fn on_hotpatch(f: impl Fn() + Send + Sync + 'static) {
cargo_hot::subsecond::register_handler(Arc::new(f));
}
pub fn is_stale() -> bool {
IS_STALE.load(atomic::Ordering::Relaxed)
}
}
#[cfg(not(feature = "hot"))]
mod hot {
pub fn init() {}
pub fn call<O>(f: impl FnOnce() -> O) -> O {
f()
}
pub fn on_hotpatch(_f: impl Fn() + Send + Sync + 'static) {}
pub fn is_stale() -> bool {
false
}
}

View file

@ -397,13 +397,16 @@ where
} }
.map(|mode| Element::from(mode).map(Event::Message)); .map(|mode| Element::from(mode).map(Event::Message));
let notification = self.show_notification.then(|| { let notification = self
bottom_right(opaque( .show_notification
container(text("Press F12 to open developer tools")) .then(|| text("Press F12 to open debug metrics"))
.padding(10) .or_else(|| {
.style(container::dark), debug::is_stale().then(|| {
)) text(
}); "Types have changed. Restart to re-enable hotpatching.",
)
})
});
let sidebar = if let Mode::Open { tester } = &self.mode { let sidebar = if let Mode::Open { tester } = &self.mode {
let title = monospace("Developer Tools"); let title = monospace("Developer Tools");
@ -429,7 +432,13 @@ where
stack![content] stack![content]
.height(Fill) .height(Fill)
.push_maybe(setup.map(opaque)) .push_maybe(setup.map(opaque))
.push_maybe(notification), .push_maybe(notification.map(|notification| {
bottom_right(opaque(
container(notification)
.padding(10)
.style(container::dark),
))
})),
) )
.into() .into()
} }

View file

@ -36,7 +36,7 @@ impl Arc {
self.cache.clear(); self.cache.clear();
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
Canvas::new(self).width(Fill).height(Fill).into() Canvas::new(self).width(Fill).height(Fill).into()
} }

View file

@ -34,7 +34,7 @@ impl Example {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
container(hover( container(hover(
self.bezier.view(&self.curves).map(Message::AddCurve), self.bezier.view(&self.curves).map(Message::AddCurve),
if self.curves.is_empty() { if self.curves.is_empty() {

View file

@ -217,7 +217,7 @@ impl Generator {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
match self { match self {
Self::Loading => center("Loading...").into(), Self::Loading => center("Loading...").into(),
Self::Done => center( Self::Done => center(

View file

@ -38,7 +38,7 @@ impl Example {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let default_checkbox = checkbox("Default", self.default) let default_checkbox = checkbox("Default", self.default)
.on_toggle(Message::DefaultToggled); .on_toggle(Message::DefaultToggled);

View file

@ -48,7 +48,7 @@ impl Clock {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let canvas = canvas(self as &Self).width(Fill).height(Fill); let canvas = canvas(self as &Self).width(Fill).height(Fill);
container(canvas).padding(20).into() container(canvas).padding(20).into()
@ -166,7 +166,7 @@ impl<Message> canvas::Program<Message> for Clock {
let y = radius * angle.0.sin(); let y = radius * angle.0.sin();
frame.fill_text(canvas::Text { frame.fill_text(canvas::Text {
content: format!("{}", hour), content: format!("{hour}"),
size: (radius / 5.0).into(), size: (radius / 5.0).into(),
position: Point::new(x * 0.82, y * 0.82), position: Point::new(x * 0.82, y * 0.82),
color: palette.secondary.strong.text, color: palette.secondary.strong.text,

View file

@ -56,7 +56,7 @@ impl ColorPalette {
self.theme = Theme::new(to_color(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 = to_rgb(base); let srgb = to_rgb(base);
@ -149,7 +149,7 @@ impl Theme {
.chain(self.higher.iter()) .chain(self.higher.iter())
} }
pub fn view(&self) -> Element<Message> { pub fn view(&self) -> Element<'_, Message> {
Canvas::new(self).width(Fill).height(Fill).into() Canvas::new(self).width(Fill).height(Fill).into()
} }
@ -296,7 +296,7 @@ trait ColorSpace: Sized {
} }
impl<C: ColorSpace + Copy> ColorPicker<C> { impl<C: ColorSpace + Copy> ColorPicker<C> {
fn view(&self, color: C) -> Element<C> { fn view(&self, color: C) -> Element<'_, C> {
let [c1, c2, c3] = color.components(); let [c1, c2, c3] = color.components();
let [cr1, cr2, cr3] = C::COMPONENT_RANGES; let [cr1, cr2, cr3] = C::COMPONENT_RANGES;

View file

@ -47,7 +47,7 @@ impl Example {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let combo_box = combo_box( let combo_box = combo_box(
&self.languages, &self.languages,
"Type a language...", "Type a language...",

View file

@ -28,7 +28,7 @@ impl Counter {
} }
} }
fn view(&self) -> Column<Message> { fn view(&self) -> Column<'_, Message> {
column![ column![
button("Increment").on_press(Message::Increment), button("Increment").on_press(Message::Increment),
text(self.value).size(50), text(self.value).size(50),

View file

@ -74,7 +74,7 @@ impl Example {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let border::Radius { let border::Radius {
top_left, top_left,
top_right, top_right,

View file

@ -103,7 +103,7 @@ impl Example {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let content = column![ let content = column![
circle(self.radius), circle(self.radius),
text!("Radius: {:.2}", self.radius), text!("Radius: {:.2}", self.radius),

View file

@ -61,7 +61,7 @@ impl Example {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let downloads = let downloads =
Column::with_children(self.downloads.iter().map(Download::view)) Column::with_children(self.downloads.iter().map(Download::view))
.push( .push(
@ -152,7 +152,7 @@ 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,

View file

@ -144,7 +144,7 @@ impl Editor {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let controls = row![ let controls = row![
action(new_icon(), "New file", Some(Message::NewFile)), action(new_icon(), "New file", Some(Message::NewFile)),
action( action(

View file

@ -55,7 +55,7 @@ impl Events {
event::listen().map(Message::EventOccurred) event::listen().map(Message::EventOccurred)
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let events = Column::with_children( let events = Column::with_children(
self.last self.last
.iter() .iter()

View file

@ -29,7 +29,7 @@ impl Exit {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let content = if self.show_confirm { let content = if self.show_confirm {
column![ column![
"Are you sure you want to exit?", "Are you sure you want to exit?",

View file

@ -91,7 +91,7 @@ impl Image {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let i_am_ferris = column![ let i_am_ferris = column![
"Hello!", "Hello!",
Element::from( Element::from(

View file

@ -111,7 +111,7 @@ impl GameOfLife {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let version = self.version; let version = self.version;
let selected_speed = self.next_speed.unwrap_or(self.speed); let selected_speed = self.next_speed.unwrap_or(self.speed);
let controls = view_controls( let controls = view_controls(
@ -320,7 +320,7 @@ mod grid {
} }
} }
pub fn view(&self) -> Element<Message> { pub fn view(&self) -> Element<'_, Message> {
Canvas::new(self).width(Fill).height(Fill).into() Canvas::new(self).width(Fill).height(Fill).into()
} }

View file

@ -51,7 +51,7 @@ impl Gradient {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let Self { let Self {
start, start,
end, end,

View file

@ -38,7 +38,7 @@ impl Controls {
} }
} }
pub fn view(&self) -> Element<Message, Theme, Renderer> { pub fn view(&self) -> Element<'_, Message, Theme, Renderer> {
let background_color = self.background_color; let background_color = self.background_color;
let sliders = row![ let sliders = row![

View file

@ -68,7 +68,7 @@ impl Layout {
}) })
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let header = row![ let header = row![
text(self.example.title).size(20).font(Font::MONOSPACE), text(self.example.title).size(20).font(Font::MONOSPACE),
horizontal_space(), horizontal_space(),
@ -121,7 +121,7 @@ impl Layout {
} }
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, Eq)]
struct Example { struct Example {
title: &'static str, title: &'static str,
view: fn() -> Element<'static, Message>, view: fn() -> Element<'static, Message>,
@ -190,7 +190,7 @@ impl Example {
Self::LIST.get(index + 1).copied().unwrap_or(self) Self::LIST.get(index + 1).copied().unwrap_or(self)
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
(self.view)() (self.view)()
} }
} }
@ -201,6 +201,12 @@ impl Default for Example {
} }
} }
impl PartialEq for Example {
fn eq(&self, other: &Self) -> bool {
self.title == other.title
}
}
fn centered<'a>() -> Element<'a, Message> { fn centered<'a>() -> Element<'a, Message> {
center(text("I am centered!").size(50)).into() center(text("I am centered!").size(50)).into()
} }

View file

@ -154,7 +154,7 @@ impl App {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let options = lazy(self.version, |_| { let options = lazy(self.version, |_| {
let mut items: Vec<_> = self.items.iter().cloned().collect(); let mut items: Vec<_> = self.items.iter().cloned().collect();

View file

@ -37,7 +37,7 @@ impl LoadingSpinners {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let column = [ let column = [
&easing::EMPHASIZED, &easing::EMPHASIZED,
&easing::EMPHASIZED_DECELERATE, &easing::EMPHASIZED_DECELERATE,

View file

@ -30,7 +30,7 @@ impl Loupe {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
center(loupe( center(loupe(
3.0, 3.0,
column![ column![

View file

@ -34,7 +34,7 @@ Now, let's show the actual counter by putting it all together in our __view logi
use iced::widget::{button, column, text, Column}; use iced::widget::{button, column, text, Column};
impl Counter { impl Counter {
pub fn view(&self) -> Column<Message> { pub fn view(&self) -> Column<'_, Message> {
// We use a column: a simple vertical layout // We use a column: a simple vertical layout
column![ column![
// The increment button. We tell it to produce an // The increment button. We tell it to produce an

View file

@ -174,7 +174,7 @@ impl Markdown {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let editor = text_editor(&self.raw) let editor = text_editor(&self.raw)
.placeholder("Type your Markdown here...") .placeholder("Type your Markdown here...")
.on_action(Message::Edit) .on_action(Message::Edit)

View file

@ -92,7 +92,7 @@ impl App {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let content = container( let content = container(
column![ column![
row![text("Top Left"), horizontal_space(), text("Top Right")] row![text("Top Left"), horizontal_space(), text("Top Right")]

View file

@ -130,7 +130,7 @@ impl Example {
} }
} }
fn view(&self, window_id: window::Id) -> Element<Message> { fn view(&self, window_id: window::Id) -> Element<'_, Message> {
if let Some(window) = self.windows.get(&window_id) { if let Some(window) = self.windows.get(&window_id) {
center(window.view(window_id)).into() center(window.view(window_id)).into()
} else { } else {
@ -161,14 +161,14 @@ impl Example {
impl Window { impl Window {
fn new(count: usize) -> Self { fn new(count: usize) -> Self {
Self { Self {
title: format!("Window_{}", count), title: format!("Window_{count}"),
scale_input: "1.0".to_string(), scale_input: "1.0".to_string(),
current_scale: 1.0, current_scale: 1.0,
theme: Theme::ALL[count % Theme::ALL.len()].clone(), theme: Theme::ALL[count % Theme::ALL.len()].clone(),
} }
} }
fn view(&self, id: window::Id) -> Element<Message> { fn view(&self, id: window::Id) -> Element<'_, Message> {
let scale_input = column![ let scale_input = column![
text("Window scale factor:"), text("Window scale factor:"),
text_input("Window Scale", &self.scale_input) text_input("Window Scale", &self.scale_input)

View file

@ -43,7 +43,7 @@ impl Multitouch {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
Canvas::new(self).width(Fill).height(Fill).into() Canvas::new(self).width(Fill).height(Fill).into()
} }
} }

View file

@ -129,7 +129,7 @@ impl Example {
}) })
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let focus = self.focus; let focus = self.focus;
let total_panes = self.panes.len(); let total_panes = self.panes.len();

View file

@ -24,7 +24,7 @@ impl Example {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let pick_list = pick_list( let pick_list = pick_list(
&Language::ALL[..], &Language::ALL[..],
self.selected_language, self.selected_language,

View file

@ -63,7 +63,7 @@ impl Pokedex {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let content: Element<_> = match self { let content: Element<_> = match self {
Pokedex::Loading => { Pokedex::Loading => {
text("Searching for Pokémon...").size(40).into() text("Searching for Pokémon...").size(40).into()
@ -100,7 +100,7 @@ struct Pokemon {
impl Pokemon { impl Pokemon {
const TOTAL: u16 = 807; const TOTAL: u16 = 807;
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
row![ row![
image::viewer(self.image.clone()), image::viewer(self.image.clone()),
column![ column![

View file

@ -30,7 +30,7 @@ impl Progress {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let bar = progress_bar(0.0..=100.0, self.value); let bar = progress_bar(0.0..=100.0, self.value);
column![ column![

View file

@ -63,7 +63,7 @@ impl QRGenerator {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let title = text("QR Code Generator").size(70); let title = text("QR Code Generator").size(70);
let input = let input =

View file

@ -174,7 +174,7 @@ impl Example {
|png_result| match png_result { |png_result| match png_result {
Ok(path) => format!("Png saved as: {path:?}!"), Ok(path) => format!("Png saved as: {path:?}!"),
Err(PngError(error)) => { Err(PngError(error)) => {
format!("Png could not be saved due to:\n{}", error) format!("Png could not be saved due to:\n{error}")
} }
}, },
); );

View file

@ -118,7 +118,7 @@ impl ScrollableDemo {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let scrollbar_width_slider = slider( let scrollbar_width_slider = slider(
0..=15, 0..=15,
self.scrollbar_width, self.scrollbar_width,

View file

@ -1,7 +1,9 @@
use iced::mouse; use iced::mouse;
use iced::widget::canvas::{self, Canvas, Event, Geometry}; use iced::widget::canvas::{self, Canvas, Event, Geometry};
use iced::widget::{column, row, slider, text}; use iced::widget::{column, row, slider, text};
use iced::{Center, Color, Fill, Point, Rectangle, Renderer, Size, Theme}; use iced::{
Center, Color, Element, Fill, Point, Rectangle, Renderer, Size, Theme,
};
use rand::Rng; use rand::Rng;
use std::fmt::Debug; use std::fmt::Debug;
@ -46,7 +48,7 @@ impl SierpinskiEmulator {
self.graph.redraw(); self.graph.redraw();
} }
fn view(&self) -> iced::Element<'_, Message> { fn view(&self) -> Element<'_, Message> {
column![ column![
Canvas::new(&self.graph).width(Fill).height(Fill), Canvas::new(&self.graph).width(Fill).height(Fill),
row![ row![

View file

@ -27,7 +27,7 @@ impl Slider {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let h_slider = container( let h_slider = container(
slider(1..=100, self.value, Message::SliderChanged) slider(1..=100, self.value, Message::SliderChanged)
.default(50) .default(50)

View file

@ -55,7 +55,7 @@ impl SolarSystem {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
canvas(&self.state).width(Fill).height(Fill).into() canvas(&self.state).width(Fill).height(Fill).into()
} }

View file

@ -83,7 +83,7 @@ impl Stopwatch {
Subscription::batch(vec![tick, keyboard::on_key_press(handle_hotkey)]) Subscription::batch(vec![tick, keyboard::on_key_press(handle_hotkey)])
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
const MINUTE: u64 = 60; const MINUTE: u64 = 60;
const HOUR: u64 = 60 * MINUTE; const HOUR: u64 = 60 * MINUTE;

View file

@ -1 +1 @@
12ba47a34ed415825a23f8ef377a2d52950d2f8614a66bf46c0ec28d0cf15c85 30570747bb062e9f7730cdd58be961c84bcf4711a6983185bff6d903e8d29e9c

View file

@ -1 +1 @@
78d3afadbdca4a992c662541d602850e0dec0abafa585e2a3078daa1be5998b8 d5a086a08544f98087189bd4ece8815e5290722a07cd580b933f1bf77a040c52

View file

@ -1 +1 @@
4e594cfec775d51f7f836646c59bf4a2de07252721d66ddddea69c17e9112bae 30e523961db89a3ee97ad1eac09e727ecb3dec485faa362534a9f5ad083b32dd

View file

@ -1 +1 @@
2ab665b51387c61086ae0199c29e291105bfe4583bd4c4daa652e30917f10bd6 bce5427d5105f68e1d7fa18a34fcc551cb78c2fefd9a583ba44686331133436d

View file

@ -1 +1 @@
61c9ee377b33ffa800f512877e45ad5f41fbac36f5d3f06d1b62d6af6ee9d7b2 c8a7edbd5a8bbf559134b84253e14e65340f4ffe3e22c272b21c8438e47ffaf7

View file

@ -1 +1 @@
75f2fb12c9090a256708515de01a25e78905f71e134b7cda79f4fe44b2434052 63d646b22d3dffbb56dac2e3f345090bd26625a388dd6cc142359f2a7ac9c8df

View file

@ -1 +1 @@
b4a1b42d2e21b2a493605745e6beb8e1f28cbeb01b73336e1e8d9061249a8311 d26f55674cbd96bc3b534ffdd098a13199718ef9c5ffe8ece0882ddab714b776

View file

@ -1 +1 @@
eb52921b3ee23e1814268701c935d0dff387e7eb741c50443f75a7ab902b5e44 482c44c13d4ff3de19e71f3dddf93bbee170e54e2d353e818811069de28e18ed

View file

@ -1 +1 @@
bf6c4cbd6eeed0167d28509e37292f5ce26ed1d58bb156bedb861d0619a1945b 6738cc4fc6eb8a5d406c613a4b0f08c0e8dcd2c1a5444445eebd3888f9303841

View file

@ -1 +1 @@
83726a4175900a9ef159b80925f2fa985b4ea87bff78d8bb01918fb6c40d6175 0a918c52538fc4848aa0c68d8f2d6f4c981ed68971dd9c725f0093a39ef7f353

View file

@ -1 +1 @@
54c8d44afbdd644f324cf40e744b3d7871263e44de2d9a91f6c10470c38ac3c6 de3e1a2c21e1a86d76ca99989c73e8a2596ef627bba95d246fab8f02d56bd0af

View file

@ -1 +1 @@
5713843396e2efcfc7cc8abd00343d5d66ce8b8a195212a9b75dbfeec8edf7a7 3418ea4eb0f7786607ef02e7db4bc97309530f2f7c08f8aea15c768a13a09ca4

View file

@ -1 +1 @@
f1b20ab79f8242776d9eb1ad9cff7090435aa416811c48a7c22c69b09cd8e70f c8474e02a9df23f123816a489c1ea7ae6cb994a0eca429592dfe6d933de1beee

View file

@ -1 +1 @@
2c4be9dc1340b65cad6d15d5318017412eba1247a016379b83db379dde0214af 02095fd09c078be02dc41e29e55de25e8a79e6ad4293aa7e430257a9016dfb3d

View file

@ -1 +1 @@
2d5d6b9613ccb6a2f23142baf704288037808e7a60ee05bdc73490d8c8780064 d82588a2aba3e7211f25b85ebb812a42dfa59137dd4b59d26f5f60d5b28e537f

View file

@ -1 +1 @@
512b8dfd4687a609d202436e75ecf1a5553acc2157000e77e31c1578941b033c d6b73545929cc7794c1a918f069b5326ef129bed8f9ad2cd001be7d078a2b6a0

View file

@ -1 +1 @@
4b3b7a2dc65307a3551227f1c5d2bb49da15d29e320ccaa160e3d9fca44c1037 0ec7251c69755becd678b7aec398a275edf31cc077960723cd6b9364e8678548

View file

@ -1 +1 @@
be1f056f0decaee8c138cd038e3e34352f02448b77c1e251796a8f0be8086913 4a15c475d45cf8eb0ccd6727cf6e493bd8c22454610b167a632a2328308faed1

View file

@ -1 +1 @@
31a7a46ed7951d69e1a29ab595d241e689cacdf66cea53dda6609db4927070e5 49a41af93e89aab0a4e352e9cedfba3c6e18caf4267955c9d362bad40264a165

View file

@ -1 +1 @@
5eab001ed1aeeea3f24fe18e4aab9b8522cb35ac1328d7ca3532dbdfdf95780f 8fcd80d4569dafdac4b4452b8ca8ab0cdceeb755f3c83d374ccd5ed4d0e8d43d

View file

@ -1 +1 @@
2fed695daa4c3da56f744832a041060704310a97d65820c97d556d297dfb271a c37a32784c769c046f3aa881914b121af373b8c6e175ced89304d15b626a653a

View file

@ -1 +1 @@
0ecd51994f6eb37f111dc1b21cad72bb705499eb83156e9dc3ae2221ec392a42 533d25575e8bf1111036fb082b424d0d0e60947a7da8428ab8c71e0bda01469e

View file

@ -65,7 +65,7 @@ impl Styling {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let choose_theme = column![ let choose_theme = column![
text("Theme:"), text("Theme:"),
pick_list(Theme::ALL, Some(&self.theme), Message::ThemeChanged) pick_list(Theme::ALL, Some(&self.theme), Message::ThemeChanged)
@ -126,7 +126,7 @@ impl Styling {
let content = column![ let content = column![
choose_theme, choose_theme,
horizontal_rule(38), horizontal_rule(1),
text_input, text_input,
row![primary, success, warning, danger] row![primary, success, warning, danger]
.spacing(10) .spacing(10)
@ -135,8 +135,8 @@ impl Styling {
progress_bar(), progress_bar(),
row![ row![
scrollable, scrollable,
vertical_rule(38), row![vertical_rule(1), column![checkbox, toggler].spacing(20)]
column![checkbox, toggler].spacing(20) .spacing(20)
] ]
.spacing(10) .spacing(10)
.height(100) .height(100)

View file

@ -24,7 +24,7 @@ impl Tiger {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let handle = svg::Handle::from_path(format!( let handle = svg::Handle::from_path(format!(
"{}/resources/tiger.svg", "{}/resources/tiger.svg",
env!("CARGO_MANIFEST_DIR") env!("CARGO_MANIFEST_DIR")

View file

@ -47,7 +47,7 @@ impl Example {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
use bytesize::ByteSize; use bytesize::ByteSize;
let content: Element<_> = match self { let content: Element<_> = match self {

View file

@ -34,7 +34,7 @@ impl TheMatrix {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
canvas(self).width(Fill).height(Fill).into() canvas(self).width(Fill).height(Fill).into()
} }

View file

@ -613,12 +613,11 @@ mod toast {
&self.viewport, &self.viewport,
renderer, renderer,
) )
.max( .max(if cursor.is_over(layout.bounds()) {
cursor mouse::Interaction::Idle
.is_over(layout.bounds()) } else {
.then_some(mouse::Interaction::Idle) Default::default()
.unwrap_or_default(), })
)
}) })
.max() .max()
.unwrap_or_default() .unwrap_or_default()

View file

@ -186,7 +186,7 @@ impl Todos {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
match self { match self {
Todos::Loading => loading_message(), Todos::Loading => loading_message(),
Todos::Loaded(State { Todos::Loaded(State {
@ -338,7 +338,7 @@ impl Task {
} }
} }
fn view(&self, i: usize) -> Element<TaskMessage> { fn view(&self, i: usize) -> Element<'_, TaskMessage> {
match &self.state { match &self.state {
TaskState::Idle => { TaskState::Idle => {
let checkbox = checkbox(&self.description, self.completed) let checkbox = checkbox(&self.description, self.completed)
@ -385,7 +385,10 @@ impl Task {
} }
} }
fn view_controls(tasks: &[Task], current_filter: Filter) -> Element<Message> { fn view_controls(
tasks: &[Task],
current_filter: Filter,
) -> Element<'_, Message> {
let tasks_left = tasks.iter().filter(|task| !task.completed).count(); let tasks_left = tasks.iter().filter(|task| !task.completed).count();
let filter_button = |label, filter, current_filter| { let filter_button = |label, filter, current_filter| {
@ -617,7 +620,7 @@ mod tests {
use iced_test::selector::id; use iced_test::selector::id;
use iced_test::{Error, Simulator}; use iced_test::{Error, Simulator};
fn simulator(todos: &Todos) -> Simulator<Message> { fn simulator(todos: &Todos) -> Simulator<'_, Message> {
Simulator::with_settings( Simulator::with_settings(
Settings { Settings {
fonts: vec![Todos::ICON_FONT.into()], fonts: vec![Todos::ICON_FONT.into()],

View file

@ -33,7 +33,7 @@ impl Tooltip {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let tooltip = tooltip( let tooltip = tooltip(
button("Press to change position") button("Press to change position")
.on_press(Message::ChangePosition), .on_press(Message::ChangePosition),

View file

@ -76,7 +76,7 @@ impl Tour {
Screen::End => "End", Screen::End => "End",
}; };
format!("{} - Iced", screen) format!("{screen} - Iced")
} }
fn update(&mut self, event: Message) { fn update(&mut self, event: Message) {
@ -141,7 +141,7 @@ impl Tour {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let controls = let controls =
row![] row![]
.push_maybe(self.screen.previous().is_some().then(|| { .push_maybe(self.screen.previous().is_some().then(|| {
@ -197,7 +197,7 @@ impl Tour {
} }
} }
fn welcome(&self) -> Column<Message> { fn welcome(&self) -> Column<'_, Message> {
Self::container("Welcome!") Self::container("Welcome!")
.push( .push(
"This is a simple tour meant to showcase a bunch of \ "This is a simple tour meant to showcase a bunch of \
@ -244,7 +244,7 @@ impl Tour {
) )
} }
fn slider(&self) -> Column<Message> { fn slider(&self) -> Column<'_, Message> {
Self::container("Slider") Self::container("Slider")
.push( .push(
"A slider allows you to smoothly select a value from a range \ "A slider allows you to smoothly select a value from a range \
@ -258,7 +258,7 @@ impl Tour {
.push(text(self.slider.to_string()).width(Fill).align_x(Center)) .push(text(self.slider.to_string()).width(Fill).align_x(Center))
} }
fn rows_and_columns(&self) -> Column<Message> { fn rows_and_columns(&self) -> Column<'_, Message> {
let row_radio = radio( let row_radio = radio(
"Row", "Row",
Layout::Row, Layout::Row,
@ -303,7 +303,7 @@ impl Tour {
.push(spacing_section) .push(spacing_section)
} }
fn text(&self) -> Column<Message> { fn text(&self) -> Column<'_, Message> {
let size = self.text_size; let size = self.text_size;
let color = self.text_color; let color = self.text_color;
@ -339,7 +339,7 @@ impl Tour {
.push(color_section) .push(color_section)
} }
fn radio(&self) -> Column<Message> { fn radio(&self) -> Column<'_, Message> {
let question = column![ let question = column![
text("Iced is written in...").size(24), text("Iced is written in...").size(24),
column( column(
@ -374,7 +374,7 @@ impl Tour {
) )
} }
fn toggler(&self) -> Column<Message> { fn toggler(&self) -> Column<'_, Message> {
Self::container("Toggler") Self::container("Toggler")
.push("A toggler is mostly used to enable or disable something.") .push("A toggler is mostly used to enable or disable something.")
.push( .push(
@ -387,7 +387,7 @@ impl Tour {
) )
} }
fn image(&self) -> Column<Message> { fn image(&self) -> Column<'_, Message> {
let width = self.image_width; let width = self.image_width;
let filter_method = self.image_filter_method; let filter_method = self.image_filter_method;
@ -406,7 +406,7 @@ impl Tour {
.align_x(Center) .align_x(Center)
} }
fn scrollable(&self) -> Column<Message> { fn scrollable(&self) -> Column<'_, Message> {
Self::container("Scrollable") Self::container("Scrollable")
.push( .push(
"Iced supports scrollable content. Try it out! Find the \ "Iced supports scrollable content. Try it out! Find the \
@ -428,7 +428,7 @@ impl Tour {
.push(text("You made it!").width(Fill).size(50).align_x(Center)) .push(text("You made it!").width(Fill).size(50).align_x(Center))
} }
fn text_input(&self) -> Column<Message> { fn text_input(&self) -> Column<'_, Message> {
let value = &self.input_value; let value = &self.input_value;
let is_secure = self.input_is_secure; let is_secure = self.input_is_secure;
let is_showing_icon = self.input_is_showing_icon; let is_showing_icon = self.input_is_showing_icon;
@ -474,7 +474,7 @@ impl Tour {
) )
} }
fn debugger(&self) -> Column<Message> { fn debugger(&self) -> Column<'_, Message> {
Self::container("Debugger") Self::container("Debugger")
.push( .push(
"You can ask Iced to visually explain the layouting of the \ "You can ask Iced to visually explain the layouting of the \
@ -491,7 +491,7 @@ impl Tour {
.push("Feel free to go back and take a look.") .push("Feel free to go back and take a look.")
} }
fn end(&self) -> Column<Message> { fn end(&self) -> Column<'_, Message> {
Self::container("You reached the end!") Self::container("You reached the end!")
.push("This tour will be updated as more features are added.") .push("This tour will be updated as more features are added.")
.push("Make sure to keep an eye on it!") .push("Make sure to keep an eye on it!")

View file

@ -31,7 +31,7 @@ impl App {
event::listen_url().map(Message::UrlReceived) event::listen_url().map(Message::UrlReceived)
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let content = match &self.url { let content = match &self.url {
Some(url) => text(url), Some(url) => text(url),
None => text("No URL received yet!"), None => text("No URL received yet!"),

View file

@ -48,7 +48,7 @@ impl VectorialText {
self.state.cache.clear(); self.state.cache.clear();
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let slider_with_label = |label, range, value, message: fn(f32) -> _| { let slider_with_label = |label, range, value, message: fn(f32) -> _| {
column![ column![
row![text(label), horizontal_space(), text!("{:.2}", value)], row![text(label), horizontal_space(), text!("{:.2}", value)],

View file

@ -59,7 +59,7 @@ impl Example {
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let data_row = |label, value, color| { let data_row = |label, value, color| {
row![ row![
text(label), text(label),

View file

@ -93,7 +93,7 @@ impl WebSocket {
]) ])
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<'_, Message> {
let message_log: Element<_> = if self.messages.is_empty() { let message_log: Element<_> = if self.messages.is_empty() {
center( center(
text("Your messages will appear here...") text("Your messages will appear here...")

View file

@ -56,6 +56,9 @@ pub enum Action<T> {
/// Run a system action. /// Run a system action.
System(system::Action), System(system::Action),
/// Recreate all user interfaces and redraw all windows.
Reload,
/// Exits the runtime. /// Exits the runtime.
/// ///
/// This will normally close any application windows and /// This will normally close any application windows and
@ -79,6 +82,7 @@ impl<T> Action<T> {
Action::Clipboard(action) => Err(Action::Clipboard(action)), Action::Clipboard(action) => Err(Action::Clipboard(action)),
Action::Window(action) => Err(Action::Window(action)), Action::Window(action) => Err(Action::Window(action)),
Action::System(action) => Err(Action::System(action)), Action::System(action) => Err(Action::System(action)),
Action::Reload => Err(Action::Reload),
Action::Exit => Err(Action::Exit), Action::Exit => Err(Action::Exit),
} }
} }
@ -102,6 +106,7 @@ where
} }
Action::Window(_) => write!(f, "Action::Window"), Action::Window(_) => write!(f, "Action::Window"),
Action::System(action) => write!(f, "Action::System({action:?})"), Action::System(action) => write!(f, "Action::System({action:?})"),
Action::Reload => write!(f, "Action::Reload"),
Action::Exit => write!(f, "Action::Exit"), Action::Exit => write!(f, "Action::Exit"),
} }
} }

View file

@ -17,7 +17,6 @@ pub use sipper::{Never, Sender, Sipper, Straw, sipper, stream};
/// A set of concurrent actions to be performed by the iced runtime. /// A set of concurrent actions to be performed by the iced runtime.
/// ///
/// A [`Task`] _may_ produce a bunch of values of type `T`. /// A [`Task`] _may_ produce a bunch of values of type `T`.
#[allow(missing_debug_implementations)]
#[must_use = "`Task` must be returned to the runtime to take effect; normally in your `update` or `new` functions."] #[must_use = "`Task` must be returned to the runtime to take effect; normally in your `update` or `new` functions."]
pub struct Task<T> { pub struct Task<T> {
stream: Option<BoxStream<Action<T>>>, stream: Option<BoxStream<Action<T>>>,
@ -278,6 +277,14 @@ impl<T> Task<T> {
} }
} }
impl<T> std::fmt::Debug for Task<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct(&format!("Task<{}>", std::any::type_name::<T>()))
.field("units", &self.units)
.finish()
}
}
/// A handle to a [`Task`] that can be used for aborting it. /// A handle to a [`Task`] that can be used for aborting it.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Handle { pub struct Handle {

View file

@ -38,6 +38,8 @@ use crate::{
Element, Executor, Font, Result, Settings, Size, Subscription, Task, Element, Executor, Font, Result, Settings, Size, Subscription, Task,
}; };
use iced_debug as debug;
use std::borrow::Cow; use std::borrow::Cow;
pub mod timed; pub mod timed;
@ -128,7 +130,7 @@ where
state: &mut Self::State, state: &mut Self::State,
message: Self::Message, message: Self::Message,
) -> Task<Self::Message> { ) -> Task<Self::Message> {
self.update.update(state, message) debug::hot(|| self.update.update(state, message))
} }
fn view<'a>( fn view<'a>(
@ -136,7 +138,7 @@ where
state: &'a Self::State, state: &'a Self::State,
_window: window::Id, _window: window::Id,
) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
self.view.view(state) debug::hot(|| self.view.view(state))
} }
fn settings(&self) -> Settings { fn settings(&self) -> Settings {
@ -342,7 +344,7 @@ impl<P: Program> Application<P> {
> { > {
Application { Application {
raw: program::with_title(self.raw, move |state, _window| { raw: program::with_title(self.raw, move |state, _window| {
title.title(state) debug::hot(|| title.title(state))
}), }),
settings: self.settings, settings: self.settings,
window: self.window, window: self.window,
@ -359,7 +361,9 @@ impl<P: Program> Application<P> {
impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
> { > {
Application { Application {
raw: program::with_subscription(self.raw, f), raw: program::with_subscription(self.raw, move |state| {
debug::hot(|| f(state))
}),
settings: self.settings, settings: self.settings,
window: self.window, window: self.window,
#[cfg(feature = "test")] #[cfg(feature = "test")]
@ -375,7 +379,9 @@ impl<P: Program> Application<P> {
impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
> { > {
Application { Application {
raw: program::with_theme(self.raw, move |state, _window| f(state)), raw: program::with_theme(self.raw, move |state, _window| {
debug::hot(|| f(state))
}),
settings: self.settings, settings: self.settings,
window: self.window, window: self.window,
#[cfg(feature = "test")] #[cfg(feature = "test")]
@ -391,7 +397,9 @@ impl<P: Program> Application<P> {
impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
> { > {
Application { Application {
raw: program::with_style(self.raw, f), raw: program::with_style(self.raw, move |state, theme| {
debug::hot(|| f(state, theme))
}),
settings: self.settings, settings: self.settings,
window: self.window, window: self.window,
#[cfg(feature = "test")] #[cfg(feature = "test")]
@ -408,7 +416,7 @@ impl<P: Program> Application<P> {
> { > {
Application { Application {
raw: program::with_scale_factor(self.raw, move |state, _window| { raw: program::with_scale_factor(self.raw, move |state, _window| {
f(state) debug::hot(|| f(state))
}), }),
settings: self.settings, settings: self.settings,
window: self.window, window: self.window,

View file

@ -6,6 +6,8 @@ use crate::time::Instant;
use crate::window; use crate::window;
use crate::{Element, Program, Settings, Subscription, Task}; use crate::{Element, Program, Settings, Subscription, Task};
use iced_debug as debug;
/// Creates an [`Application`] with an `update` function that also /// Creates an [`Application`] with an `update` function that also
/// takes the [`Instant`] of each `Message`. /// takes the [`Instant`] of each `Message`.
/// ///
@ -101,10 +103,12 @@ where
state: &mut Self::State, state: &mut Self::State,
(message, now): Self::Message, (message, now): Self::Message,
) -> Task<Self::Message> { ) -> Task<Self::Message> {
self.update debug::hot(move || {
.update(state, message, now) self.update
.into() .update(state, message, now)
.map(|message| (message, Instant::now())) .into()
.map(|message| (message, Instant::now()))
})
} }
fn view<'a>( fn view<'a>(
@ -112,16 +116,21 @@ where
state: &'a Self::State, state: &'a Self::State,
_window: window::Id, _window: window::Id,
) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
self.view debug::hot(|| {
.view(state) self.view
.map(|message| (message, Instant::now())) .view(state)
.map(|message| (message, Instant::now()))
})
} }
fn subscription( fn subscription(
&self, &self,
state: &Self::State, state: &Self::State,
) -> self::Subscription<Self::Message> { ) -> self::Subscription<Self::Message> {
(self.subscription)(state).map(|message| (message, Instant::now())) debug::hot(|| {
(self.subscription)(state)
.map(|message| (message, Instant::now()))
})
} }
} }

View file

@ -6,6 +6,8 @@ use crate::theme;
use crate::window; use crate::window;
use crate::{Element, Executor, Font, Result, Settings, Subscription, Task}; use crate::{Element, Executor, Font, Result, Settings, Subscription, Task};
use iced_debug as debug;
use std::borrow::Cow; use std::borrow::Cow;
/// Creates an iced [`Daemon`] given its boot, update, and view logic. /// Creates an iced [`Daemon`] given its boot, update, and view logic.
@ -76,7 +78,7 @@ where
state: &mut Self::State, state: &mut Self::State,
message: Self::Message, message: Self::Message,
) -> Task<Self::Message> { ) -> Task<Self::Message> {
self.update.update(state, message) debug::hot(|| self.update.update(state, message))
} }
fn view<'a>( fn view<'a>(
@ -84,7 +86,7 @@ where
state: &'a Self::State, state: &'a Self::State,
window: window::Id, window: window::Id,
) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
self.view.view(state, window) debug::hot(|| self.view.view(state, window))
} }
} }
@ -182,7 +184,7 @@ impl<P: Program> Daemon<P> {
> { > {
Daemon { Daemon {
raw: program::with_title(self.raw, move |state, window| { raw: program::with_title(self.raw, move |state, window| {
title.title(state, window) debug::hot(|| title.title(state, window))
}), }),
settings: self.settings, settings: self.settings,
} }
@ -196,7 +198,9 @@ impl<P: Program> Daemon<P> {
impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
> { > {
Daemon { Daemon {
raw: program::with_subscription(self.raw, f), raw: program::with_subscription(self.raw, move |state| {
debug::hot(|| f(state))
}),
settings: self.settings, settings: self.settings,
} }
} }
@ -209,7 +213,9 @@ impl<P: Program> Daemon<P> {
impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
> { > {
Daemon { Daemon {
raw: program::with_theme(self.raw, f), raw: program::with_theme(self.raw, move |state, window| {
debug::hot(|| f(state, window))
}),
settings: self.settings, settings: self.settings,
} }
} }
@ -222,7 +228,9 @@ impl<P: Program> Daemon<P> {
impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
> { > {
Daemon { Daemon {
raw: program::with_style(self.raw, f), raw: program::with_style(self.raw, move |state, theme| {
debug::hot(|| f(state, theme))
}),
settings: self.settings, settings: self.settings,
} }
} }
@ -235,7 +243,9 @@ impl<P: Program> Daemon<P> {
impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
> { > {
Daemon { Daemon {
raw: program::with_scale_factor(self.raw, f), raw: program::with_scale_factor(self.raw, move |state, window| {
debug::hot(|| f(state, window))
}),
settings: self.settings, settings: self.settings,
} }
} }

View file

@ -34,7 +34,7 @@
//! iced::run(update, view) //! iced::run(update, view)
//! } //! }
//! # fn update(state: &mut (), message: ()) {} //! # fn update(state: &mut (), message: ()) {}
//! # fn view(state: &()) -> iced::Element<()> { iced::widget::text("").into() } //! # fn view(state: &()) -> iced::Element<'_, ()> { iced::widget::text("").into() }
//! ``` //! ```
//! //!
//! Define an `update` function to __change__ your state: //! Define an `update` function to __change__ your state:
@ -55,7 +55,7 @@
//! use iced::widget::{button, text}; //! use iced::widget::{button, text};
//! use iced::Element; //! use iced::Element;
//! //!
//! fn view(counter: &u64) -> Element<Message> { //! fn view(counter: &u64) -> Element<'_, Message> {
//! button(text(counter)).on_press(Message::Increment).into() //! button(text(counter)).on_press(Message::Increment).into()
//! } //! }
//! # #[derive(Clone)] //! # #[derive(Clone)]
@ -95,7 +95,7 @@
//! } //! }
//! } //! }
//! //!
//! fn view(counter: &Counter) -> Element<Message> { //! fn view(counter: &Counter) -> Element<'_, Message> {
//! button(text(counter.value)).on_press(Message::Increment).into() //! button(text(counter.value)).on_press(Message::Increment).into()
//! } //! }
//! ``` //! ```
@ -115,7 +115,7 @@
//! use iced::widget::{button, column, text}; //! use iced::widget::{button, column, text};
//! use iced::Element; //! use iced::Element;
//! //!
//! fn view(counter: &Counter) -> Element<Message> { //! fn view(counter: &Counter) -> Element<'_, Message> {
//! column![ //! column![
//! text(counter.value).size(20), //! text(counter.value).size(20),
//! button("Increment").on_press(Message::Increment), //! button("Increment").on_press(Message::Increment),
@ -144,7 +144,7 @@
//! use iced::widget::{column, container, row}; //! use iced::widget::{column, container, row};
//! use iced::{Fill, Element}; //! use iced::{Fill, Element};
//! //!
//! fn view(state: &State) -> Element<Message> { //! fn view(state: &State) -> Element<'_, Message> {
//! container( //! container(
//! column![ //! column![
//! "Top", //! "Top",
@ -187,7 +187,7 @@
//! use iced::widget::container; //! use iced::widget::container;
//! use iced::Element; //! use iced::Element;
//! //!
//! fn view(state: &State) -> Element<Message> { //! fn view(state: &State) -> Element<'_, Message> {
//! container("I am 300px tall!").height(300).into() //! container("I am 300px tall!").height(300).into()
//! } //! }
//! ``` //! ```
@ -216,7 +216,7 @@
//! Theme::TokyoNight //! Theme::TokyoNight
//! } //! }
//! # fn update(state: &mut State, message: ()) {} //! # fn update(state: &mut State, message: ()) {}
//! # fn view(state: &State) -> iced::Element<()> { iced::widget::text("").into() } //! # fn view(state: &State) -> iced::Element<'_, ()> { iced::widget::text("").into() }
//! ``` //! ```
//! //!
//! The `theme` function takes the current state of the application, allowing the //! The `theme` function takes the current state of the application, allowing the
@ -237,7 +237,7 @@
//! use iced::widget::container; //! use iced::widget::container;
//! use iced::Element; //! use iced::Element;
//! //!
//! fn view(state: &State) -> Element<Message> { //! fn view(state: &State) -> Element<'_, Message> {
//! container("I am a rounded box!").style(container::rounded_box).into() //! container("I am a rounded box!").style(container::rounded_box).into()
//! } //! }
//! ``` //! ```
@ -252,7 +252,7 @@
//! use iced::widget::button; //! use iced::widget::button;
//! use iced::{Element, Theme}; //! use iced::{Element, Theme};
//! //!
//! fn view(state: &State) -> Element<Message> { //! fn view(state: &State) -> Element<'_, Message> {
//! button("I am a styled button!").style(|theme: &Theme, status| { //! button("I am a styled button!").style(|theme: &Theme, status| {
//! let palette = theme.extended_palette(); //! let palette = theme.extended_palette();
//! //!
@ -359,7 +359,7 @@
//! } //! }
//! # fn new() -> State { State } //! # fn new() -> State { State }
//! # fn update(state: &mut State, message: Message) {} //! # fn update(state: &mut State, message: Message) {}
//! # fn view(state: &State) -> iced::Element<Message> { iced::widget::text("").into() } //! # fn view(state: &State) -> iced::Element<'_, Message> { iced::widget::text("").into() }
//! ``` //! ```
//! //!
//! A [`Subscription`] is [a _declarative_ builder of streams](Subscription#the-lifetime-of-a-subscription) //! A [`Subscription`] is [a _declarative_ builder of streams](Subscription#the-lifetime-of-a-subscription)
@ -452,7 +452,7 @@
//! } //! }
//! } //! }
//! //!
//! fn view(state: &State) -> Element<Message> { //! fn view(state: &State) -> Element<'_, Message> {
//! match &state.screen { //! match &state.screen {
//! Screen::Contacts(contacts) => contacts.view().map(Message::Contacts), //! Screen::Contacts(contacts) => contacts.view().map(Message::Contacts),
//! Screen::Conversation(conversation) => conversation.view().map(Message::Conversation), //! Screen::Conversation(conversation) => conversation.view().map(Message::Conversation),
@ -659,8 +659,8 @@ pub type Element<
/// The result of running an iced program. /// The result of running an iced program.
pub type Result = std::result::Result<(), Error>; pub type Result = std::result::Result<(), Error>;
/// Runs a basic iced application with default [`Settings`] given its title, /// Runs a basic iced application with default [`Settings`] given its update
/// update, and view logic. /// and view logic.
/// ///
/// This is equivalent to chaining [`application()`] with [`Application::run`]. /// This is equivalent to chaining [`application()`] with [`Application::run`].
/// ///

View file

@ -153,6 +153,9 @@ impl<P: Program + 'static> Emulator<P> {
Action::Exit => { Action::Exit => {
// TODO // TODO
} }
Action::Reload => {
// TODO
}
} }
} }

View file

@ -626,10 +626,10 @@ fn prepare(
scale: transformation.scale_factor() scale: transformation.scale_factor()
* layer_transformation.scale_factor(), * layer_transformation.scale_factor(),
bounds: cryoglyph::TextBounds { bounds: cryoglyph::TextBounds {
left: clip_bounds.x as i32, left: clip_bounds.x.round() as i32,
top: clip_bounds.y as i32, top: clip_bounds.y.round() as i32,
right: (clip_bounds.x + clip_bounds.width) as i32, right: (clip_bounds.x + clip_bounds.width).round() as i32,
bottom: (clip_bounds.y + clip_bounds.height) as i32, bottom: (clip_bounds.y + clip_bounds.height).round() as i32,
}, },
default_color: to_color(color), default_color: to_color(color),
}) })

View file

@ -96,7 +96,7 @@ impl Compositor {
let adapter = instance let adapter = instance
.request_adapter(&adapter_options) .request_adapter(&adapter_options)
.await .await
.ok_or(Error::NoAdapterFound(format!("{:?}", adapter_options)))?; .ok_or(Error::NoAdapterFound(format!("{adapter_options:?}")))?;
log::info!("Selected: {:#?}", adapter.get_info()); log::info!("Selected: {:#?}", adapter.get_info());

View file

@ -400,9 +400,9 @@ where
Renderer: core::Renderer + 'a, Renderer: core::Renderer + 'a,
{ {
fn from( fn from(
column: Container<'a, Message, Theme, Renderer>, container: Container<'a, Message, Theme, Renderer>,
) -> Element<'a, Message, Theme, Renderer> { ) -> Element<'a, Message, Theme, Renderer> {
Element::new(column) Element::new(container)
} }
} }

View file

@ -50,7 +50,10 @@ use crate::core::theme;
use crate::core::{ use crate::core::{
self, Color, Element, Length, Padding, Pixels, Theme, color, self, Color, Element, Length, Padding, Pixels, Theme, color,
}; };
use crate::{column, container, rich_text, row, scrollable, span, text}; use crate::{
column, container, horizontal_rule, rich_text, row, rule, scrollable, span,
text, vertical_rule,
};
use std::borrow::BorrowMut; use std::borrow::BorrowMut;
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
@ -208,6 +211,10 @@ pub enum Item {
/// The alternative text of the image. /// The alternative text of the image.
alt: Text, alt: Text,
}, },
/// A quote.
Quote(Vec<Item>),
/// A horizontal separator.
Rule,
} }
/// A bunch of parsed Markdown text. /// A bunch of parsed Markdown text.
@ -339,8 +346,8 @@ impl Span {
/// ///
/// fn view(&self) -> Element<'_, Message> { /// fn view(&self) -> Element<'_, Message> {
/// markdown::view(&self.markdown, Theme::TokyoNight) /// markdown::view(&self.markdown, Theme::TokyoNight)
/// .map(Message::LinkClicked) /// .map(Message::LinkClicked)
/// .into() /// .into()
/// } /// }
/// ///
/// fn update(state: &mut State, message: Message) { /// fn update(state: &mut State, message: Message) {
@ -454,6 +461,7 @@ fn parse_with<'a>(
) -> impl Iterator<Item = (Item, &'a str, HashSet<String>)> + 'a { ) -> impl Iterator<Item = (Item, &'a str, HashSet<String>)> + 'a {
enum Scope { enum Scope {
List(List), List(List),
Quote(Vec<Item>),
} }
struct List { struct List {
@ -524,6 +532,9 @@ fn parse_with<'a>(
Scope::List(list) => { Scope::List(list) => {
list.items.last_mut().expect("item context").push(item); list.items.last_mut().expect("item context").push(item);
} }
Scope::Quote(items) => {
items.push(item);
}
} }
None None
@ -605,6 +616,22 @@ fn parse_with<'a>(
None None
} }
pulldown_cmark::Tag::BlockQuote(_kind) if !metadata && !table => {
let prev = if spans.is_empty() {
None
} else {
produce(
state.borrow_mut(),
&mut stack,
Item::Paragraph(Text::new(spans.drain(..).collect())),
source,
)
};
stack.push(Scope::Quote(Vec::new()));
prev
}
pulldown_cmark::Tag::CodeBlock( pulldown_cmark::Tag::CodeBlock(
pulldown_cmark::CodeBlockKind::Fenced(language), pulldown_cmark::CodeBlockKind::Fenced(language),
) if !metadata && !table => { ) if !metadata && !table => {
@ -703,7 +730,9 @@ fn parse_with<'a>(
pulldown_cmark::TagEnd::List(_) if !metadata && !table => { pulldown_cmark::TagEnd::List(_) if !metadata && !table => {
let scope = stack.pop()?; let scope = stack.pop()?;
let Scope::List(list) = scope; let Scope::List(list) = scope else {
return None;
};
produce( produce(
state.borrow_mut(), state.borrow_mut(),
@ -715,6 +744,22 @@ fn parse_with<'a>(
source, source,
) )
} }
pulldown_cmark::TagEnd::BlockQuote(_kind)
if !metadata && !table =>
{
let scope = stack.pop()?;
let Scope::Quote(quote) = scope else {
return None;
};
produce(
state.borrow_mut(),
&mut stack,
Item::Quote(quote),
source,
)
}
pulldown_cmark::TagEnd::Image if !metadata && !table => { pulldown_cmark::TagEnd::Image if !metadata && !table => {
let (url, title) = image.take()?; let (url, title) = image.take()?;
let alt = Text::new(spans.drain(..).collect()); let alt = Text::new(spans.drain(..).collect());
@ -834,6 +879,9 @@ fn parse_with<'a>(
}); });
None None
} }
pulldown_cmark::Event::Rule => {
produce(state.borrow_mut(), &mut stack, Item::Rule, source)
}
_ => None, _ => None,
}) })
} }
@ -1063,6 +1111,8 @@ where
start: Some(start), start: Some(start),
items, items,
} => viewer.ordered_list(settings, *start, items), } => viewer.ordered_list(settings, *start, items),
Item::Quote(quote) => viewer.quote(settings, quote),
Item::Rule => viewer.rule(settings),
} }
} }
@ -1226,7 +1276,44 @@ where
.into() .into()
} }
/// A view strategy to display a Markdown [`Item`].j /// Displays a quote using the default look.
pub fn quote<'a, Message, Theme, Renderer>(
viewer: &impl Viewer<'a, Message, Theme, Renderer>,
settings: Settings,
contents: &'a [Item],
) -> Element<'a, Message, Theme, Renderer>
where
Message: 'a,
Theme: Catalog + 'a,
Renderer: core::text::Renderer<Font = Font> + 'a,
{
row![
vertical_rule(4),
column(
contents
.iter()
.enumerate()
.map(|(i, content)| item(viewer, settings, content, i)),
)
.spacing(settings.spacing.0),
]
.height(Length::Shrink)
.spacing(settings.spacing.0)
.into()
}
/// Displays a rule using the default look.
pub fn rule<'a, Message, Theme, Renderer>()
-> Element<'a, Message, Theme, Renderer>
where
Message: 'a,
Theme: Catalog + 'a,
Renderer: core::text::Renderer<Font = Font> + 'a,
{
horizontal_rule(2).into()
}
/// A view strategy to display a Markdown [`Item`].
pub trait Viewer<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer> pub trait Viewer<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer>
where where
Self: Sized + 'a, Self: Sized + 'a,
@ -1321,6 +1408,27 @@ where
) -> Element<'a, Message, Theme, Renderer> { ) -> Element<'a, Message, Theme, Renderer> {
ordered_list(self, settings, start, items) ordered_list(self, settings, start, items)
} }
/// Displays a quote.
///
/// By default, it calls [`quote`].
fn quote(
&self,
settings: Settings,
contents: &'a [Item],
) -> Element<'a, Message, Theme, Renderer> {
quote(self, settings, contents)
}
/// Displays a rule.
///
/// By default, it calls [`rule`](self::rule()).
fn rule(
&self,
_settings: Settings,
) -> Element<'a, Message, Theme, Renderer> {
rule()
}
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -1338,7 +1446,7 @@ where
/// The theme catalog of Markdown items. /// The theme catalog of Markdown items.
pub trait Catalog: pub trait Catalog:
container::Catalog + scrollable::Catalog + text::Catalog container::Catalog + scrollable::Catalog + rule::Catalog + text::Catalog
{ {
/// The styling class of a Markdown code block. /// The styling class of a Markdown code block.
fn code_block<'a>() -> <Self as container::Catalog>::Class<'a>; fn code_block<'a>() -> <Self as container::Catalog>::Class<'a>;

View file

@ -297,11 +297,11 @@ where
} }
fn drag_enabled(&self) -> bool { fn drag_enabled(&self) -> bool {
self.internal if self.internal.maximized().is_none() {
.maximized() self.on_drag.is_some()
.is_none() } else {
.then(|| self.on_drag.is_some()) Default::default()
.unwrap_or_default() }
} }
fn grid_interaction( fn grid_interaction(

View file

@ -134,9 +134,7 @@ where
let style = theme.style(&self.class); let style = theme.style(&self.class);
let bounds = if self.is_horizontal { let bounds = if self.is_horizontal {
let line_y = (bounds.y + (bounds.height / 2.0) let line_y = (bounds.y + (bounds.height / 2.0)).round();
- (style.width as f32 / 2.0))
.round();
let (offset, line_width) = style.fill_mode.fill(bounds.width); let (offset, line_width) = style.fill_mode.fill(bounds.width);
let line_x = bounds.x + offset; let line_x = bounds.x + offset;
@ -145,12 +143,10 @@ where
x: line_x, x: line_x,
y: line_y, y: line_y,
width: line_width, width: line_width,
height: style.width as f32, height: bounds.height,
} }
} else { } else {
let line_x = (bounds.x + (bounds.width / 2.0) let line_x = (bounds.x + (bounds.width / 2.0)).round();
- (style.width as f32 / 2.0))
.round();
let (offset, line_height) = style.fill_mode.fill(bounds.height); let (offset, line_height) = style.fill_mode.fill(bounds.height);
let line_y = bounds.y + offset; let line_y = bounds.y + offset;
@ -158,7 +154,7 @@ where
Rectangle { Rectangle {
x: line_x, x: line_x,
y: line_y, y: line_y,
width: style.width as f32, width: bounds.width,
height: line_height, height: line_height,
} }
}; };
@ -167,6 +163,7 @@ where
renderer::Quad { renderer::Quad {
bounds, bounds,
border: border::rounded(style.radius), border: border::rounded(style.radius),
snap: style.snap,
..renderer::Quad::default() ..renderer::Quad::default()
}, },
style.color, style.color,
@ -191,12 +188,12 @@ where
pub struct Style { pub struct Style {
/// The color of the rule. /// The color of the rule.
pub color: Color, pub color: Color,
/// The width (thickness) of the rule line.
pub width: u16,
/// The radius of the line corners. /// The radius of the line corners.
pub radius: border::Radius, pub radius: border::Radius,
/// The [`FillMode`] of the rule. /// The [`FillMode`] of the rule.
pub fill_mode: FillMode, pub fill_mode: FillMode,
/// Whether the rule should be snapped to the pixel grid.
pub snap: bool,
} }
/// The fill mode of a rule. /// The fill mode of a rule.
@ -298,8 +295,8 @@ pub fn default(theme: &Theme) -> Style {
Style { Style {
color: palette.background.strong.color, color: palette.background.strong.color,
width: 1,
radius: 0.0.into(), radius: 0.0.into(),
fill_mode: FillMode::Full, fill_mode: FillMode::Full,
snap: true,
} }
} }

View file

@ -438,7 +438,7 @@ where
}, },
|limits| { |limits| {
let child_limits = layout::Limits::new( let child_limits = layout::Limits::new(
Size::new(limits.min().width, limits.min().height), limits.min(),
Size::new( Size::new(
if self.direction.horizontal().is_some() { if self.direction.horizontal().is_some() {
f32::INFINITY f32::INFINITY

View file

@ -85,6 +85,15 @@ where
let (proxy, worker) = Proxy::new(event_loop.create_proxy()); let (proxy, worker) = Proxy::new(event_loop.create_proxy());
#[cfg(feature = "debug")]
{
let proxy = proxy.clone();
debug::on_hotpatch(move || {
proxy.send_action(Action::Reload);
});
}
let mut runtime = { let mut runtime = {
let executor = let executor =
P::Executor::new().map_err(Error::ExecutorCreationFailed)?; P::Executor::new().map_err(Error::ExecutorCreationFailed)?;
@ -527,7 +536,7 @@ async fn run_instance<P>(
let create_compositor = { let create_compositor = {
let window = window.clone(); let window = window.clone();
let mut proxy = proxy.clone(); let proxy = proxy.clone();
let default_fonts = default_fonts.clone(); let default_fonts = default_fonts.clone();
async move { async move {
@ -801,7 +810,7 @@ async fn run_instance<P>(
Err(error) => match error { Err(error) => match error {
// This is an unrecoverable error. // This is an unrecoverable error.
compositor::SurfaceError::OutOfMemory => { compositor::SurfaceError::OutOfMemory => {
panic!("{:?}", error); panic!("{error:?}");
} }
_ => { _ => {
present_span.finish(); present_span.finish();
@ -1075,9 +1084,9 @@ fn update<P: Program, E: Executor>(
runtime.track(recipes); runtime.track(recipes);
} }
fn run_action<P, C>( fn run_action<'a, P, C>(
action: Action<P::Message>, action: Action<P::Message>,
program: &program::Instance<P>, program: &'a program::Instance<P>,
compositor: &mut Option<C>, compositor: &mut Option<C>,
events: &mut Vec<(window::Id, core::Event)>, events: &mut Vec<(window::Id, core::Event)>,
messages: &mut Vec<P::Message>, messages: &mut Vec<P::Message>,
@ -1085,7 +1094,7 @@ fn run_action<P, C>(
control_sender: &mut mpsc::UnboundedSender<Control>, control_sender: &mut mpsc::UnboundedSender<Control>,
interfaces: &mut FxHashMap< interfaces: &mut FxHashMap<
window::Id, window::Id,
UserInterface<'_, P::Message, P::Theme, P::Renderer>, UserInterface<'a, P::Message, P::Theme, P::Renderer>,
>, >,
window_manager: &mut WindowManager<P, C>, window_manager: &mut WindowManager<P, C>,
ui_caches: &mut FxHashMap<window::Id, user_interface::Cache>, ui_caches: &mut FxHashMap<window::Id, user_interface::Cache>,
@ -1437,6 +1446,29 @@ fn run_action<P, C>(
let _ = channel.send(Ok(())); let _ = channel.send(Ok(()));
} }
} }
Action::Reload => {
for (id, window) in window_manager.iter_mut() {
let Some(ui) = interfaces.remove(&id) else {
continue;
};
let cache = ui.into_cache();
let size = window.size();
let _ = interfaces.insert(
id,
build_user_interface(
program,
cache,
&mut window.renderer,
size,
id,
),
);
window.raw.request_redraw();
}
}
Action::Exit => { Action::Exit => {
control_sender control_sender
.start_send(Control::Exit) .start_send(Control::Exit)

View file

@ -77,7 +77,7 @@ impl<T: 'static> Proxy<T> {
/// ///
/// Note: This skips the backpressure mechanism with an unbounded /// Note: This skips the backpressure mechanism with an unbounded
/// channel. Use sparingly! /// channel. Use sparingly!
pub fn send(&mut self, value: T) pub fn send(&self, value: T)
where where
T: std::fmt::Debug, T: std::fmt::Debug,
{ {
@ -88,13 +88,11 @@ impl<T: 'static> Proxy<T> {
/// ///
/// Note: This skips the backpressure mechanism with an unbounded /// Note: This skips the backpressure mechanism with an unbounded
/// channel. Use sparingly! /// channel. Use sparingly!
pub fn send_action(&mut self, action: Action<T>) pub fn send_action(&self, action: Action<T>)
where where
T: std::fmt::Debug, T: std::fmt::Debug,
{ {
self.raw let _ = self.raw.send_event(action);
.send_event(action)
.expect("Send message to event loop");
} }
/// Frees an amount of slots for additional messages to be queued in /// Frees an amount of slots for additional messages to be queued in