summaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--examples/progress_indicators/Cargo.toml13
-rw-r--r--examples/progress_indicators/README.md14
-rw-r--r--examples/progress_indicators/src/circular.rs425
-rw-r--r--examples/progress_indicators/src/easing.rs67
-rw-r--r--examples/progress_indicators/src/linear.rs325
-rw-r--r--examples/progress_indicators/src/main.rs104
6 files changed, 948 insertions, 0 deletions
diff --git a/examples/progress_indicators/Cargo.toml b/examples/progress_indicators/Cargo.toml
new file mode 100644
index 00000000..89ceb127
--- /dev/null
+++ b/examples/progress_indicators/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "progress_indicators"
+version = "0.1.0"
+authors = ["Nick Senger <dev@nsenger.com>"]
+edition = "2021"
+publish = false
+
+[dependencies]
+flo_curves = "0.7"
+iced = { path = "../..", features = ["canvas"] }
+iced_core = { path = "../../core" }
+iced_widget = { path = "../../widget" }
+lazy_static = "1.4"
diff --git a/examples/progress_indicators/README.md b/examples/progress_indicators/README.md
new file mode 100644
index 00000000..acacca8f
--- /dev/null
+++ b/examples/progress_indicators/README.md
@@ -0,0 +1,14 @@
+## Progress indicators
+
+Example implementation of animated indeterminate progress indicators.
+
+<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 progress_indicators
+```
diff --git a/examples/progress_indicators/src/circular.rs b/examples/progress_indicators/src/circular.rs
new file mode 100644
index 00000000..2a9f7e1e
--- /dev/null
+++ b/examples/progress_indicators/src/circular.rs
@@ -0,0 +1,425 @@
+//! Show a circular progress indicator.
+use iced::widget::canvas::{self, Cursor, Program, Renderer as CanvasRenderer};
+use iced::window;
+use iced_core::event::{self, Event};
+use iced_core::time::Instant;
+use iced_core::widget::tree::{self, Tree};
+use iced_core::window::RedrawRequest;
+use iced_core::{layout, Size};
+use iced_core::{renderer, Vector};
+use iced_core::{
+ Background, Clipboard, Color, Element, Layout, Length, Point, Rectangle,
+ Renderer, Shell, Widget,
+};
+
+use super::easing::{self, Easing};
+
+use std::borrow::Cow;
+use std::f32::consts::PI;
+use std::time::Duration;
+
+type R<Theme> = iced_widget::renderer::Renderer<Theme>;
+
+const MIN_RADIANS: f32 = PI / 8.0;
+const WRAP_RADIANS: f32 = 2.0 * PI - PI / 4.0;
+const PROCESSION_VELOCITY: u32 = u32::MAX / 120;
+
+#[allow(missing_debug_implementations)]
+pub struct Circular<'a, Theme>
+where
+ Theme: StyleSheet,
+{
+ size: f32,
+ bar_height: f32,
+ style: <Theme as StyleSheet>::Style,
+ easing: Cow<'a, Easing>,
+ cycle_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: Cow::Borrowed(&easing::STANDARD),
+ cycle_duration: Duration::from_millis(600),
+ }
+ }
+
+ /// 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: impl Into<Cow<'a, Easing>>) -> Self {
+ self.easing = easing.into();
+ self
+ }
+
+ /// Sets the cycle duration of this [`Circular`].
+ pub fn cycle_duration(mut self, duration: Duration) -> Self {
+ self.cycle_duration = duration / 2;
+ self
+ }
+}
+
+impl<'a, Theme> Default for Circular<'a, Theme>
+where
+ Theme: StyleSheet,
+{
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+#[derive(Clone, Copy)]
+enum State {
+ Expanding {
+ start: Instant,
+ progress: f32,
+ procession: u32,
+ },
+ Contracting {
+ start: Instant,
+ progress: f32,
+ procession: u32,
+ },
+}
+
+impl Default for State {
+ fn default() -> Self {
+ Self::Expanding {
+ start: Instant::now(),
+ progress: 0.0,
+ procession: 0,
+ }
+ }
+}
+
+impl State {
+ fn next(&self, now: Instant) -> Self {
+ match self {
+ Self::Expanding { procession, .. } => Self::Contracting {
+ start: now,
+ progress: 0.0,
+ procession: procession.wrapping_add(PROCESSION_VELOCITY),
+ },
+ Self::Contracting { procession, .. } => Self::Expanding {
+ start: now,
+ progress: 0.0,
+ procession: procession.wrapping_add(
+ PROCESSION_VELOCITY.wrapping_add(
+ ((WRAP_RADIANS / (2.0 * PI)) * u32::MAX as f32) as u32,
+ ),
+ ),
+ },
+ }
+ }
+
+ 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, procession, ..
+ } => Self::Expanding {
+ start: *start,
+ progress,
+ procession: procession.wrapping_add(PROCESSION_VELOCITY),
+ },
+ Self::Contracting {
+ start, procession, ..
+ } => Self::Contracting {
+ start: *start,
+ progress,
+ procession: procession.wrapping_add(PROCESSION_VELOCITY),
+ },
+ }
+ }
+
+ fn procession(&self) -> f32 {
+ match self {
+ Self::Expanding { procession, .. }
+ | Self::Contracting { procession, .. } => {
+ *procession as f32 / u32::MAX as f32
+ }
+ }
+ }
+}
+
+impl<'a, Message, Theme> Widget<Message, R<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_widget::renderer::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_position: Point,
+ _renderer: &iced_widget::renderer::Renderer<Theme>,
+ _clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> event::Status {
+ 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::NextFrame);
+ }
+
+ event::Status::Ignored
+ }
+
+ fn draw(
+ &self,
+ tree: &Tree,
+ renderer: &mut R<Theme>,
+ theme: &Theme,
+ _style: &renderer::Style,
+ layout: Layout<'_>,
+ _cursor_position: Point,
+ _viewport: &Rectangle,
+ ) {
+ let bounds = layout.bounds();
+ let state = tree.state.downcast_ref::<State>();
+
+ renderer.with_translation(
+ Vector::new(bounds.x, bounds.y),
+ |renderer| {
+ renderer.draw(<StateWithStyle<Theme> as Program<
+ Message,
+ R<Theme>,
+ >>::draw(
+ &StateWithStyle {
+ state,
+ style: &self.style,
+ bar_height: self.bar_height,
+ easing: self.easing.as_ref(),
+ },
+ &(),
+ renderer,
+ theme,
+ bounds,
+ Cursor::Unavailable,
+ ));
+ },
+ );
+ }
+}
+
+impl<'a, Message, Theme> From<Circular<'a, Theme>>
+ for Element<'a, Message, R<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,
+ }
+ }
+}
+
+struct StateWithStyle<'a, Theme>
+where
+ Theme: StyleSheet,
+{
+ state: &'a State,
+ style: &'a <Theme as StyleSheet>::Style,
+ easing: &'a Easing,
+ bar_height: f32,
+}
+
+impl<'a, Message, Theme>
+ canvas::Program<Message, iced_widget::renderer::Renderer<Theme>>
+ for StateWithStyle<'a, Theme>
+where
+ Theme: StyleSheet,
+{
+ type State = ();
+
+ fn update(
+ &self,
+ _state: &mut Self::State,
+ _event: canvas::Event,
+ _bounds: Rectangle,
+ _cursor: canvas::Cursor,
+ ) -> (canvas::event::Status, Option<Message>) {
+ (canvas::event::Status::Ignored, None)
+ }
+
+ fn draw(
+ &self,
+ _state: &Self::State,
+ renderer: &iced_widget::renderer::Renderer<Theme>,
+ theme: &Theme,
+ bounds: Rectangle,
+ _cursor: canvas::Cursor,
+ ) -> Vec<canvas::Geometry> {
+ let mut frame = canvas::Frame::new(renderer, bounds.size());
+ let custom_style = <Theme as StyleSheet>::appearance(theme, self.style);
+ let track_radius = frame.width() / 2.0 - self.bar_height;
+
+ if let Some(Background::Color(color)) = custom_style.background {
+ let background_path =
+ canvas::Path::circle(frame.center(), track_radius);
+ frame.fill(&background_path, color);
+ }
+
+ 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 = self.state.procession() * 2.0 * PI;
+
+ match self.state {
+ State::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)),
+ });
+ }
+ State::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),
+ );
+
+ vec![frame.into_geometry()]
+ }
+}
diff --git a/examples/progress_indicators/src/easing.rs b/examples/progress_indicators/src/easing.rs
new file mode 100644
index 00000000..10479659
--- /dev/null
+++ b/examples/progress_indicators/src/easing.rs
@@ -0,0 +1,67 @@
+use flo_curves::bezier::Curve;
+use flo_curves::*;
+use lazy_static::lazy_static;
+
+use std::borrow::Cow;
+
+lazy_static! {
+ pub static ref EXAMPLES: [Easing; 3] = [
+ Easing::CubicBezier(Curve::from_points(
+ Coord2(0.0, 0.0),
+ (Coord2(0.05, 0.7), Coord2(0.1, 1.0)),
+ Coord2(1.0, 1.0),
+ )),
+ Easing::CubicBezier(Curve::from_points(
+ Coord2(0.0, 0.0),
+ (Coord2(0.3, 0.0), Coord2(0.8, 0.15)),
+ Coord2(1.0, 1.0),
+ )),
+ Easing::CubicBezier(Curve::from_points(
+ Coord2(0.0, 0.0),
+ (Coord2(0.2, 0.0), Coord2(0.0, 1.0)),
+ Coord2(1.0, 1.0),
+ ))
+ ];
+ pub static ref STANDARD: Easing = {
+ Easing::CubicBezier(Curve::from_points(
+ Coord2(0.0, 0.0),
+ (Coord2(0.2, 0.0), Coord2(0.0, 1.0)),
+ Coord2(1.0, 1.0),
+ ))
+ };
+}
+
+#[derive(Clone, Debug)]
+pub enum Easing {
+ BezierPath(Vec<Curve<Coord2>>),
+ CubicBezier(Curve<Coord2>),
+}
+
+impl Easing {
+ pub fn y_at_x(&self, x: f32) -> f32 {
+ let x = x as f64;
+
+ match self {
+ Self::BezierPath(curves) => curves
+ .iter()
+ .find_map(|curve| {
+ (curve.start_point().0 <= x && curve.end_point().0 >= x)
+ .then(|| curve.point_at_pos(x).1 as f32)
+ })
+ .unwrap_or_default(),
+ Self::CubicBezier(curve) => curve.point_at_pos(x).1 as f32,
+ }
+ }
+}
+
+impl<'a> From<Easing> for Cow<'a, Easing> {
+ fn from(easing: Easing) -> Self {
+ Cow::Owned(easing)
+ }
+}
+
+impl<'a> From<&'a Easing> for Cow<'a, Easing> {
+ fn from(easing: &'a Easing) -> Self {
+ Cow::Borrowed(easing)
+ }
+}
diff --git a/examples/progress_indicators/src/linear.rs b/examples/progress_indicators/src/linear.rs
new file mode 100644
index 00000000..735892e7
--- /dev/null
+++ b/examples/progress_indicators/src/linear.rs
@@ -0,0 +1,325 @@
+//! Show a linear progress indicator.
+use iced::window;
+use iced_core::event::{self, Event};
+use iced_core::renderer;
+use iced_core::time::Instant;
+use iced_core::widget::tree::{self, Tree};
+use iced_core::window::RedrawRequest;
+use iced_core::{layout, Size};
+use iced_core::{
+ Background, Clipboard, Color, Element, Layout, Length, Point, Rectangle,
+ Shell, Widget,
+};
+
+use super::easing::{self, Easing};
+
+use std::borrow::Cow;
+use std::time::Duration;
+
+#[allow(missing_debug_implementations)]
+pub struct Linear<'a, Renderer>
+where
+ Renderer: iced_core::Renderer,
+ Renderer::Theme: StyleSheet,
+{
+ width: Length,
+ height: Length,
+ style: <Renderer::Theme as StyleSheet>::Style,
+ easing: Cow<'a, Easing>,
+ cycle_duration: Duration,
+}
+
+impl<'a, Renderer> Linear<'a, Renderer>
+where
+ Renderer: iced_widget::core::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: Cow::Borrowed(&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: impl Into<Cow<'a, Easing>>) -> Self {
+ self.easing = easing.into();
+ 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_core::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_core::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_position: Point,
+ _renderer: &Renderer,
+ _clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> event::Status {
+ 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::NextFrame);
+ }
+
+ event::Status::Ignored
+ }
+
+ fn draw(
+ &self,
+ tree: &Tree,
+ renderer: &mut Renderer,
+ theme: &Renderer::Theme,
+ _style: &renderer::Style,
+ layout: Layout<'_>,
+ _cursor_position: Point,
+ _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(
+ renderer::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_core::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/progress_indicators/src/main.rs b/examples/progress_indicators/src/main.rs
new file mode 100644
index 00000000..136b8d8c
--- /dev/null
+++ b/examples/progress_indicators/src/main.rs
@@ -0,0 +1,104 @@
+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 {
+ ProgressIndicators::run(Settings {
+ antialiasing: true,
+ ..Default::default()
+ })
+}
+
+struct ProgressIndicators {
+ cycle_duration: f32,
+}
+
+impl Default for ProgressIndicators {
+ fn default() -> Self {
+ Self {
+ cycle_duration: 2.0,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+enum Message {
+ CycleDurationChanged(f32),
+}
+
+impl Application for ProgressIndicators {
+ 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("Progress Indicators - 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::EXAMPLES
+ .iter()
+ .zip(["Decelerating:", "Accelerating:", "Standard:"])
+ .fold(column![], |column, (easing, label)| {
+ column.push(
+ row![
+ text(label).width(150),
+ 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(150.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()
+ }
+}