summaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to 'examples')
-rw-r--r--examples/geometry/Cargo.toml1
-rw-r--r--examples/geometry/src/main.rs28
-rw-r--r--examples/loading_spinners/Cargo.toml11
-rw-r--r--examples/loading_spinners/README.md14
-rw-r--r--examples/loading_spinners/src/circular.rs421
-rw-r--r--examples/loading_spinners/src/easing.rs133
-rw-r--r--examples/loading_spinners/src/linear.rs326
-rw-r--r--examples/loading_spinners/src/main.rs118
-rw-r--r--examples/modal/src/main.rs57
-rw-r--r--examples/screenshot/Cargo.toml11
-rw-r--r--examples/screenshot/src/main.rs320
-rw-r--r--examples/scrollable/src/main.rs28
-rw-r--r--examples/styling/src/main.rs4
-rw-r--r--examples/toast/src/main.rs7
14 files changed, 1445 insertions, 34 deletions
diff --git a/examples/geometry/Cargo.toml b/examples/geometry/Cargo.toml
index 79fe52d5..6068d651 100644
--- a/examples/geometry/Cargo.toml
+++ b/examples/geometry/Cargo.toml
@@ -7,4 +7,3 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["advanced"] }
-iced_graphics = { path = "../../graphics" }
diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs
index 29f78ea1..3bc7f46b 100644
--- a/examples/geometry/src/main.rs
+++ b/examples/geometry/src/main.rs
@@ -1,8 +1,6 @@
//! This example showcases a simple native custom widget that renders using
//! arbitrary low-level geometry.
mod rainbow {
- use iced_graphics::primitive::{ColoredVertex2D, Primitive};
-
use iced::advanced::graphics::color;
use iced::advanced::layout::{self, Layout};
use iced::advanced::renderer;
@@ -46,8 +44,8 @@ mod rainbow {
cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
+ use iced::advanced::graphics::mesh::{self, Mesh, SolidVertex2D};
use iced::advanced::Renderer as _;
- use iced_graphics::primitive::Mesh2D;
let bounds = layout.bounds();
@@ -78,43 +76,43 @@ mod rainbow {
let posn_bl = [0.0, bounds.height];
let posn_l = [0.0, bounds.height / 2.0];
- let mesh = Primitive::SolidMesh {
+ let mesh = Mesh::Solid {
size: bounds.size(),
- buffers: Mesh2D {
+ buffers: mesh::Indexed {
vertices: vec![
- ColoredVertex2D {
+ SolidVertex2D {
position: posn_center,
color: color::pack([1.0, 1.0, 1.0, 1.0]),
},
- ColoredVertex2D {
+ SolidVertex2D {
position: posn_tl,
color: color::pack(color_r),
},
- ColoredVertex2D {
+ SolidVertex2D {
position: posn_t,
color: color::pack(color_o),
},
- ColoredVertex2D {
+ SolidVertex2D {
position: posn_tr,
color: color::pack(color_y),
},
- ColoredVertex2D {
+ SolidVertex2D {
position: posn_r,
color: color::pack(color_g),
},
- ColoredVertex2D {
+ SolidVertex2D {
position: posn_br,
color: color::pack(color_gb),
},
- ColoredVertex2D {
+ SolidVertex2D {
position: posn_b,
color: color::pack(color_b),
},
- ColoredVertex2D {
+ SolidVertex2D {
position: posn_bl,
color: color::pack(color_i),
},
- ColoredVertex2D {
+ SolidVertex2D {
position: posn_l,
color: color::pack(color_v),
},
@@ -135,7 +133,7 @@ mod rainbow {
renderer.with_translation(
Vector::new(bounds.x, bounds.y),
|renderer| {
- renderer.draw_primitive(mesh);
+ renderer.draw_mesh(mesh);
},
);
}
diff --git a/examples/loading_spinners/Cargo.toml b/examples/loading_spinners/Cargo.toml
new file mode 100644
index 00000000..ee9a48aa
--- /dev/null
+++ b/examples/loading_spinners/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "loading_spinners"
+version = "0.1.0"
+authors = ["Nick Senger <dev@nsenger.com>"]
+edition = "2021"
+publish = false
+
+[dependencies]
+iced = { path = "../..", features = ["advanced", "canvas"] }
+lyon_algorithms = "1"
+once_cell = "1"
diff --git a/examples/loading_spinners/README.md b/examples/loading_spinners/README.md
new file mode 100644
index 00000000..3573c6f6
--- /dev/null
+++ b/examples/loading_spinners/README.md
@@ -0,0 +1,14 @@
+## Loading Spinners
+
+Example implementation of animated indeterminate loading spinners.
+
+<div align="center">
+ <a href="https://gfycat.com/importantdevotedhammerheadbird">
+ <img src="https://thumbs.gfycat.com/ImportantDevotedHammerheadbird-small.gif">
+ </a>
+</div>
+
+You can run it with `cargo run`:
+```
+cargo run --package loading_spinners
+```
diff --git a/examples/loading_spinners/src/circular.rs b/examples/loading_spinners/src/circular.rs
new file mode 100644
index 00000000..3a35e029
--- /dev/null
+++ b/examples/loading_spinners/src/circular.rs
@@ -0,0 +1,421 @@
+//! Show a circular progress indicator.
+use iced::advanced::layout;
+use iced::advanced::renderer;
+use iced::advanced::widget::tree::{self, Tree};
+use iced::advanced::{Clipboard, Layout, Renderer, Shell, Widget};
+use iced::event;
+use iced::mouse;
+use iced::time::Instant;
+use iced::widget::canvas;
+use iced::window::{self, RedrawRequest};
+use iced::{
+ Background, Color, Element, Event, Length, Rectangle, Size, Vector,
+};
+
+use super::easing::{self, Easing};
+
+use std::f32::consts::PI;
+use std::time::Duration;
+
+const MIN_RADIANS: f32 = PI / 8.0;
+const WRAP_RADIANS: f32 = 2.0 * PI - PI / 4.0;
+const BASE_ROTATION_SPEED: u32 = u32::MAX / 80;
+
+#[allow(missing_debug_implementations)]
+pub struct Circular<'a, Theme>
+where
+ Theme: StyleSheet,
+{
+ size: f32,
+ bar_height: f32,
+ style: <Theme as StyleSheet>::Style,
+ easing: &'a Easing,
+ cycle_duration: Duration,
+ rotation_duration: Duration,
+}
+
+impl<'a, Theme> Circular<'a, Theme>
+where
+ Theme: StyleSheet,
+{
+ /// Creates a new [`Circular`] with the given content.
+ pub fn new() -> Self {
+ Circular {
+ size: 40.0,
+ bar_height: 4.0,
+ style: <Theme as StyleSheet>::Style::default(),
+ easing: &easing::STANDARD,
+ cycle_duration: Duration::from_millis(600),
+ rotation_duration: Duration::from_secs(2),
+ }
+ }
+
+ /// Sets the size of the [`Circular`].
+ pub fn size(mut self, size: f32) -> Self {
+ self.size = size;
+ self
+ }
+
+ /// Sets the bar height of the [`Circular`].
+ pub fn bar_height(mut self, bar_height: f32) -> Self {
+ self.bar_height = bar_height;
+ self
+ }
+
+ /// Sets the style variant of this [`Circular`].
+ pub fn style(mut self, style: <Theme as StyleSheet>::Style) -> Self {
+ self.style = style;
+ self
+ }
+
+ /// Sets the easing of this [`Circular`].
+ pub fn easing(mut self, easing: &'a Easing) -> Self {
+ self.easing = easing;
+ self
+ }
+
+ /// Sets the cycle duration of this [`Circular`].
+ pub fn cycle_duration(mut self, duration: Duration) -> Self {
+ self.cycle_duration = duration / 2;
+ self
+ }
+
+ /// Sets the base rotation duration of this [`Circular`]. This is the duration that a full
+ /// rotation would take if the cycle rotation were set to 0.0 (no expanding or contracting)
+ pub fn rotation_duration(mut self, duration: Duration) -> Self {
+ self.rotation_duration = duration;
+ self
+ }
+}
+
+impl<'a, Theme> Default for Circular<'a, Theme>
+where
+ Theme: StyleSheet,
+{
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+#[derive(Clone, Copy)]
+enum Animation {
+ Expanding {
+ start: Instant,
+ progress: f32,
+ rotation: u32,
+ last: Instant,
+ },
+ Contracting {
+ start: Instant,
+ progress: f32,
+ rotation: u32,
+ last: Instant,
+ },
+}
+
+impl Default for Animation {
+ fn default() -> Self {
+ Self::Expanding {
+ start: Instant::now(),
+ progress: 0.0,
+ rotation: 0,
+ last: Instant::now(),
+ }
+ }
+}
+
+impl Animation {
+ fn next(&self, additional_rotation: u32, now: Instant) -> Self {
+ match self {
+ Self::Expanding { rotation, .. } => Self::Contracting {
+ start: now,
+ progress: 0.0,
+ rotation: rotation.wrapping_add(additional_rotation),
+ last: now,
+ },
+ Self::Contracting { rotation, .. } => Self::Expanding {
+ start: now,
+ progress: 0.0,
+ rotation: rotation.wrapping_add(
+ BASE_ROTATION_SPEED.wrapping_add(
+ ((WRAP_RADIANS / (2.0 * PI)) * u32::MAX as f32) as u32,
+ ),
+ ),
+ last: now,
+ },
+ }
+ }
+
+ fn start(&self) -> Instant {
+ match self {
+ Self::Expanding { start, .. } | Self::Contracting { start, .. } => {
+ *start
+ }
+ }
+ }
+
+ fn last(&self) -> Instant {
+ match self {
+ Self::Expanding { last, .. } | Self::Contracting { last, .. } => {
+ *last
+ }
+ }
+ }
+
+ fn timed_transition(
+ &self,
+ cycle_duration: Duration,
+ rotation_duration: Duration,
+ now: Instant,
+ ) -> Self {
+ let elapsed = now.duration_since(self.start());
+ let additional_rotation = ((now - self.last()).as_secs_f32()
+ / rotation_duration.as_secs_f32()
+ * (u32::MAX) as f32) as u32;
+
+ match elapsed {
+ elapsed if elapsed > cycle_duration => {
+ self.next(additional_rotation, now)
+ }
+ _ => self.with_elapsed(
+ cycle_duration,
+ additional_rotation,
+ elapsed,
+ now,
+ ),
+ }
+ }
+
+ fn with_elapsed(
+ &self,
+ cycle_duration: Duration,
+ additional_rotation: u32,
+ elapsed: Duration,
+ now: Instant,
+ ) -> Self {
+ let progress = elapsed.as_secs_f32() / cycle_duration.as_secs_f32();
+ match self {
+ Self::Expanding {
+ start, rotation, ..
+ } => Self::Expanding {
+ start: *start,
+ progress,
+ rotation: rotation.wrapping_add(additional_rotation),
+ last: now,
+ },
+ Self::Contracting {
+ start, rotation, ..
+ } => Self::Contracting {
+ start: *start,
+ progress,
+ rotation: rotation.wrapping_add(additional_rotation),
+ last: now,
+ },
+ }
+ }
+
+ fn rotation(&self) -> f32 {
+ match self {
+ Self::Expanding { rotation, .. }
+ | Self::Contracting { rotation, .. } => {
+ *rotation as f32 / u32::MAX as f32
+ }
+ }
+ }
+}
+
+#[derive(Default)]
+struct State {
+ animation: Animation,
+ cache: canvas::Cache,
+}
+
+impl<'a, Message, Theme> Widget<Message, iced::Renderer<Theme>>
+ for Circular<'a, Theme>
+where
+ Message: 'a + Clone,
+ Theme: StyleSheet,
+{
+ fn tag(&self) -> tree::Tag {
+ tree::Tag::of::<State>()
+ }
+
+ fn state(&self) -> tree::State {
+ tree::State::new(State::default())
+ }
+
+ fn width(&self) -> Length {
+ Length::Fixed(self.size)
+ }
+
+ fn height(&self) -> Length {
+ Length::Fixed(self.size)
+ }
+
+ fn layout(
+ &self,
+ _renderer: &iced::Renderer<Theme>,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ let limits = limits.width(self.size).height(self.size);
+ let size = limits.resolve(Size::ZERO);
+
+ layout::Node::new(size)
+ }
+
+ fn on_event(
+ &mut self,
+ tree: &mut Tree,
+ event: Event,
+ _layout: Layout<'_>,
+ _cursor: mouse::Cursor,
+ _renderer: &iced::Renderer<Theme>,
+ _clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> event::Status {
+ const FRAME_RATE: u64 = 60;
+
+ let state = tree.state.downcast_mut::<State>();
+
+ if let Event::Window(window::Event::RedrawRequested(now)) = event {
+ state.animation = state.animation.timed_transition(
+ self.cycle_duration,
+ self.rotation_duration,
+ now,
+ );
+
+ state.cache.clear();
+ shell.request_redraw(RedrawRequest::At(
+ now + Duration::from_millis(1000 / FRAME_RATE),
+ ));
+ }
+
+ event::Status::Ignored
+ }
+
+ fn draw(
+ &self,
+ tree: &Tree,
+ renderer: &mut iced::Renderer<Theme>,
+ theme: &Theme,
+ _style: &renderer::Style,
+ layout: Layout<'_>,
+ _cursor: mouse::Cursor,
+ _viewport: &Rectangle,
+ ) {
+ let state = tree.state.downcast_ref::<State>();
+ let bounds = layout.bounds();
+ let custom_style =
+ <Theme as StyleSheet>::appearance(theme, &self.style);
+
+ let geometry = state.cache.draw(renderer, bounds.size(), |frame| {
+ let track_radius = frame.width() / 2.0 - self.bar_height;
+ let track_path = canvas::Path::circle(frame.center(), track_radius);
+
+ frame.stroke(
+ &track_path,
+ canvas::Stroke::default()
+ .with_color(custom_style.track_color)
+ .with_width(self.bar_height),
+ );
+
+ let mut builder = canvas::path::Builder::new();
+
+ let start = state.animation.rotation() * 2.0 * PI;
+
+ match state.animation {
+ Animation::Expanding { progress, .. } => {
+ builder.arc(canvas::path::Arc {
+ center: frame.center(),
+ radius: track_radius,
+ start_angle: start,
+ end_angle: start
+ + MIN_RADIANS
+ + WRAP_RADIANS * (self.easing.y_at_x(progress)),
+ });
+ }
+ Animation::Contracting { progress, .. } => {
+ builder.arc(canvas::path::Arc {
+ center: frame.center(),
+ radius: track_radius,
+ start_angle: start
+ + WRAP_RADIANS * (self.easing.y_at_x(progress)),
+ end_angle: start + MIN_RADIANS + WRAP_RADIANS,
+ });
+ }
+ }
+
+ let bar_path = builder.build();
+
+ frame.stroke(
+ &bar_path,
+ canvas::Stroke::default()
+ .with_color(custom_style.bar_color)
+ .with_width(self.bar_height),
+ );
+ });
+
+ renderer.with_translation(
+ Vector::new(bounds.x, bounds.y),
+ |renderer| {
+ use iced::advanced::graphics::geometry::Renderer as _;
+
+ renderer.draw(vec![geometry]);
+ },
+ );
+ }
+}
+
+impl<'a, Message, Theme> From<Circular<'a, Theme>>
+ for Element<'a, Message, iced::Renderer<Theme>>
+where
+ Message: Clone + 'a,
+ Theme: StyleSheet + 'a,
+{
+ fn from(circular: Circular<'a, Theme>) -> Self {
+ Self::new(circular)
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+pub struct Appearance {
+ /// The [`Background`] of the progress indicator.
+ pub background: Option<Background>,
+ /// The track [`Color`] of the progress indicator.
+ pub track_color: Color,
+ /// The bar [`Color`] of the progress indicator.
+ pub bar_color: Color,
+}
+
+impl std::default::Default for Appearance {
+ fn default() -> Self {
+ Self {
+ background: None,
+ track_color: Color::TRANSPARENT,
+ bar_color: Color::BLACK,
+ }
+ }
+}
+
+/// A set of rules that dictate the style of an indicator.
+pub trait StyleSheet {
+ /// The supported style of the [`StyleSheet`].
+ type Style: Default;
+
+ /// Produces the active [`Appearance`] of a indicator.
+ fn appearance(&self, style: &Self::Style) -> Appearance;
+}
+
+impl StyleSheet for iced::Theme {
+ type Style = ();
+
+ fn appearance(&self, _style: &Self::Style) -> Appearance {
+ let palette = self.extended_palette();
+
+ Appearance {
+ background: None,
+ track_color: palette.background.weak.color,
+ bar_color: palette.primary.base.color,
+ }
+ }
+}
diff --git a/examples/loading_spinners/src/easing.rs b/examples/loading_spinners/src/easing.rs
new file mode 100644
index 00000000..665b3329
--- /dev/null
+++ b/examples/loading_spinners/src/easing.rs
@@ -0,0 +1,133 @@
+use iced::Point;
+
+use lyon_algorithms::measure::PathMeasurements;
+use lyon_algorithms::path::{builder::NoAttributes, path::BuilderImpl, Path};
+use once_cell::sync::Lazy;
+
+pub static EMPHASIZED: Lazy<Easing> = Lazy::new(|| {
+ Easing::builder()
+ .cubic_bezier_to([0.05, 0.0], [0.133333, 0.06], [0.166666, 0.4])
+ .cubic_bezier_to([0.208333, 0.82], [0.25, 1.0], [1.0, 1.0])
+ .build()
+});
+
+pub static EMPHASIZED_DECELERATE: Lazy<Easing> = Lazy::new(|| {
+ Easing::builder()
+ .cubic_bezier_to([0.05, 0.7], [0.1, 1.0], [1.0, 1.0])
+ .build()
+});
+
+pub static EMPHASIZED_ACCELERATE: Lazy<Easing> = Lazy::new(|| {
+ Easing::builder()
+ .cubic_bezier_to([0.3, 0.0], [0.8, 0.15], [1.0, 1.0])
+ .build()
+});
+
+pub static STANDARD: Lazy<Easing> = Lazy::new(|| {
+ Easing::builder()
+ .cubic_bezier_to([0.2, 0.0], [0.0, 1.0], [1.0, 1.0])
+ .build()
+});
+
+pub static STANDARD_DECELERATE: Lazy<Easing> = Lazy::new(|| {
+ Easing::builder()
+ .cubic_bezier_to([0.0, 0.0], [0.0, 1.0], [1.0, 1.0])
+ .build()
+});
+
+pub static STANDARD_ACCELERATE: Lazy<Easing> = Lazy::new(|| {
+ Easing::builder()
+ .cubic_bezier_to([0.3, 0.0], [1.0, 1.0], [1.0, 1.0])
+ .build()
+});
+
+pub struct Easing {
+ path: Path,
+ measurements: PathMeasurements,
+}
+
+impl Easing {
+ pub fn builder() -> Builder {
+ Builder::new()
+ }
+
+ pub fn y_at_x(&self, x: f32) -> f32 {
+ let mut sampler = self.measurements.create_sampler(
+ &self.path,
+ lyon_algorithms::measure::SampleType::Normalized,
+ );
+ let sample = sampler.sample(x);
+
+ sample.position().y
+ }
+}
+
+pub struct Builder(NoAttributes<BuilderImpl>);
+
+impl Builder {
+ pub fn new() -> Self {
+ let mut builder = Path::builder();
+ builder.begin(lyon_algorithms::geom::point(0.0, 0.0));
+
+ Self(builder)
+ }
+
+ /// Adds a line segment. Points must be between 0,0 and 1,1
+ pub fn line_to(mut self, to: impl Into<Point>) -> Self {
+ self.0.line_to(Self::point(to));
+
+ self
+ }
+
+ /// Adds a quadratic bézier curve. Points must be between 0,0 and 1,1
+ pub fn quadratic_bezier_to(
+ mut self,
+ ctrl: impl Into<Point>,
+ to: impl Into<Point>,
+ ) -> Self {
+ self.0
+ .quadratic_bezier_to(Self::point(ctrl), Self::point(to));
+
+ self
+ }
+
+ /// Adds a cubic bézier curve. Points must be between 0,0 and 1,1
+ pub fn cubic_bezier_to(
+ mut self,
+ ctrl1: impl Into<Point>,
+ ctrl2: impl Into<Point>,
+ to: impl Into<Point>,
+ ) -> Self {
+ self.0.cubic_bezier_to(
+ Self::point(ctrl1),
+ Self::point(ctrl2),
+ Self::point(to),
+ );
+
+ self
+ }
+
+ pub fn build(mut self) -> Easing {
+ self.0.line_to(lyon_algorithms::geom::point(1.0, 1.0));
+ self.0.end(false);
+
+ let path = self.0.build();
+ let measurements = PathMeasurements::from_path(&path, 0.0);
+
+ Easing { path, measurements }
+ }
+
+ fn point(p: impl Into<Point>) -> lyon_algorithms::geom::Point<f32> {
+ let p: Point = p.into();
+ lyon_algorithms::geom::point(
+ p.x.min(1.0).max(0.0),
+ p.y.min(1.0).max(0.0),
+ )
+ }
+}
+
+impl Default for Builder {
+ fn default() -> Self {
+ Self::new()
+ }
+}
diff --git a/examples/loading_spinners/src/linear.rs b/examples/loading_spinners/src/linear.rs
new file mode 100644
index 00000000..3d95729b
--- /dev/null
+++ b/examples/loading_spinners/src/linear.rs
@@ -0,0 +1,326 @@
+//! Show a linear progress indicator.
+use iced::advanced::layout;
+use iced::advanced::renderer::{self, Quad};
+use iced::advanced::widget::tree::{self, Tree};
+use iced::advanced::{Clipboard, Layout, Shell, Widget};
+use iced::event;
+use iced::mouse;
+use iced::time::Instant;
+use iced::window::{self, RedrawRequest};
+use iced::{Background, Color, Element, Event, Length, Rectangle, Size};
+
+use super::easing::{self, Easing};
+
+use std::time::Duration;
+
+#[allow(missing_debug_implementations)]
+pub struct Linear<'a, Renderer>
+where
+ Renderer: iced::advanced::Renderer,
+ Renderer::Theme: StyleSheet,
+{
+ width: Length,
+ height: Length,
+ style: <Renderer::Theme as StyleSheet>::Style,
+ easing: &'a Easing,
+ cycle_duration: Duration,
+}
+
+impl<'a, Renderer> Linear<'a, Renderer>
+where
+ Renderer: iced::advanced::Renderer,
+ Renderer::Theme: StyleSheet,
+{
+ /// Creates a new [`Linear`] with the given content.
+ pub fn new() -> Self {
+ Linear {
+ width: Length::Fixed(100.0),
+ height: Length::Fixed(4.0),
+ style: <Renderer::Theme as StyleSheet>::Style::default(),
+ easing: &easing::STANDARD,
+ cycle_duration: Duration::from_millis(600),
+ }
+ }
+
+ /// Sets the width of the [`Linear`].
+ pub fn width(mut self, width: impl Into<Length>) -> Self {
+ self.width = width.into();
+ self
+ }
+
+ /// Sets the height of the [`Linear`].
+ pub fn height(mut self, height: impl Into<Length>) -> Self {
+ self.height = height.into();
+ self
+ }
+
+ /// Sets the style variant of this [`Linear`].
+ pub fn style(
+ mut self,
+ style: <Renderer::Theme as StyleSheet>::Style,
+ ) -> Self {
+ self.style = style;
+ self
+ }
+
+ /// Sets the motion easing of this [`Linear`].
+ pub fn easing(mut self, easing: &'a Easing) -> Self {
+ self.easing = easing;
+ self
+ }
+
+ /// Sets the cycle duration of this [`Linear`].
+ pub fn cycle_duration(mut self, duration: Duration) -> Self {
+ self.cycle_duration = duration / 2;
+ self
+ }
+}
+
+impl<'a, Renderer> Default for Linear<'a, Renderer>
+where
+ Renderer: iced::advanced::Renderer,
+ Renderer::Theme: StyleSheet,
+{
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+#[derive(Clone, Copy)]
+enum State {
+ Expanding { start: Instant, progress: f32 },
+ Contracting { start: Instant, progress: f32 },
+}
+
+impl Default for State {
+ fn default() -> Self {
+ Self::Expanding {
+ start: Instant::now(),
+ progress: 0.0,
+ }
+ }
+}
+
+impl State {
+ fn next(&self, now: Instant) -> Self {
+ match self {
+ Self::Expanding { .. } => Self::Contracting {
+ start: now,
+ progress: 0.0,
+ },
+ Self::Contracting { .. } => Self::Expanding {
+ start: now,
+ progress: 0.0,
+ },
+ }
+ }
+
+ fn start(&self) -> Instant {
+ match self {
+ Self::Expanding { start, .. } | Self::Contracting { start, .. } => {
+ *start
+ }
+ }
+ }
+
+ fn timed_transition(&self, cycle_duration: Duration, now: Instant) -> Self {
+ let elapsed = now.duration_since(self.start());
+
+ match elapsed {
+ elapsed if elapsed > cycle_duration => self.next(now),
+ _ => self.with_elapsed(cycle_duration, elapsed),
+ }
+ }
+
+ fn with_elapsed(
+ &self,
+ cycle_duration: Duration,
+ elapsed: Duration,
+ ) -> Self {
+ let progress = elapsed.as_secs_f32() / cycle_duration.as_secs_f32();
+ match self {
+ Self::Expanding { start, .. } => Self::Expanding {
+ start: *start,
+ progress,
+ },
+ Self::Contracting { start, .. } => Self::Contracting {
+ start: *start,
+ progress,
+ },
+ }
+ }
+}
+
+impl<'a, Message, Renderer> Widget<Message, Renderer> for Linear<'a, Renderer>
+where
+ Message: 'a + Clone,
+ Renderer: 'a + iced::advanced::Renderer,
+ Renderer::Theme: StyleSheet,
+{
+ fn tag(&self) -> tree::Tag {
+ tree::Tag::of::<State>()
+ }
+
+ fn state(&self) -> tree::State {
+ tree::State::new(State::default())
+ }
+
+ fn width(&self) -> Length {
+ self.width
+ }
+
+ fn height(&self) -> Length {
+ self.height
+ }
+
+ fn layout(
+ &self,
+ _renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ let limits = limits.width(self.width).height(self.height);
+ let size = limits.resolve(Size::ZERO);
+
+ layout::Node::new(size)
+ }
+
+ fn on_event(
+ &mut self,
+ tree: &mut Tree,
+ event: Event,
+ _layout: Layout<'_>,
+ _cursor: mouse::Cursor,
+ _renderer: &Renderer,
+ _clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> event::Status {
+ const FRAME_RATE: u64 = 60;
+
+ let state = tree.state.downcast_mut::<State>();
+
+ if let Event::Window(window::Event::RedrawRequested(now)) = event {
+ *state = state.timed_transition(self.cycle_duration, now);
+
+ shell.request_redraw(RedrawRequest::At(
+ now + Duration::from_millis(1000 / FRAME_RATE),
+ ));
+ }
+
+ event::Status::Ignored
+ }
+
+ fn draw(
+ &self,
+ tree: &Tree,
+ renderer: &mut Renderer,
+ theme: &Renderer::Theme,
+ _style: &renderer::Style,
+ layout: Layout<'_>,
+ _cursor: mouse::Cursor,
+ _viewport: &Rectangle,
+ ) {
+ let bounds = layout.bounds();
+ let custom_style = theme.appearance(&self.style);
+ let state = tree.state.downcast_ref::<State>();
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: Rectangle {
+ x: bounds.x,
+ y: bounds.y,
+ width: bounds.width,
+ height: bounds.height,
+ },
+ border_radius: 0.0.into(),
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ Background::Color(custom_style.track_color),
+ );
+
+ match state {
+ State::Expanding { progress, .. } => renderer.fill_quad(
+ renderer::Quad {
+ bounds: Rectangle {
+ x: bounds.x,
+ y: bounds.y,
+ width: self.easing.y_at_x(*progress) * bounds.width,
+ height: bounds.height,
+ },
+ border_radius: 0.0.into(),
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ Background::Color(custom_style.bar_color),
+ ),
+
+ State::Contracting { progress, .. } => renderer.fill_quad(
+ Quad {
+ bounds: Rectangle {
+ x: bounds.x
+ + self.easing.y_at_x(*progress) * bounds.width,
+ y: bounds.y,
+ width: (1.0 - self.easing.y_at_x(*progress))
+ * bounds.width,
+ height: bounds.height,
+ },
+ border_radius: 0.0.into(),
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ Background::Color(custom_style.bar_color),
+ ),
+ }
+ }
+}
+
+impl<'a, Message, Renderer> From<Linear<'a, Renderer>>
+ for Element<'a, Message, Renderer>
+where
+ Message: Clone + 'a,
+ Renderer: iced::advanced::Renderer + 'a,
+ Renderer::Theme: StyleSheet,
+{
+ fn from(linear: Linear<'a, Renderer>) -> Self {
+ Self::new(linear)
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+pub struct Appearance {
+ /// The track [`Color`] of the progress indicator.
+ pub track_color: Color,
+ /// The bar [`Color`] of the progress indicator.
+ pub bar_color: Color,
+}
+
+impl std::default::Default for Appearance {
+ fn default() -> Self {
+ Self {
+ track_color: Color::TRANSPARENT,
+ bar_color: Color::BLACK,
+ }
+ }
+}
+
+/// A set of rules that dictate the style of an indicator.
+pub trait StyleSheet {
+ /// The supported style of the [`StyleSheet`].
+ type Style: Default;
+
+ /// Produces the active [`Appearance`] of a indicator.
+ fn appearance(&self, style: &Self::Style) -> Appearance;
+}
+
+impl StyleSheet for iced::Theme {
+ type Style = ();
+
+ fn appearance(&self, _style: &Self::Style) -> Appearance {
+ let palette = self.extended_palette();
+
+ Appearance {
+ track_color: palette.background.weak.color,
+ bar_color: palette.primary.base.color,
+ }
+ }
+}
diff --git a/examples/loading_spinners/src/main.rs b/examples/loading_spinners/src/main.rs
new file mode 100644
index 00000000..a78e9590
--- /dev/null
+++ b/examples/loading_spinners/src/main.rs
@@ -0,0 +1,118 @@
+use iced::executor;
+use iced::widget::{column, container, row, slider, text};
+use iced::{Application, Command, Element, Length, Settings, Theme};
+
+use std::time::Duration;
+
+mod circular;
+mod easing;
+mod linear;
+
+use circular::Circular;
+use linear::Linear;
+
+pub fn main() -> iced::Result {
+ LoadingSpinners::run(Settings {
+ antialiasing: true,
+ ..Default::default()
+ })
+}
+
+struct LoadingSpinners {
+ cycle_duration: f32,
+}
+
+impl Default for LoadingSpinners {
+ fn default() -> Self {
+ Self {
+ cycle_duration: 2.0,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+enum Message {
+ CycleDurationChanged(f32),
+}
+
+impl Application for LoadingSpinners {
+ type Message = Message;
+ type Flags = ();
+ type Executor = executor::Default;
+ type Theme = Theme;
+
+ fn new(_flags: Self::Flags) -> (Self, Command<Message>) {
+ (Self::default(), Command::none())
+ }
+
+ fn title(&self) -> String {
+ String::from("Loading Spinners - Iced")
+ }
+
+ fn update(&mut self, message: Message) -> Command<Message> {
+ match message {
+ Message::CycleDurationChanged(duration) => {
+ self.cycle_duration = duration;
+ }
+ }
+
+ Command::none()
+ }
+
+ fn view(&self) -> Element<Message> {
+ let column = [
+ &easing::EMPHASIZED,
+ &easing::EMPHASIZED_DECELERATE,
+ &easing::EMPHASIZED_ACCELERATE,
+ &easing::STANDARD,
+ &easing::STANDARD_DECELERATE,
+ &easing::STANDARD_ACCELERATE,
+ ]
+ .iter()
+ .zip([
+ "Emphasized:",
+ "Emphasized Decelerate:",
+ "Emphasized Accelerate:",
+ "Standard:",
+ "Standard Decelerate:",
+ "Standard Accelerate:",
+ ])
+ .fold(column![], |column, (easing, label)| {
+ column.push(
+ row![
+ text(label).width(250),
+ Linear::new().easing(easing).cycle_duration(
+ Duration::from_secs_f32(self.cycle_duration)
+ ),
+ Circular::new().easing(easing).cycle_duration(
+ Duration::from_secs_f32(self.cycle_duration)
+ )
+ ]
+ .align_items(iced::Alignment::Center)
+ .spacing(20.0),
+ )
+ })
+ .spacing(20);
+
+ container(
+ column.push(
+ row(vec![
+ text("Cycle duration:").into(),
+ slider(1.0..=1000.0, self.cycle_duration * 100.0, |x| {
+ Message::CycleDurationChanged(x / 100.0)
+ })
+ .width(200.0)
+ .into(),
+ text(format!("{:.2}s", self.cycle_duration)).into(),
+ ])
+ .align_items(iced::Alignment::Center)
+ .spacing(20.0),
+ ),
+ )
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .center_x()
+ .center_y()
+ .into()
+ }
+}
diff --git a/examples/modal/src/main.rs b/examples/modal/src/main.rs
index 5c43c203..7fcbbfe4 100644
--- a/examples/modal/src/main.rs
+++ b/examples/modal/src/main.rs
@@ -3,11 +3,13 @@ use iced::keyboard;
use iced::subscription::{self, Subscription};
use iced::theme;
use iced::widget::{
- self, button, column, container, horizontal_space, row, text, text_input,
+ self, button, column, container, horizontal_space, pick_list, row, text,
+ text_input,
};
use iced::{Alignment, Application, Command, Element, Event, Length, Settings};
-use self::modal::Modal;
+use modal::Modal;
+use std::fmt;
pub fn main() -> iced::Result {
App::run(Settings::default())
@@ -18,6 +20,7 @@ struct App {
show_modal: bool,
email: String,
password: String,
+ plan: Plan,
}
#[derive(Debug, Clone)]
@@ -26,6 +29,7 @@ enum Message {
HideModal,
Email(String),
Password(String),
+ Plan(Plan),
Submit,
Event(Event),
}
@@ -66,6 +70,10 @@ impl Application for App {
self.password = password;
Command::none()
}
+ Message::Plan(plan) => {
+ self.plan = plan;
+ Command::none()
+ }
Message::Submit => {
if !self.email.is_empty() && !self.password.is_empty() {
self.hide_modal();
@@ -149,6 +157,16 @@ impl Application for App {
.padding(5),
]
.spacing(5),
+ column![
+ text("Plan").size(12),
+ pick_list(
+ Plan::ALL,
+ Some(self.plan),
+ Message::Plan
+ )
+ .padding(5),
+ ]
+ .spacing(5),
button(text("Submit")).on_press(Message::HideModal),
]
.spacing(10)
@@ -176,6 +194,29 @@ impl App {
}
}
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+enum Plan {
+ #[default]
+ Basic,
+ Pro,
+ Enterprise,
+}
+
+impl Plan {
+ pub const ALL: &[Self] = &[Self::Basic, Self::Pro, Self::Enterprise];
+}
+
+impl fmt::Display for Plan {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Plan::Basic => "Basic",
+ Plan::Pro => "Pro",
+ Plan::Enterprise => "Enterprise",
+ }
+ .fmt(f)
+ }
+}
+
mod modal {
use iced::advanced::layout::{self, Layout};
use iced::advanced::overlay;
@@ -469,6 +510,18 @@ mod modal {
renderer,
)
}
+
+ fn overlay<'c>(
+ &'c mut self,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ ) -> Option<overlay::Element<'c, Message, Renderer>> {
+ self.content.as_widget_mut().overlay(
+ self.tree,
+ layout.children().next().unwrap(),
+ renderer,
+ )
+ }
}
impl<'a, Message, Renderer> From<Modal<'a, Message, Renderer>>
diff --git a/examples/screenshot/Cargo.toml b/examples/screenshot/Cargo.toml
new file mode 100644
index 00000000..b79300b7
--- /dev/null
+++ b/examples/screenshot/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "screenshot"
+version = "0.1.0"
+authors = ["Bingus <shankern@protonmail.com>"]
+edition = "2021"
+publish = false
+
+[dependencies]
+iced = { path = "../..", features = ["debug", "image", "advanced"] }
+image = { version = "0.24.6", features = ["png"]}
+env_logger = "0.10.0"
diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs
new file mode 100644
index 00000000..83824535
--- /dev/null
+++ b/examples/screenshot/src/main.rs
@@ -0,0 +1,320 @@
+use iced::alignment;
+use iced::keyboard::KeyCode;
+use iced::theme::{Button, Container};
+use iced::widget::{button, column, container, image, row, text, text_input};
+use iced::window::screenshot::{self, Screenshot};
+use iced::{
+ event, executor, keyboard, subscription, Alignment, Application, Command,
+ ContentFit, Element, Event, Length, Rectangle, Renderer, Subscription,
+ Theme,
+};
+
+use ::image as img;
+use ::image::ColorType;
+
+fn main() -> iced::Result {
+ env_logger::builder().format_timestamp(None).init();
+
+ Example::run(iced::Settings::default())
+}
+
+struct Example {
+ screenshot: Option<Screenshot>,
+ saved_png_path: Option<Result<String, PngError>>,
+ png_saving: bool,
+ crop_error: Option<screenshot::CropError>,
+ x_input_value: Option<u32>,
+ y_input_value: Option<u32>,
+ width_input_value: Option<u32>,
+ height_input_value: Option<u32>,
+}
+
+#[derive(Clone, Debug)]
+enum Message {
+ Crop,
+ Screenshot,
+ ScreenshotData(Screenshot),
+ Png,
+ PngSaved(Result<String, PngError>),
+ XInputChanged(Option<u32>),
+ YInputChanged(Option<u32>),
+ WidthInputChanged(Option<u32>),
+ HeightInputChanged(Option<u32>),
+}
+
+impl Application for Example {
+ type Executor = executor::Default;
+ type Message = Message;
+ type Theme = Theme;
+ type Flags = ();
+
+ fn new(_flags: Self::Flags) -> (Self, Command<Self::Message>) {
+ (
+ Example {
+ screenshot: None,
+ saved_png_path: None,
+ png_saving: false,
+ crop_error: None,
+ x_input_value: None,
+ y_input_value: None,
+ width_input_value: None,
+ height_input_value: None,
+ },
+ Command::none(),
+ )
+ }
+
+ fn title(&self) -> String {
+ "Screenshot".to_string()
+ }
+
+ fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
+ match message {
+ Message::Screenshot => {
+ return iced::window::screenshot(Message::ScreenshotData);
+ }
+ Message::ScreenshotData(screenshot) => {
+ self.screenshot = Some(screenshot);
+ }
+ Message::Png => {
+ if let Some(screenshot) = &self.screenshot {
+ self.png_saving = true;
+
+ return Command::perform(
+ save_to_png(screenshot.clone()),
+ Message::PngSaved,
+ );
+ }
+ }
+ Message::PngSaved(res) => {
+ self.png_saving = false;
+ self.saved_png_path = Some(res);
+ }
+ Message::XInputChanged(new_value) => {
+ self.x_input_value = new_value;
+ }
+ Message::YInputChanged(new_value) => {
+ self.y_input_value = new_value;
+ }
+ Message::WidthInputChanged(new_value) => {
+ self.width_input_value = new_value;
+ }
+ Message::HeightInputChanged(new_value) => {
+ self.height_input_value = new_value;
+ }
+ Message::Crop => {
+ if let Some(screenshot) = &self.screenshot {
+ let cropped = screenshot.crop(Rectangle::<u32> {
+ x: self.x_input_value.unwrap_or(0),
+ y: self.y_input_value.unwrap_or(0),
+ width: self.width_input_value.unwrap_or(0),
+ height: self.height_input_value.unwrap_or(0),
+ });
+
+ match cropped {
+ Ok(screenshot) => {
+ self.screenshot = Some(screenshot);
+ self.crop_error = None;
+ }
+ Err(crop_error) => {
+ self.crop_error = Some(crop_error);
+ }
+ }
+ }
+ }
+ }
+
+ Command::none()
+ }
+
+ fn view(&self) -> Element<'_, Self::Message, Renderer<Self::Theme>> {
+ let image: Element<Message> = if let Some(screenshot) = &self.screenshot
+ {
+ image(image::Handle::from_pixels(
+ screenshot.size.width,
+ screenshot.size.height,
+ screenshot.clone(),
+ ))
+ .content_fit(ContentFit::Contain)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .into()
+ } else {
+ text("Press the button to take a screenshot!").into()
+ };
+
+ let image = container(image)
+ .padding(10)
+ .style(Container::Box)
+ .width(Length::FillPortion(2))
+ .height(Length::Fill)
+ .center_x()
+ .center_y();
+
+ let crop_origin_controls = row![
+ text("X:")
+ .vertical_alignment(alignment::Vertical::Center)
+ .width(30),
+ numeric_input("0", self.x_input_value).map(Message::XInputChanged),
+ text("Y:")
+ .vertical_alignment(alignment::Vertical::Center)
+ .width(30),
+ numeric_input("0", self.y_input_value).map(Message::YInputChanged)
+ ]
+ .spacing(10)
+ .align_items(Alignment::Center);
+
+ let crop_dimension_controls = row![
+ text("W:")
+ .vertical_alignment(alignment::Vertical::Center)
+ .width(30),
+ numeric_input("0", self.width_input_value)
+ .map(Message::WidthInputChanged),
+ text("H:")
+ .vertical_alignment(alignment::Vertical::Center)
+ .width(30),
+ numeric_input("0", self.height_input_value)
+ .map(Message::HeightInputChanged)
+ ]
+ .spacing(10)
+ .align_items(Alignment::Center);
+
+ let mut crop_controls =
+ column![crop_origin_controls, crop_dimension_controls]
+ .spacing(10)
+ .align_items(Alignment::Center);
+
+ if let Some(crop_error) = &self.crop_error {
+ crop_controls = crop_controls
+ .push(text(format!("Crop error! \n{}", crop_error)));
+ }
+
+ let mut controls = column![
+ column![
+ button(centered_text("Screenshot!"))
+ .padding([10, 20, 10, 20])
+ .width(Length::Fill)
+ .on_press(Message::Screenshot),
+ if !self.png_saving {
+ button(centered_text("Save as png")).on_press_maybe(
+ self.screenshot.is_some().then(|| Message::Png),
+ )
+ } else {
+ button(centered_text("Saving...")).style(Button::Secondary)
+ }
+ .style(Button::Secondary)
+ .padding([10, 20, 10, 20])
+ .width(Length::Fill)
+ ]
+ .spacing(10),
+ column![
+ crop_controls,
+ button(centered_text("Crop"))
+ .on_press(Message::Crop)
+ .style(Button::Destructive)
+ .padding([10, 20, 10, 20])
+ .width(Length::Fill),
+ ]
+ .spacing(10)
+ .align_items(Alignment::Center),
+ ]
+ .spacing(40);
+
+ if let Some(png_result) = &self.saved_png_path {
+ let msg = match png_result {
+ Ok(path) => format!("Png saved as: {:?}!", path),
+ Err(msg) => {
+ format!("Png could not be saved due to:\n{:?}", msg)
+ }
+ };
+
+ controls = controls.push(text(msg));
+ }
+
+ let side_content = container(controls)
+ .align_x(alignment::Horizontal::Center)
+ .width(Length::FillPortion(1))
+ .height(Length::Fill)
+ .center_y()
+ .center_x();
+
+ let content = row![side_content, image]
+ .spacing(10)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .align_items(Alignment::Center);
+
+ container(content)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .padding(10)
+ .center_x()
+ .center_y()
+ .into()
+ }
+
+ fn subscription(&self) -> Subscription<Self::Message> {
+ subscription::events_with(|event, status| {
+ if let event::Status::Captured = status {
+ return None;
+ }
+
+ if let Event::Keyboard(keyboard::Event::KeyPressed {
+ key_code: KeyCode::F5,
+ ..
+ }) = event
+ {
+ Some(Message::Screenshot)
+ } else {
+ None
+ }
+ })
+ }
+}
+
+async fn save_to_png(screenshot: Screenshot) -> Result<String, PngError> {
+ let path = "screenshot.png".to_string();
+ img::save_buffer(
+ &path,
+ &screenshot.bytes,
+ screenshot.size.width,
+ screenshot.size.height,
+ ColorType::Rgba8,
+ )
+ .map(|_| path)
+ .map_err(|err| PngError(format!("{:?}", err)))
+}
+
+#[derive(Clone, Debug)]
+struct PngError(String);
+
+fn numeric_input(
+ placeholder: &str,
+ value: Option<u32>,
+) -> Element<'_, Option<u32>> {
+ text_input(
+ placeholder,
+ &value
+ .as_ref()
+ .map(ToString::to_string)
+ .unwrap_or_else(String::new),
+ )
+ .on_input(move |text| {
+ if text.is_empty() {
+ None
+ } else if let Ok(new_value) = text.parse() {
+ Some(new_value)
+ } else {
+ value
+ }
+ })
+ .width(40)
+ .into()
+}
+
+fn centered_text(content: &str) -> Element<'_, Message> {
+ text(content)
+ .width(Length::Fill)
+ .horizontal_alignment(alignment::Horizontal::Center)
+ .into()
+}
diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs
index 3038661e..4104871f 100644
--- a/examples/scrollable/src/main.rs
+++ b/examples/scrollable/src/main.rs
@@ -5,6 +5,7 @@ use iced::widget::{
};
use iced::{executor, theme, Alignment, Color};
use iced::{Application, Command, Element, Length, Settings, Theme};
+
use once_cell::sync::Lazy;
static SCROLLABLE_ID: Lazy<scrollable::Id> = Lazy::new(scrollable::Id::unique);
@@ -199,12 +200,12 @@ impl Application for ScrollableDemo {
.spacing(40),
)
.height(Length::Fill)
- .vertical_scroll(
+ .direction(scrollable::Direction::Vertical(
Properties::new()
.width(self.scrollbar_width)
.margin(self.scrollbar_margin)
.scroller_width(self.scroller_width),
- )
+ ))
.id(SCROLLABLE_ID.clone())
.on_scroll(Message::Scrolled),
Direction::Horizontal => scrollable(
@@ -223,12 +224,12 @@ impl Application for ScrollableDemo {
.spacing(40),
)
.height(Length::Fill)
- .horizontal_scroll(
+ .direction(scrollable::Direction::Horizontal(
Properties::new()
.width(self.scrollbar_width)
.margin(self.scrollbar_margin)
.scroller_width(self.scroller_width),
- )
+ ))
.style(theme::Scrollable::custom(ScrollbarCustomStyle))
.id(SCROLLABLE_ID.clone())
.on_scroll(Message::Scrolled),
@@ -264,18 +265,17 @@ impl Application for ScrollableDemo {
.spacing(40),
)
.height(Length::Fill)
- .vertical_scroll(
- Properties::new()
- .width(self.scrollbar_width)
- .margin(self.scrollbar_margin)
- .scroller_width(self.scroller_width),
- )
- .horizontal_scroll(
- Properties::new()
+ .direction({
+ let properties = Properties::new()
.width(self.scrollbar_width)
.margin(self.scrollbar_margin)
- .scroller_width(self.scroller_width),
- )
+ .scroller_width(self.scroller_width);
+
+ scrollable::Direction::Both {
+ horizontal: properties,
+ vertical: properties,
+ }
+ })
.style(theme::Scrollable::Custom(Box::new(
ScrollbarCustomStyle,
)))
diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs
index e2015bac..f8a4c80a 100644
--- a/examples/styling/src/main.rs
+++ b/examples/styling/src/main.rs
@@ -127,7 +127,9 @@ impl Sandbox for Styling {
let content = column![
choose_theme,
horizontal_rule(38),
- row![text_input, button].spacing(10),
+ row![text_input, button]
+ .spacing(10)
+ .align_items(Alignment::Center),
slider,
progress_bar,
row![
diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs
index 395cbc10..4282ddcf 100644
--- a/examples/toast/src/main.rs
+++ b/examples/toast/src/main.rs
@@ -650,7 +650,12 @@ mod toast {
.unwrap_or_default()
}
- fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
+ fn is_over(
+ &self,
+ layout: Layout<'_>,
+ _renderer: &Renderer,
+ cursor_position: Point,
+ ) -> bool {
layout
.children()
.any(|layout| layout.bounds().contains(cursor_position))