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",
]
[[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]]
name = "bit-set"
version = "0.8.0"
@ -730,6 +750,17 @@ dependencies = [
"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]]
name = "cast"
version = "0.3.0"
@ -2464,7 +2495,7 @@ dependencies = [
name = "iced_beacon"
version = "0.14.0-dev"
dependencies = [
"bincode",
"bincode 1.3.3",
"futures",
"iced_core",
"log",
@ -2496,6 +2527,7 @@ dependencies = [
name = "iced_debug"
version = "0.14.0-dev"
dependencies = [
"cargo-hot-protocol",
"iced_beacon",
"iced_core",
"iced_futures",
@ -3293,6 +3325,15 @@ version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "memmap2"
version = "0.9.5"
@ -5518,6 +5559,34 @@ dependencies = [
"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]]
name = "subtle"
version = "2.6.1"
@ -5595,7 +5664,7 @@ version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1"
dependencies = [
"bincode",
"bincode 1.3.3",
"bitflags 1.3.2",
"flate2",
"fnv",
@ -6281,6 +6350,12 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "unty"
version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
[[package]]
name = "url"
version = "2.5.4"
@ -6406,6 +6481,12 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "virtue"
version = "0.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1"
[[package]]
name = "visible_bounds"
version = "0.1.0"

View file

@ -41,10 +41,12 @@ qr_code = ["iced_widget/qr_code"]
markdown = ["iced_widget/markdown"]
# Enables lazy widgets
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"]
# Enables time-travel debugging (very experimental!)
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)
tester = ["debug", "test", "iced_devtools/tester"]
# Enables testing features (e.g. application presets)
@ -171,6 +173,7 @@ bincode = "1.3"
bitflags = "2.0"
bytemuck = { version = "1.0", features = ["derive"] }
bytes = "1.6"
cargo-hot = { package = "cargo-hot-protocol", git = "https://github.com/hecrj/cargo-hot.git", rev = "b8dc518b8640928178a501257e353b73bc06cf47" }
cosmic-text = "0.14"
dark-light = "2.0"
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
// 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 {
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();
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 child_limits =
@ -176,9 +184,9 @@ where
};
// 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.
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 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 mut main = pad.0;
// FOURTH PASS
// FIFTH PASS
// We align all the laid out nodes in the cross axis, if needed.
for (i, node) in nodes.iter_mut().enumerate() {
if i > 0 {

View file

@ -107,3 +107,13 @@ where
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.
pub fn snap(self) -> Option<Rectangle<u32>> {
let width = self.width as u32;
let height = self.height as u32;
let top_left = self.position().snap();
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 {
return None;
}
Some(Rectangle {
x: self.x as u32,
y: self.y as u32,
x: top_left.x,
y: top_left.y,
width,
height,
})

View file

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

View file

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

View file

@ -12,6 +12,7 @@ keywords.workspace = true
[features]
enable = ["dep:iced_beacon"]
hot = ["enable", "dep:cargo-hot"]
[dependencies]
iced_core.workspace = true
@ -21,3 +22,6 @@ log.workspace = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
iced_beacon.workspace = 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) {
internal::init(metadata);
hot::init();
}
pub fn quit() -> bool {
@ -113,6 +114,18 @@ pub fn commands() -> Subscription<Command> {
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")))]
mod internal {
use crate::core::theme;
@ -399,3 +412,83 @@ mod internal {
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));
let notification = self.show_notification.then(|| {
bottom_right(opaque(
container(text("Press F12 to open developer tools"))
.padding(10)
.style(container::dark),
))
});
let notification = self
.show_notification
.then(|| text("Press F12 to open debug metrics"))
.or_else(|| {
debug::is_stale().then(|| {
text(
"Types have changed. Restart to re-enable hotpatching.",
)
})
});
let sidebar = if let Mode::Open { tester } = &self.mode {
let title = monospace("Developer Tools");
@ -429,7 +432,13 @@ where
stack![content]
.height(Fill)
.push_maybe(setup.map(opaque))
.push_maybe(notification),
.push_maybe(notification.map(|notification| {
bottom_right(opaque(
container(notification)
.padding(10)
.style(container::dark),
))
})),
)
.into()
}

View file

@ -36,7 +36,7 @@ impl Arc {
self.cache.clear();
}
fn view(&self) -> Element<Message> {
fn view(&self) -> Element<'_, Message> {
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(
self.bezier.view(&self.curves).map(Message::AddCurve),
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 {
Self::Loading => center("Loading...").into(),
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)
.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);
container(canvas).padding(20).into()
@ -166,7 +166,7 @@ impl<Message> canvas::Program<Message> for Clock {
let y = radius * angle.0.sin();
frame.fill_text(canvas::Text {
content: format!("{}", hour),
content: format!("{hour}"),
size: (radius / 5.0).into(),
position: Point::new(x * 0.82, y * 0.82),
color: palette.secondary.strong.text,

View file

@ -56,7 +56,7 @@ impl ColorPalette {
self.theme = Theme::new(to_color(srgb));
}
fn view(&self) -> Element<Message> {
fn view(&self) -> Element<'_, Message> {
let base = self.theme.base;
let srgb = to_rgb(base);
@ -149,7 +149,7 @@ impl Theme {
.chain(self.higher.iter())
}
pub fn view(&self) -> Element<Message> {
pub fn view(&self) -> Element<'_, Message> {
Canvas::new(self).width(Fill).height(Fill).into()
}
@ -296,7 +296,7 @@ trait ColorSpace: Sized {
}
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 [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(
&self.languages,
"Type a language...",

View file

@ -28,7 +28,7 @@ impl Counter {
}
}
fn view(&self) -> Column<Message> {
fn view(&self) -> Column<'_, Message> {
column![
button("Increment").on_press(Message::Increment),
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 {
top_left,
top_right,

View file

@ -103,7 +103,7 @@ impl Example {
}
}
fn view(&self) -> Element<Message> {
fn view(&self) -> Element<'_, Message> {
let content = column![
circle(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 =
Column::with_children(self.downloads.iter().map(Download::view))
.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 {
State::Idle => 0.0,
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![
action(new_icon(), "New file", Some(Message::NewFile)),
action(

View file

@ -55,7 +55,7 @@ impl Events {
event::listen().map(Message::EventOccurred)
}
fn view(&self) -> Element<Message> {
fn view(&self) -> Element<'_, Message> {
let events = Column::with_children(
self.last
.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 {
column![
"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![
"Hello!",
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 selected_speed = self.next_speed.unwrap_or(self.speed);
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()
}

View file

@ -51,7 +51,7 @@ impl Gradient {
}
}
fn view(&self) -> Element<Message> {
fn view(&self) -> Element<'_, Message> {
let Self {
start,
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 sliders = row![

View file

@ -68,7 +68,7 @@ impl Layout {
})
}
fn view(&self) -> Element<Message> {
fn view(&self) -> Element<'_, Message> {
let header = row![
text(self.example.title).size(20).font(Font::MONOSPACE),
horizontal_space(),
@ -121,7 +121,7 @@ impl Layout {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, Eq)]
struct Example {
title: &'static str,
view: fn() -> Element<'static, Message>,
@ -190,7 +190,7 @@ impl Example {
Self::LIST.get(index + 1).copied().unwrap_or(self)
}
fn view(&self) -> Element<Message> {
fn view(&self) -> Element<'_, Message> {
(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> {
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 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 = [
&easing::EMPHASIZED,
&easing::EMPHASIZED_DECELERATE,

View file

@ -30,7 +30,7 @@ impl Loupe {
}
}
fn view(&self) -> Element<Message> {
fn view(&self) -> Element<'_, Message> {
center(loupe(
3.0,
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};
impl Counter {
pub fn view(&self) -> Column<Message> {
pub fn view(&self) -> Column<'_, Message> {
// We use a column: a simple vertical layout
column![
// 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)
.placeholder("Type your Markdown here...")
.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(
column![
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) {
center(window.view(window_id)).into()
} else {
@ -161,14 +161,14 @@ impl Example {
impl Window {
fn new(count: usize) -> Self {
Self {
title: format!("Window_{}", count),
title: format!("Window_{count}"),
scale_input: "1.0".to_string(),
current_scale: 1.0,
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![
text("Window scale factor:"),
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()
}
}

View file

@ -129,7 +129,7 @@ impl Example {
})
}
fn view(&self) -> Element<Message> {
fn view(&self) -> Element<'_, Message> {
let focus = self.focus;
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(
&Language::ALL[..],
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 {
Pokedex::Loading => {
text("Searching for Pokémon...").size(40).into()
@ -100,7 +100,7 @@ struct Pokemon {
impl Pokemon {
const TOTAL: u16 = 807;
fn view(&self) -> Element<Message> {
fn view(&self) -> Element<'_, Message> {
row![
image::viewer(self.image.clone()),
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);
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 input =

View file

@ -174,7 +174,7 @@ impl Example {
|png_result| match png_result {
Ok(path) => format!("Png saved as: {path:?}!"),
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(
0..=15,
self.scrollbar_width,

View file

@ -1,7 +1,9 @@
use iced::mouse;
use iced::widget::canvas::{self, Canvas, Event, Geometry};
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 std::fmt::Debug;
@ -46,7 +48,7 @@ impl SierpinskiEmulator {
self.graph.redraw();
}
fn view(&self) -> iced::Element<'_, Message> {
fn view(&self) -> Element<'_, Message> {
column![
Canvas::new(&self.graph).width(Fill).height(Fill),
row![

View file

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

View file

@ -83,7 +83,7 @@ impl Stopwatch {
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 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![
text("Theme:"),
pick_list(Theme::ALL, Some(&self.theme), Message::ThemeChanged)
@ -126,7 +126,7 @@ impl Styling {
let content = column![
choose_theme,
horizontal_rule(38),
horizontal_rule(1),
text_input,
row![primary, success, warning, danger]
.spacing(10)
@ -135,8 +135,8 @@ impl Styling {
progress_bar(),
row![
scrollable,
vertical_rule(38),
column![checkbox, toggler].spacing(20)
row![vertical_rule(1), column![checkbox, toggler].spacing(20)]
.spacing(20)
]
.spacing(10)
.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!(
"{}/resources/tiger.svg",
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;
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()
}

View file

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

View file

@ -186,7 +186,7 @@ impl Todos {
}
}
fn view(&self) -> Element<Message> {
fn view(&self) -> Element<'_, Message> {
match self {
Todos::Loading => loading_message(),
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 {
TaskState::Idle => {
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 filter_button = |label, filter, current_filter| {
@ -617,7 +620,7 @@ mod tests {
use iced_test::selector::id;
use iced_test::{Error, Simulator};
fn simulator(todos: &Todos) -> Simulator<Message> {
fn simulator(todos: &Todos) -> Simulator<'_, Message> {
Simulator::with_settings(
Settings {
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(
button("Press to change position")
.on_press(Message::ChangePosition),

View file

@ -76,7 +76,7 @@ impl Tour {
Screen::End => "End",
};
format!("{} - Iced", screen)
format!("{screen} - Iced")
}
fn update(&mut self, event: Message) {
@ -141,7 +141,7 @@ impl Tour {
}
}
fn view(&self) -> Element<Message> {
fn view(&self) -> Element<'_, Message> {
let controls =
row![]
.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!")
.push(
"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")
.push(
"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))
}
fn rows_and_columns(&self) -> Column<Message> {
fn rows_and_columns(&self) -> Column<'_, Message> {
let row_radio = radio(
"Row",
Layout::Row,
@ -303,7 +303,7 @@ impl Tour {
.push(spacing_section)
}
fn text(&self) -> Column<Message> {
fn text(&self) -> Column<'_, Message> {
let size = self.text_size;
let color = self.text_color;
@ -339,7 +339,7 @@ impl Tour {
.push(color_section)
}
fn radio(&self) -> Column<Message> {
fn radio(&self) -> Column<'_, Message> {
let question = column![
text("Iced is written in...").size(24),
column(
@ -374,7 +374,7 @@ impl Tour {
)
}
fn toggler(&self) -> Column<Message> {
fn toggler(&self) -> Column<'_, Message> {
Self::container("Toggler")
.push("A toggler is mostly used to enable or disable something.")
.push(
@ -387,7 +387,7 @@ impl Tour {
)
}
fn image(&self) -> Column<Message> {
fn image(&self) -> Column<'_, Message> {
let width = self.image_width;
let filter_method = self.image_filter_method;
@ -406,7 +406,7 @@ impl Tour {
.align_x(Center)
}
fn scrollable(&self) -> Column<Message> {
fn scrollable(&self) -> Column<'_, Message> {
Self::container("Scrollable")
.push(
"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))
}
fn text_input(&self) -> Column<Message> {
fn text_input(&self) -> Column<'_, Message> {
let value = &self.input_value;
let is_secure = self.input_is_secure;
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")
.push(
"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.")
}
fn end(&self) -> Column<Message> {
fn end(&self) -> Column<'_, Message> {
Self::container("You reached the end!")
.push("This tour will be updated as more features are added.")
.push("Make sure to keep an eye on it!")

View file

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

View file

@ -48,7 +48,7 @@ impl VectorialText {
self.state.cache.clear();
}
fn view(&self) -> Element<Message> {
fn view(&self) -> Element<'_, Message> {
let slider_with_label = |label, range, value, message: fn(f32) -> _| {
column![
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| {
row![
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() {
center(
text("Your messages will appear here...")

View file

@ -56,6 +56,9 @@ pub enum Action<T> {
/// Run a system action.
System(system::Action),
/// Recreate all user interfaces and redraw all windows.
Reload,
/// Exits the runtime.
///
/// This will normally close any application windows and
@ -79,6 +82,7 @@ impl<T> Action<T> {
Action::Clipboard(action) => Err(Action::Clipboard(action)),
Action::Window(action) => Err(Action::Window(action)),
Action::System(action) => Err(Action::System(action)),
Action::Reload => Err(Action::Reload),
Action::Exit => Err(Action::Exit),
}
}
@ -102,6 +106,7 @@ where
}
Action::Window(_) => write!(f, "Action::Window"),
Action::System(action) => write!(f, "Action::System({action:?})"),
Action::Reload => write!(f, "Action::Reload"),
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 [`Task`] _may_ produce a bunch of values of type `T`.
#[allow(missing_debug_implementations)]
#[must_use = "`Task` must be returned to the runtime to take effect; normally in your `update` or `new` functions."]
pub struct Task<T> {
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.
#[derive(Debug, Clone)]
pub struct Handle {

View file

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

View file

@ -6,6 +6,8 @@ use crate::time::Instant;
use crate::window;
use crate::{Element, Program, Settings, Subscription, Task};
use iced_debug as debug;
/// Creates an [`Application`] with an `update` function that also
/// takes the [`Instant`] of each `Message`.
///
@ -101,10 +103,12 @@ where
state: &mut Self::State,
(message, now): Self::Message,
) -> Task<Self::Message> {
self.update
.update(state, message, now)
.into()
.map(|message| (message, Instant::now()))
debug::hot(move || {
self.update
.update(state, message, now)
.into()
.map(|message| (message, Instant::now()))
})
}
fn view<'a>(
@ -112,16 +116,21 @@ where
state: &'a Self::State,
_window: window::Id,
) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
self.view
.view(state)
.map(|message| (message, Instant::now()))
debug::hot(|| {
self.view
.view(state)
.map(|message| (message, Instant::now()))
})
}
fn subscription(
&self,
state: &Self::State,
) -> 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::{Element, Executor, Font, Result, Settings, Subscription, Task};
use iced_debug as debug;
use std::borrow::Cow;
/// Creates an iced [`Daemon`] given its boot, update, and view logic.
@ -76,7 +78,7 @@ where
state: &mut Self::State,
message: Self::Message,
) -> Task<Self::Message> {
self.update.update(state, message)
debug::hot(|| self.update.update(state, message))
}
fn view<'a>(
@ -84,7 +86,7 @@ where
state: &'a Self::State,
window: window::Id,
) -> 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 {
raw: program::with_title(self.raw, move |state, window| {
title.title(state, window)
debug::hot(|| title.title(state, window))
}),
settings: self.settings,
}
@ -196,7 +198,9 @@ impl<P: Program> Daemon<P> {
impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
> {
Daemon {
raw: program::with_subscription(self.raw, f),
raw: program::with_subscription(self.raw, move |state| {
debug::hot(|| f(state))
}),
settings: self.settings,
}
}
@ -209,7 +213,9 @@ impl<P: Program> Daemon<P> {
impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
> {
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,
}
}
@ -222,7 +228,9 @@ impl<P: Program> Daemon<P> {
impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
> {
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,
}
}
@ -235,7 +243,9 @@ impl<P: Program> Daemon<P> {
impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
> {
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,
}
}

View file

@ -34,7 +34,7 @@
//! iced::run(update, view)
//! }
//! # 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:
@ -55,7 +55,7 @@
//! use iced::widget::{button, text};
//! use iced::Element;
//!
//! fn view(counter: &u64) -> Element<Message> {
//! fn view(counter: &u64) -> Element<'_, Message> {
//! button(text(counter)).on_press(Message::Increment).into()
//! }
//! # #[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()
//! }
//! ```
@ -115,7 +115,7 @@
//! use iced::widget::{button, column, text};
//! use iced::Element;
//!
//! fn view(counter: &Counter) -> Element<Message> {
//! fn view(counter: &Counter) -> Element<'_, Message> {
//! column![
//! text(counter.value).size(20),
//! button("Increment").on_press(Message::Increment),
@ -144,7 +144,7 @@
//! use iced::widget::{column, container, row};
//! use iced::{Fill, Element};
//!
//! fn view(state: &State) -> Element<Message> {
//! fn view(state: &State) -> Element<'_, Message> {
//! container(
//! column![
//! "Top",
@ -187,7 +187,7 @@
//! use iced::widget::container;
//! use iced::Element;
//!
//! fn view(state: &State) -> Element<Message> {
//! fn view(state: &State) -> Element<'_, Message> {
//! container("I am 300px tall!").height(300).into()
//! }
//! ```
@ -216,7 +216,7 @@
//! Theme::TokyoNight
//! }
//! # 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
@ -237,7 +237,7 @@
//! use iced::widget::container;
//! 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()
//! }
//! ```
@ -252,7 +252,7 @@
//! use iced::widget::button;
//! 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| {
//! let palette = theme.extended_palette();
//!
@ -359,7 +359,7 @@
//! }
//! # fn new() -> State { State }
//! # 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)
@ -452,7 +452,7 @@
//! }
//! }
//!
//! fn view(state: &State) -> Element<Message> {
//! fn view(state: &State) -> Element<'_, Message> {
//! match &state.screen {
//! Screen::Contacts(contacts) => contacts.view().map(Message::Contacts),
//! Screen::Conversation(conversation) => conversation.view().map(Message::Conversation),
@ -659,8 +659,8 @@ pub type Element<
/// The result of running an iced program.
pub type Result = std::result::Result<(), Error>;
/// Runs a basic iced application with default [`Settings`] given its title,
/// update, and view logic.
/// Runs a basic iced application with default [`Settings`] given its update
/// and view logic.
///
/// This is equivalent to chaining [`application()`] with [`Application::run`].
///

View file

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

View file

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

View file

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

View file

@ -400,9 +400,9 @@ where
Renderer: core::Renderer + 'a,
{
fn from(
column: Container<'a, Message, Theme, Renderer>,
container: Container<'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::{
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::cell::{Cell, RefCell};
@ -208,6 +211,10 @@ pub enum Item {
/// The alternative text of the image.
alt: Text,
},
/// A quote.
Quote(Vec<Item>),
/// A horizontal separator.
Rule,
}
/// A bunch of parsed Markdown text.
@ -339,8 +346,8 @@ impl Span {
///
/// fn view(&self) -> Element<'_, Message> {
/// markdown::view(&self.markdown, Theme::TokyoNight)
/// .map(Message::LinkClicked)
/// .into()
/// .map(Message::LinkClicked)
/// .into()
/// }
///
/// fn update(state: &mut State, message: Message) {
@ -454,6 +461,7 @@ fn parse_with<'a>(
) -> impl Iterator<Item = (Item, &'a str, HashSet<String>)> + 'a {
enum Scope {
List(List),
Quote(Vec<Item>),
}
struct List {
@ -524,6 +532,9 @@ fn parse_with<'a>(
Scope::List(list) => {
list.items.last_mut().expect("item context").push(item);
}
Scope::Quote(items) => {
items.push(item);
}
}
None
@ -605,6 +616,22 @@ fn parse_with<'a>(
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::CodeBlockKind::Fenced(language),
) if !metadata && !table => {
@ -703,7 +730,9 @@ fn parse_with<'a>(
pulldown_cmark::TagEnd::List(_) if !metadata && !table => {
let scope = stack.pop()?;
let Scope::List(list) = scope;
let Scope::List(list) = scope else {
return None;
};
produce(
state.borrow_mut(),
@ -715,6 +744,22 @@ fn parse_with<'a>(
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 => {
let (url, title) = image.take()?;
let alt = Text::new(spans.drain(..).collect());
@ -834,6 +879,9 @@ fn parse_with<'a>(
});
None
}
pulldown_cmark::Event::Rule => {
produce(state.borrow_mut(), &mut stack, Item::Rule, source)
}
_ => None,
})
}
@ -1063,6 +1111,8 @@ where
start: Some(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()
}
/// 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>
where
Self: Sized + 'a,
@ -1321,6 +1408,27 @@ where
) -> Element<'a, Message, Theme, Renderer> {
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)]
@ -1338,7 +1446,7 @@ where
/// The theme catalog of Markdown items.
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.
fn code_block<'a>() -> <Self as container::Catalog>::Class<'a>;

View file

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

View file

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

View file

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

View file

@ -85,6 +85,15 @@ where
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 executor =
P::Executor::new().map_err(Error::ExecutorCreationFailed)?;
@ -527,7 +536,7 @@ async fn run_instance<P>(
let create_compositor = {
let window = window.clone();
let mut proxy = proxy.clone();
let proxy = proxy.clone();
let default_fonts = default_fonts.clone();
async move {
@ -801,7 +810,7 @@ async fn run_instance<P>(
Err(error) => match error {
// This is an unrecoverable error.
compositor::SurfaceError::OutOfMemory => {
panic!("{:?}", error);
panic!("{error:?}");
}
_ => {
present_span.finish();
@ -1075,9 +1084,9 @@ fn update<P: Program, E: Executor>(
runtime.track(recipes);
}
fn run_action<P, C>(
fn run_action<'a, P, C>(
action: Action<P::Message>,
program: &program::Instance<P>,
program: &'a program::Instance<P>,
compositor: &mut Option<C>,
events: &mut Vec<(window::Id, core::Event)>,
messages: &mut Vec<P::Message>,
@ -1085,7 +1094,7 @@ fn run_action<P, C>(
control_sender: &mut mpsc::UnboundedSender<Control>,
interfaces: &mut FxHashMap<
window::Id,
UserInterface<'_, P::Message, P::Theme, P::Renderer>,
UserInterface<'a, P::Message, P::Theme, P::Renderer>,
>,
window_manager: &mut WindowManager<P, C>,
ui_caches: &mut FxHashMap<window::Id, user_interface::Cache>,
@ -1437,6 +1446,29 @@ fn run_action<P, C>(
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 => {
control_sender
.start_send(Control::Exit)

View file

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