summaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to 'examples')
-rw-r--r--examples/README.md6
-rw-r--r--examples/arc/Cargo.toml9
-rw-r--r--examples/arc/README.md14
-rw-r--r--examples/arc/src/main.rs126
-rw-r--r--examples/bezier_tool/src/main.rs76
-rw-r--r--examples/clock/src/main.rs31
-rw-r--r--examples/color_palette/Cargo.toml2
-rw-r--r--examples/color_palette/README.md2
-rw-r--r--examples/color_palette/src/main.rs126
-rw-r--r--examples/component/src/main.rs115
-rw-r--r--examples/counter/src/main.rs33
-rw-r--r--examples/custom_widget/src/main.rs60
-rw-r--r--examples/download_progress/src/download.rs4
-rw-r--r--examples/download_progress/src/main.rs110
-rw-r--r--examples/events/src/main.rs35
-rw-r--r--examples/exit/src/main.rs43
-rw-r--r--examples/game_of_life/src/main.rs289
-rw-r--r--examples/game_of_life/src/preset.rs10
-rw-r--r--examples/game_of_life/src/style.rs189
-rw-r--r--examples/geometry/src/main.rs71
-rw-r--r--examples/integration_opengl/src/controls.rs17
-rw-r--r--examples/integration_opengl/src/main.rs12
-rw-r--r--examples/integration_wgpu/README.md2
-rw-r--r--examples/integration_wgpu/src/controls.rs27
-rw-r--r--examples/integration_wgpu/src/main.rs35
-rw-r--r--examples/integration_wgpu/src/scene.rs77
-rw-r--r--examples/integration_wgpu/src/shader/frag.wgsl4
-rw-r--r--examples/integration_wgpu/src/shader/vert.wgsl4
-rw-r--r--examples/pane_grid/src/main.rs369
-rw-r--r--examples/pick_list/src/main.rs33
-rw-r--r--examples/pokedex/src/main.rs144
-rw-r--r--examples/progress_bar/src/main.rs25
-rw-r--r--examples/qr_code/src/main.rs28
-rw-r--r--examples/scrollable/src/main.rs240
-rw-r--r--examples/scrollable/src/style.rs191
-rw-r--r--examples/sierpinski_triangle/Cargo.toml10
-rw-r--r--examples/sierpinski_triangle/README.md16
-rw-r--r--examples/sierpinski_triangle/src/main.rs193
-rw-r--r--examples/solar_system/src/main.rs41
-rw-r--r--examples/stopwatch/src/main.rs91
-rw-r--r--examples/styling/src/main.rs576
-rw-r--r--examples/svg/src/main.rs11
-rw-r--r--examples/system_information/Cargo.toml10
-rw-r--r--examples/system_information/src/main.rs149
-rw-r--r--examples/todos/Cargo.toml3
-rw-r--r--examples/todos/src/main.rs464
-rw-r--r--examples/tooltip/src/main.rs155
-rw-r--r--examples/tour/README.md9
-rw-r--r--examples/tour/src/main.rs577
-rw-r--r--examples/url_handler/src/main.rs16
-rw-r--r--examples/websocket/Cargo.toml3
-rw-r--r--examples/websocket/src/echo.rs21
-rw-r--r--examples/websocket/src/echo/server.rs8
-rw-r--r--examples/websocket/src/main.rs80
54 files changed, 2073 insertions, 2919 deletions
diff --git a/examples/README.md b/examples/README.md
index 137d134c..bb15dc2e 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -27,10 +27,6 @@ You can run the native version with `cargo run`:
cargo run --package tour
```
-The web version can be run by following [the usage instructions of `iced_web`] or by accessing [iced.rs](https://iced.rs/)!
-
-[the usage instructions of `iced_web`]: https://github.com/iced-rs/iced_web#usage
-
## [Todos](todos)
A todos tracker inspired by [TodoMVC]. It showcases dynamic layout, text input, checkboxes, scrollables, icons, and async actions! It automatically saves your tasks in the background, even if you did not finish typing them.
@@ -46,7 +42,6 @@ You can run the native version with `cargo run`:
```
cargo run --package todos
```
-We have not yet implemented a `LocalStorage` version of the auto-save feature. Therefore, it does not work on web _yet_!
[TodoMVC]: http://todomvc.com/
@@ -105,6 +100,7 @@ A bunch of simpler examples exist:
- [`pokedex`](pokedex), an application that displays a random Pokédex entry (sprite included!) by using the [PokéAPI].
- [`progress_bar`](progress_bar), a simple progress bar that can be filled by using a slider.
- [`scrollable`](scrollable), a showcase of the various scrollbar width options.
+- [`sierpinski_triangle`](sierpinski_triangle), a [sierpiński triangle](https://en.wikipedia.org/wiki/Sierpi%C5%84ski_triangle) Emulator, use `Canvas` and `Slider`.
- [`solar_system`](solar_system), an animated solar system drawn using the `Canvas` widget and showcasing how to compose different transforms.
- [`stopwatch`](stopwatch), a watch with start/stop and reset buttons showcasing how to listen to time.
- [`svg`](svg), an application that renders the [Ghostscript Tiger] by leveraging the `Svg` widget.
diff --git a/examples/arc/Cargo.toml b/examples/arc/Cargo.toml
new file mode 100644
index 00000000..e6e74363
--- /dev/null
+++ b/examples/arc/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "arc"
+version = "0.1.0"
+authors = ["ThatsNoMoon <git@thatsnomoon.dev>"]
+edition = "2021"
+publish = false
+
+[dependencies]
+iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
diff --git a/examples/arc/README.md b/examples/arc/README.md
new file mode 100644
index 00000000..303253da
--- /dev/null
+++ b/examples/arc/README.md
@@ -0,0 +1,14 @@
+## Arc
+
+An application that uses the `Canvas` widget to draw a rotating arc.
+
+This is a simple demo for https://github.com/iced-rs/iced/pull/1358.
+
+The __[`main`]__ file contains all the code of the example.
+
+You can run it with `cargo run`:
+```
+cargo run --package arc
+```
+
+[`main`]: src/main.rs
diff --git a/examples/arc/src/main.rs b/examples/arc/src/main.rs
new file mode 100644
index 00000000..0c619dc9
--- /dev/null
+++ b/examples/arc/src/main.rs
@@ -0,0 +1,126 @@
+use std::{f32::consts::PI, time::Instant};
+
+use iced::executor;
+use iced::widget::canvas::{
+ self, Cache, Canvas, Cursor, Geometry, Path, Stroke,
+};
+use iced::{
+ Application, Command, Element, Length, Point, Rectangle, Settings,
+ Subscription, Theme,
+};
+
+pub fn main() -> iced::Result {
+ Arc::run(Settings {
+ antialiasing: true,
+ ..Settings::default()
+ })
+}
+
+struct Arc {
+ start: Instant,
+ cache: Cache,
+}
+
+#[derive(Debug, Clone, Copy)]
+enum Message {
+ Tick,
+}
+
+impl Application for Arc {
+ type Executor = executor::Default;
+ type Message = Message;
+ type Theme = Theme;
+ type Flags = ();
+
+ fn new(_flags: ()) -> (Self, Command<Message>) {
+ (
+ Arc {
+ start: Instant::now(),
+ cache: Default::default(),
+ },
+ Command::none(),
+ )
+ }
+
+ fn title(&self) -> String {
+ String::from("Arc - Iced")
+ }
+
+ fn update(&mut self, _: Message) -> Command<Message> {
+ self.cache.clear();
+
+ Command::none()
+ }
+
+ fn subscription(&self) -> Subscription<Message> {
+ iced::time::every(std::time::Duration::from_millis(10))
+ .map(|_| Message::Tick)
+ }
+
+ fn view(&self) -> Element<Message> {
+ Canvas::new(self)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .into()
+ }
+
+ fn theme(&self) -> Theme {
+ Theme::Dark
+ }
+}
+
+impl<Message> canvas::Program<Message> for Arc {
+ type State = ();
+
+ fn draw(
+ &self,
+ _state: &Self::State,
+ theme: &Theme,
+ bounds: Rectangle,
+ _cursor: Cursor,
+ ) -> Vec<Geometry> {
+ let geometry = self.cache.draw(bounds.size(), |frame| {
+ let palette = theme.palette();
+
+ let center = frame.center();
+ let radius = frame.width().min(frame.height()) / 5.0;
+
+ let start = Point::new(center.x, center.y - radius);
+
+ let angle = (self.start.elapsed().as_millis() % 10_000) as f32
+ / 10_000.0
+ * 2.0
+ * PI;
+
+ let end = Point::new(
+ center.x + radius * angle.cos(),
+ center.y + radius * angle.sin(),
+ );
+
+ let circles = Path::new(|b| {
+ b.circle(start, 10.0);
+ b.move_to(end);
+ b.circle(end, 10.0);
+ });
+
+ frame.fill(&circles, palette.text);
+
+ let path = Path::new(|b| {
+ b.move_to(start);
+ b.arc_to(center, end, 50.0);
+ b.line_to(end);
+ });
+
+ frame.stroke(
+ &path,
+ Stroke {
+ color: palette.text,
+ width: 10.0,
+ ..Stroke::default()
+ },
+ );
+ });
+
+ vec![geometry]
+ }
+}
diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs
index 35b5182c..7c3916d4 100644
--- a/examples/bezier_tool/src/main.rs
+++ b/examples/bezier_tool/src/main.rs
@@ -1,7 +1,6 @@
//! This example showcases an interactive `Canvas` for drawing Bézier curves.
-use iced::{
- button, Alignment, Button, Column, Element, Length, Sandbox, Settings, Text,
-};
+use iced::widget::{button, column, text};
+use iced::{Alignment, Element, Length, Sandbox, Settings};
pub fn main() -> iced::Result {
Example::run(Settings {
@@ -14,7 +13,6 @@ pub fn main() -> iced::Result {
struct Example {
bezier: bezier::State,
curves: Vec<bezier::Curve>,
- button_state: button::State,
}
#[derive(Debug, Clone, Copy)]
@@ -47,44 +45,34 @@ impl Sandbox for Example {
}
}
- fn view(&mut self) -> Element<Message> {
- Column::new()
- .padding(20)
- .spacing(20)
- .align_items(Alignment::Center)
- .push(
- Text::new("Bezier tool example")
- .width(Length::Shrink)
- .size(50),
- )
- .push(self.bezier.view(&self.curves).map(Message::AddCurve))
- .push(
- Button::new(&mut self.button_state, Text::new("Clear"))
- .padding(8)
- .on_press(Message::Clear),
- )
- .into()
+ fn view(&self) -> Element<Message> {
+ column![
+ text("Bezier tool example").width(Length::Shrink).size(50),
+ self.bezier.view(&self.curves).map(Message::AddCurve),
+ button("Clear").padding(8).on_press(Message::Clear),
+ ]
+ .padding(20)
+ .spacing(20)
+ .align_items(Alignment::Center)
+ .into()
}
}
mod bezier {
- use iced::{
- canvas::event::{self, Event},
- canvas::{self, Canvas, Cursor, Frame, Geometry, Path, Stroke},
- mouse, Element, Length, Point, Rectangle,
+ use iced::mouse;
+ use iced::widget::canvas::event::{self, Event};
+ use iced::widget::canvas::{
+ self, Canvas, Cursor, Frame, Geometry, Path, Stroke,
};
+ use iced::{Element, Length, Point, Rectangle, Theme};
#[derive(Default)]
pub struct State {
- pending: Option<Pending>,
cache: canvas::Cache,
}
impl State {
- pub fn view<'a>(
- &'a mut self,
- curves: &'a [Curve],
- ) -> Element<'a, Curve> {
+ pub fn view<'a>(&'a self, curves: &'a [Curve]) -> Element<'a, Curve> {
Canvas::new(Bezier {
state: self,
curves,
@@ -100,13 +88,16 @@ mod bezier {
}
struct Bezier<'a> {
- state: &'a mut State,
+ state: &'a State,
curves: &'a [Curve],
}
impl<'a> canvas::Program<Curve> for Bezier<'a> {
+ type State = Option<Pending>;
+
fn update(
- &mut self,
+ &self,
+ state: &mut Self::State,
event: Event,
bounds: Rectangle,
cursor: Cursor,
@@ -122,16 +113,16 @@ mod bezier {
Event::Mouse(mouse_event) => {
let message = match mouse_event {
mouse::Event::ButtonPressed(mouse::Button::Left) => {
- match self.state.pending {
+ match *state {
None => {
- self.state.pending = Some(Pending::One {
+ *state = Some(Pending::One {
from: cursor_position,
});
None
}
Some(Pending::One { from }) => {
- self.state.pending = Some(Pending::Two {
+ *state = Some(Pending::Two {
from,
to: cursor_position,
});
@@ -139,7 +130,7 @@ mod bezier {
None
}
Some(Pending::Two { from, to }) => {
- self.state.pending = None;
+ *state = None;
Some(Curve {
from,
@@ -158,18 +149,24 @@ mod bezier {
}
}
- fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec<Geometry> {
+ fn draw(
+ &self,
+ state: &Self::State,
+ _theme: &Theme,
+ bounds: Rectangle,
+ cursor: Cursor,
+ ) -> Vec<Geometry> {
let content =
self.state.cache.draw(bounds.size(), |frame: &mut Frame| {
Curve::draw_all(self.curves, frame);
frame.stroke(
&Path::rectangle(Point::ORIGIN, frame.size()),
- Stroke::default(),
+ Stroke::default().with_width(2.0),
);
});
- if let Some(pending) = &self.state.pending {
+ if let Some(pending) = state {
let pending_curve = pending.draw(bounds, cursor);
vec![content, pending_curve]
@@ -180,6 +177,7 @@ mod bezier {
fn mouse_interaction(
&self,
+ _state: &Self::State,
bounds: Rectangle,
cursor: Cursor,
) -> mouse::Interaction {
diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs
index 325ccc1a..8818fb54 100644
--- a/examples/clock/src/main.rs
+++ b/examples/clock/src/main.rs
@@ -1,7 +1,9 @@
+use iced::executor;
+use iced::widget::canvas::{Cache, Cursor, Geometry, LineCap, Path, Stroke};
+use iced::widget::{canvas, container};
use iced::{
- canvas::{self, Cache, Canvas, Cursor, Geometry, LineCap, Path, Stroke},
- executor, Application, Color, Command, Container, Element, Length, Point,
- Rectangle, Settings, Subscription, Vector,
+ Application, Color, Command, Element, Length, Point, Rectangle, Settings,
+ Subscription, Theme, Vector,
};
pub fn main() -> iced::Result {
@@ -22,8 +24,9 @@ enum Message {
}
impl Application for Clock {
- type Executor = executor::Default;
type Message = Message;
+ type Theme = Theme;
+ type Executor = executor::Default;
type Flags = ();
fn new(_flags: ()) -> (Self, Command<Message>) {
@@ -65,10 +68,12 @@ impl Application for Clock {
})
}
- fn view(&mut self) -> Element<Message> {
- let canvas = Canvas::new(self).width(Length::Fill).height(Length::Fill);
+ fn view(&self) -> Element<Message> {
+ let canvas = canvas(self as &Self)
+ .width(Length::Fill)
+ .height(Length::Fill);
- Container::new(canvas)
+ container(canvas)
.width(Length::Fill)
.height(Length::Fill)
.padding(20)
@@ -76,8 +81,16 @@ impl Application for Clock {
}
}
-impl canvas::Program<Message> for Clock {
- fn draw(&self, bounds: Rectangle, _cursor: Cursor) -> Vec<Geometry> {
+impl<Message> canvas::Program<Message> for Clock {
+ type State = ();
+
+ fn draw(
+ &self,
+ _state: &Self::State,
+ _theme: &Theme,
+ bounds: Rectangle,
+ _cursor: Cursor,
+ ) -> Vec<Geometry> {
let clock = self.clock.draw(bounds.size(), |frame| {
let center = frame.center();
let radius = frame.width().min(frame.height()) / 2.0;
diff --git a/examples/color_palette/Cargo.toml b/examples/color_palette/Cargo.toml
index 23670b46..8fd37202 100644
--- a/examples/color_palette/Cargo.toml
+++ b/examples/color_palette/Cargo.toml
@@ -7,4 +7,4 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["canvas", "palette"] }
-palette = "0.5.0"
+palette = "0.6.0"
diff --git a/examples/color_palette/README.md b/examples/color_palette/README.md
index 95a23f48..f90020b1 100644
--- a/examples/color_palette/README.md
+++ b/examples/color_palette/README.md
@@ -11,5 +11,5 @@ A color palette generator, based on a user-defined root color.
You can run it with `cargo run`:
```
-cargo run --package color_palette
+cargo run --package pure_color_palette
```
diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs
index ad3004b0..42149965 100644
--- a/examples/color_palette/src/main.rs
+++ b/examples/color_palette/src/main.rs
@@ -1,9 +1,10 @@
-use iced::canvas::{self, Cursor, Frame, Geometry, Path};
+use iced::widget::canvas::{self, Canvas, Cursor, Frame, Geometry, Path};
+use iced::widget::{column, row, text, Slider};
use iced::{
- alignment, slider, Alignment, Canvas, Color, Column, Element, Length,
- Point, Rectangle, Row, Sandbox, Settings, Size, Slider, Text, Vector,
+ alignment, Alignment, Color, Element, Length, Point, Rectangle, Sandbox,
+ Settings, Size, Vector,
};
-use palette::{self, Hsl, Limited, Srgb};
+use palette::{self, convert::FromColor, Hsl, Srgb};
use std::marker::PhantomData;
use std::ops::RangeInclusive;
@@ -49,42 +50,43 @@ impl Sandbox for ColorPalette {
fn update(&mut self, message: Message) {
let srgb = match message {
Message::RgbColorChanged(rgb) => palette::Srgb::from(rgb),
- Message::HslColorChanged(hsl) => palette::Srgb::from(hsl),
- Message::HsvColorChanged(hsv) => palette::Srgb::from(hsv),
- Message::HwbColorChanged(hwb) => palette::Srgb::from(hwb),
- Message::LabColorChanged(lab) => palette::Srgb::from(lab),
- Message::LchColorChanged(lch) => palette::Srgb::from(lch),
+ Message::HslColorChanged(hsl) => palette::Srgb::from_color(hsl),
+ Message::HsvColorChanged(hsv) => palette::Srgb::from_color(hsv),
+ Message::HwbColorChanged(hwb) => palette::Srgb::from_color(hwb),
+ Message::LabColorChanged(lab) => palette::Srgb::from_color(lab),
+ Message::LchColorChanged(lch) => palette::Srgb::from_color(lch),
};
- self.theme = Theme::new(srgb.clamp());
+ self.theme = Theme::new(srgb);
}
- fn view(&mut self) -> Element<Message> {
+ fn view(&self) -> Element<Message> {
let base = self.theme.base;
let srgb = palette::Srgb::from(base);
- let hsl = palette::Hsl::from(srgb);
- let hsv = palette::Hsv::from(srgb);
- let hwb = palette::Hwb::from(srgb);
- let lab = palette::Lab::from(srgb);
- let lch = palette::Lch::from(srgb);
-
- Column::new()
- .padding(10)
- .spacing(10)
- .push(self.rgb.view(base).map(Message::RgbColorChanged))
- .push(self.hsl.view(hsl).map(Message::HslColorChanged))
- .push(self.hsv.view(hsv).map(Message::HsvColorChanged))
- .push(self.hwb.view(hwb).map(Message::HwbColorChanged))
- .push(self.lab.view(lab).map(Message::LabColorChanged))
- .push(self.lch.view(lch).map(Message::LchColorChanged))
- .push(self.theme.view())
- .into()
+ let hsl = palette::Hsl::from_color(srgb);
+ let hsv = palette::Hsv::from_color(srgb);
+ let hwb = palette::Hwb::from_color(srgb);
+ let lab = palette::Lab::from_color(srgb);
+ let lch = palette::Lch::from_color(srgb);
+
+ column![
+ self.rgb.view(base).map(Message::RgbColorChanged),
+ self.hsl.view(hsl).map(Message::HslColorChanged),
+ self.hsv.view(hsv).map(Message::HsvColorChanged),
+ self.hwb.view(hwb).map(Message::HwbColorChanged),
+ self.lab.view(lab).map(Message::LabColorChanged),
+ self.lch.view(lch).map(Message::LchColorChanged),
+ self.theme.view(),
+ ]
+ .padding(10)
+ .spacing(10)
+ .into()
}
}
#[derive(Debug)]
-pub struct Theme {
+struct Theme {
lower: Vec<Color>,
base: Color,
higher: Vec<Color>,
@@ -98,7 +100,7 @@ impl Theme {
let base = base.into();
// Convert to HSL color for manipulation
- let hsl = Hsl::from(Srgb::from(base));
+ let hsl = Hsl::from_color(Srgb::from(base));
let lower = [
hsl.shift_hue(-135.0).lighten(0.075),
@@ -117,12 +119,12 @@ impl Theme {
Theme {
lower: lower
.iter()
- .map(|&color| Srgb::from(color).clamp().into())
+ .map(|&color| Srgb::from_color(color).into())
.collect(),
base,
higher: higher
.iter()
- .map(|&color| Srgb::from(color).clamp().into())
+ .map(|&color| Srgb::from_color(color).into())
.collect(),
canvas_cache: canvas::Cache::default(),
}
@@ -139,7 +141,7 @@ impl Theme {
.chain(self.higher.iter())
}
- pub fn view(&mut self) -> Element<Message> {
+ pub fn view(&self) -> Element<Message> {
Canvas::new(self)
.width(Length::Fill)
.height(Length::Fill)
@@ -207,14 +209,14 @@ impl Theme {
text.vertical_alignment = alignment::Vertical::Bottom;
- let hsl = Hsl::from(Srgb::from(self.base));
+ let hsl = Hsl::from_color(Srgb::from(self.base));
for i in 0..self.len() {
let pct = (i as f32 + 1.0) / (self.len() as f32 + 1.0);
let graded = Hsl {
lightness: 1.0 - pct,
..hsl
};
- let color: Color = Srgb::from(graded.clamp()).into();
+ let color: Color = Srgb::from_color(graded).into();
let anchor = Point {
x: (i as f32) * box_size.width,
@@ -235,8 +237,16 @@ impl Theme {
}
}
-impl canvas::Program<Message> for Theme {
- fn draw(&self, bounds: Rectangle, _cursor: Cursor) -> Vec<Geometry> {
+impl<Message> canvas::Program<Message> for Theme {
+ type State = ();
+
+ fn draw(
+ &self,
+ _state: &Self::State,
+ _theme: &iced::Theme,
+ bounds: Rectangle,
+ _cursor: Cursor,
+ ) -> Vec<Geometry> {
let theme = self.canvas_cache.draw(bounds.size(), |frame| {
self.draw(frame);
});
@@ -262,7 +272,6 @@ fn color_hex_string(color: &Color) -> String {
#[derive(Default)]
struct ColorPicker<C: ColorSpace> {
- sliders: [slider::State; 3],
color_space: PhantomData<C>,
}
@@ -277,37 +286,30 @@ trait ColorSpace: Sized {
fn to_string(&self) -> String;
}
-impl<C: 'static + ColorSpace + Copy> ColorPicker<C> {
- fn view(&mut self, color: C) -> Element<C> {
+impl<C: ColorSpace + Copy> ColorPicker<C> {
+ fn view(&self, color: C) -> Element<C> {
let [c1, c2, c3] = color.components();
- let [s1, s2, s3] = &mut self.sliders;
let [cr1, cr2, cr3] = C::COMPONENT_RANGES;
- fn slider<C: Clone>(
- state: &mut slider::State,
+ fn slider<'a, C: Clone>(
range: RangeInclusive<f64>,
component: f32,
- update: impl Fn(f32) -> C + 'static,
- ) -> Slider<f64, C> {
- Slider::new(state, range, f64::from(component), move |v| {
- update(v as f32)
- })
- .step(0.01)
+ update: impl Fn(f32) -> C + 'a,
+ ) -> Slider<'a, f64, C, iced::Renderer> {
+ Slider::new(range, f64::from(component), move |v| update(v as f32))
+ .step(0.01)
}
- Row::new()
- .spacing(10)
- .align_items(Alignment::Center)
- .push(Text::new(C::LABEL).width(Length::Units(50)))
- .push(slider(s1, cr1, c1, move |v| C::new(v, c2, c3)))
- .push(slider(s2, cr2, c2, move |v| C::new(c1, v, c3)))
- .push(slider(s3, cr3, c3, move |v| C::new(c1, c2, v)))
- .push(
- Text::new(color.to_string())
- .width(Length::Units(185))
- .size(14),
- )
- .into()
+ row![
+ text(C::LABEL).width(Length::Units(50)),
+ slider(cr1, c1, move |v| C::new(v, c2, c3)),
+ slider(cr2, c2, move |v| C::new(c1, v, c3)),
+ slider(cr3, c3, move |v| C::new(c1, c2, v)),
+ text(color.to_string()).width(Length::Units(185)).size(14),
+ ]
+ .spacing(10)
+ .align_items(Alignment::Center)
+ .into()
}
}
diff --git a/examples/component/src/main.rs b/examples/component/src/main.rs
index 39335cf1..06b1e53a 100644
--- a/examples/component/src/main.rs
+++ b/examples/component/src/main.rs
@@ -1,5 +1,7 @@
-use iced::{Container, Element, Length, Sandbox, Settings};
-use numeric_input::NumericInput;
+use iced::widget::container;
+use iced::{Element, Length, Sandbox, Settings};
+
+use numeric_input::numeric_input;
pub fn main() -> iced::Result {
Component::run(Settings::default())
@@ -7,7 +9,6 @@ pub fn main() -> iced::Result {
#[derive(Default)]
struct Component {
- numeric_input: numeric_input::State,
value: Option<u32>,
}
@@ -35,39 +36,31 @@ impl Sandbox for Component {
}
}
- fn view(&mut self) -> Element<Message> {
- Container::new(NumericInput::new(
- &mut self.numeric_input,
- self.value,
- Message::NumericInputChanged,
- ))
- .padding(20)
- .height(Length::Fill)
- .center_y()
- .into()
+ fn view(&self) -> Element<Message> {
+ container(numeric_input(self.value, Message::NumericInputChanged))
+ .padding(20)
+ .height(Length::Fill)
+ .center_y()
+ .into()
}
}
mod numeric_input {
- use iced_lazy::component::{self, Component};
- use iced_native::alignment::{self, Alignment};
- use iced_native::text;
- use iced_native::widget::button::{self, Button};
- use iced_native::widget::text_input::{self, TextInput};
- use iced_native::widget::{Row, Text};
- use iced_native::{Element, Length};
-
- pub struct NumericInput<'a, Message> {
- state: &'a mut State,
+ use iced::alignment::{self, Alignment};
+ use iced::widget::{self, button, row, text, text_input};
+ use iced::{Element, Length};
+ use iced_lazy::{self, Component};
+
+ pub struct NumericInput<Message> {
value: Option<u32>,
on_change: Box<dyn Fn(Option<u32>) -> Message>,
}
- #[derive(Default)]
- pub struct State {
- input: text_input::State,
- decrement_button: button::State,
- increment_button: button::State,
+ pub fn numeric_input<Message>(
+ value: Option<u32>,
+ on_change: impl Fn(Option<u32>) -> Message + 'static,
+ ) -> NumericInput<Message> {
+ NumericInput::new(value, on_change)
}
#[derive(Debug, Clone)]
@@ -77,28 +70,33 @@ mod numeric_input {
DecrementPressed,
}
- impl<'a, Message> NumericInput<'a, Message> {
+ impl<Message> NumericInput<Message> {
pub fn new(
- state: &'a mut State,
value: Option<u32>,
on_change: impl Fn(Option<u32>) -> Message + 'static,
) -> Self {
Self {
- state,
value,
on_change: Box::new(on_change),
}
}
}
- impl<'a, Message, Renderer> Component<Message, Renderer>
- for NumericInput<'a, Message>
+ impl<Message, Renderer> Component<Message, Renderer> for NumericInput<Message>
where
- Renderer: 'a + text::Renderer,
+ Renderer: iced_native::text::Renderer + 'static,
+ Renderer::Theme: widget::button::StyleSheet
+ + widget::text_input::StyleSheet
+ + widget::text::StyleSheet,
{
+ type State = ();
type Event = Event;
- fn update(&mut self, event: Event) -> Option<Message> {
+ fn update(
+ &mut self,
+ _state: &mut Self::State,
+ event: Event,
+ ) -> Option<Message> {
match event {
Event::IncrementPressed => Some((self.on_change)(Some(
self.value.unwrap_or_default().saturating_add(1),
@@ -120,11 +118,10 @@ mod numeric_input {
}
}
- fn view(&mut self) -> Element<Event, Renderer> {
- let button = |state, label, on_press| {
- Button::new(
- state,
- Text::new(label)
+ fn view(&self, _state: &Self::State) -> Element<Event, Renderer> {
+ let button = |label, on_press| {
+ button(
+ text(label)
.width(Length::Fill)
.height(Length::Fill)
.horizontal_alignment(alignment::Horizontal::Center)
@@ -134,47 +131,37 @@ mod numeric_input {
.on_press(on_press)
};
- Row::with_children(vec![
- button(
- &mut self.state.decrement_button,
- "-",
- Event::DecrementPressed,
- )
- .into(),
- TextInput::new(
- &mut self.state.input,
+ row![
+ button("-", Event::DecrementPressed),
+ text_input(
"Type a number",
self.value
.as_ref()
.map(u32::to_string)
- .as_ref()
- .map(String::as_str)
+ .as_deref()
.unwrap_or(""),
Event::InputChanged,
)
- .padding(10)
- .into(),
- button(
- &mut self.state.increment_button,
- "+",
- Event::IncrementPressed,
- )
- .into(),
- ])
+ .padding(10),
+ button("+", Event::IncrementPressed),
+ ]
.align_items(Alignment::Fill)
.spacing(10)
.into()
}
}
- impl<'a, Message, Renderer> From<NumericInput<'a, Message>>
+ impl<'a, Message, Renderer> From<NumericInput<Message>>
for Element<'a, Message, Renderer>
where
Message: 'a,
- Renderer: text::Renderer + 'a,
+ Renderer: 'static + iced_native::text::Renderer,
+ Renderer::Theme: widget::button::StyleSheet
+ + widget::text_input::StyleSheet
+ + widget::text::StyleSheet,
{
- fn from(numeric_input: NumericInput<'a, Message>) -> Self {
- component::view(numeric_input)
+ fn from(numeric_input: NumericInput<Message>) -> Self {
+ iced_lazy::component(numeric_input)
}
}
}
diff --git a/examples/counter/src/main.rs b/examples/counter/src/main.rs
index 931cf5e1..13dcbf86 100644
--- a/examples/counter/src/main.rs
+++ b/examples/counter/src/main.rs
@@ -1,16 +1,12 @@
-use iced::{
- button, Alignment, Button, Column, Element, Sandbox, Settings, Text,
-};
+use iced::widget::{button, column, text};
+use iced::{Alignment, Element, Sandbox, Settings};
pub fn main() -> iced::Result {
Counter::run(Settings::default())
}
-#[derive(Default)]
struct Counter {
value: i32,
- increment_button: button::State,
- decrement_button: button::State,
}
#[derive(Debug, Clone, Copy)]
@@ -23,7 +19,7 @@ impl Sandbox for Counter {
type Message = Message;
fn new() -> Self {
- Self::default()
+ Self { value: 0 }
}
fn title(&self) -> String {
@@ -41,19 +37,14 @@ impl Sandbox for Counter {
}
}
- fn view(&mut self) -> Element<Message> {
- Column::new()
- .padding(20)
- .align_items(Alignment::Center)
- .push(
- Button::new(&mut self.increment_button, Text::new("Increment"))
- .on_press(Message::IncrementPressed),
- )
- .push(Text::new(self.value.to_string()).size(50))
- .push(
- Button::new(&mut self.decrement_button, Text::new("Decrement"))
- .on_press(Message::DecrementPressed),
- )
- .into()
+ fn view(&self) -> Element<Message> {
+ column![
+ button("Increment").on_press(Message::IncrementPressed),
+ text(self.value).size(50),
+ button("Decrement").on_press(Message::DecrementPressed)
+ ]
+ .padding(20)
+ .align_items(Alignment::Center)
+ .into()
}
}
diff --git a/examples/custom_widget/src/main.rs b/examples/custom_widget/src/main.rs
index 28edf256..c37a1a12 100644
--- a/examples/custom_widget/src/main.rs
+++ b/examples/custom_widget/src/main.rs
@@ -11,7 +11,8 @@ mod circle {
// implemented by `iced_wgpu` and other renderers.
use iced_native::layout::{self, Layout};
use iced_native::renderer;
- use iced_native::{Color, Element, Length, Point, Rectangle, Size, Widget};
+ use iced_native::widget::{self, Widget};
+ use iced_native::{Color, Element, Length, Point, Rectangle, Size};
pub struct Circle {
radius: f32,
@@ -23,6 +24,10 @@ mod circle {
}
}
+ pub fn circle(radius: f32) -> Circle {
+ Circle::new(radius)
+ }
+
impl<Message, Renderer> Widget<Message, Renderer> for Circle
where
Renderer: renderer::Renderer,
@@ -45,7 +50,9 @@ mod circle {
fn draw(
&self,
+ _state: &widget::Tree,
renderer: &mut Renderer,
+ _theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
@@ -63,21 +70,19 @@ mod circle {
}
}
- impl<'a, Message, Renderer> Into<Element<'a, Message, Renderer>> for Circle
+ impl<'a, Message, Renderer> From<Circle> for Element<'a, Message, Renderer>
where
Renderer: renderer::Renderer,
{
- fn into(self) -> Element<'a, Message, Renderer> {
- Element::new(self)
+ fn from(circle: Circle) -> Self {
+ Self::new(circle)
}
}
}
-use circle::Circle;
-use iced::{
- slider, Alignment, Column, Container, Element, Length, Sandbox, Settings,
- Slider, Text,
-};
+use circle::circle;
+use iced::widget::{column, container, slider, text};
+use iced::{Alignment, Element, Length, Sandbox, Settings};
pub fn main() -> iced::Result {
Example::run(Settings::default())
@@ -85,7 +90,6 @@ pub fn main() -> iced::Result {
struct Example {
radius: f32,
- slider: slider::State,
}
#[derive(Debug, Clone, Copy)]
@@ -97,10 +101,7 @@ impl Sandbox for Example {
type Message = Message;
fn new() -> Self {
- Example {
- radius: 50.0,
- slider: slider::State::new(),
- }
+ Example { radius: 50.0 }
}
fn title(&self) -> String {
@@ -115,25 +116,18 @@ impl Sandbox for Example {
}
}
- fn view(&mut self) -> Element<Message> {
- let content = Column::new()
- .padding(20)
- .spacing(20)
- .max_width(500)
- .align_items(Alignment::Center)
- .push(Circle::new(self.radius))
- .push(Text::new(format!("Radius: {:.2}", self.radius)))
- .push(
- Slider::new(
- &mut self.slider,
- 1.0..=100.0,
- self.radius,
- Message::RadiusChanged,
- )
- .step(0.01),
- );
-
- Container::new(content)
+ fn view(&self) -> Element<Message> {
+ let content = column![
+ circle(self.radius),
+ text(format!("Radius: {:.2}", self.radius)),
+ slider(1.0..=100.0, self.radius, Message::RadiusChanged).step(0.01),
+ ]
+ .padding(20)
+ .spacing(20)
+ .max_width(500)
+ .align_items(Alignment::Center);
+
+ container(content)
.width(Length::Fill)
.height(Length::Fill)
.center_x()
diff --git a/examples/download_progress/src/download.rs b/examples/download_progress/src/download.rs
index 7db1206b..39dd843f 100644
--- a/examples/download_progress/src/download.rs
+++ b/examples/download_progress/src/download.rs
@@ -70,9 +70,7 @@ async fn download<I: Copy>(
// We do not let the stream die, as it would start a
// new download repeatedly if the user is not careful
// in case of errors.
- let _: () = iced::futures::future::pending().await;
-
- unreachable!()
+ iced::futures::future::pending().await
}
}
}
diff --git a/examples/download_progress/src/main.rs b/examples/download_progress/src/main.rs
index 21804a0a..3ef9ef7a 100644
--- a/examples/download_progress/src/main.rs
+++ b/examples/download_progress/src/main.rs
@@ -1,6 +1,8 @@
+use iced::executor;
+use iced::widget::{button, column, container, progress_bar, text, Column};
use iced::{
- button, executor, Alignment, Application, Button, Column, Command,
- Container, Element, Length, ProgressBar, Settings, Subscription, Text,
+ Alignment, Application, Command, Element, Length, Settings, Subscription,
+ Theme,
};
mod download;
@@ -13,7 +15,6 @@ pub fn main() -> iced::Result {
struct Example {
downloads: Vec<Download>,
last_id: usize,
- add: button::State,
}
#[derive(Debug, Clone)]
@@ -24,8 +25,9 @@ pub enum Message {
}
impl Application for Example {
- type Executor = executor::Default;
type Message = Message;
+ type Theme = Theme;
+ type Executor = executor::Default;
type Flags = ();
fn new(_flags: ()) -> (Example, Command<Message>) {
@@ -33,7 +35,6 @@ impl Application for Example {
Example {
downloads: vec![Download::new(0)],
last_id: 0,
- add: button::State::new(),
},
Command::none(),
)
@@ -46,7 +47,7 @@ impl Application for Example {
fn update(&mut self, message: Message) -> Command<Message> {
match message {
Message::Add => {
- self.last_id = self.last_id + 1;
+ self.last_id += 1;
self.downloads.push(Download::new(self.last_id));
}
@@ -71,21 +72,19 @@ impl Application for Example {
Subscription::batch(self.downloads.iter().map(Download::subscription))
}
- fn view(&mut self) -> Element<Message> {
- let downloads = self
- .downloads
- .iter_mut()
- .fold(Column::new().spacing(20), |column, download| {
- column.push(download.view())
- })
- .push(
- Button::new(&mut self.add, Text::new("Add another download"))
- .on_press(Message::Add)
- .padding(10),
- )
- .align_items(Alignment::End);
-
- Container::new(downloads)
+ fn view(&self) -> Element<Message> {
+ let downloads = Column::with_children(
+ self.downloads.iter().map(Download::view).collect(),
+ )
+ .push(
+ button("Add another download")
+ .on_press(Message::Add)
+ .padding(10),
+ )
+ .spacing(20)
+ .align_items(Alignment::End);
+
+ container(downloads)
.width(Length::Fill)
.height(Length::Fill)
.center_x()
@@ -103,19 +102,17 @@ struct Download {
#[derive(Debug)]
enum State {
- Idle { button: button::State },
+ Idle,
Downloading { progress: f32 },
- Finished { button: button::State },
- Errored { button: button::State },
+ Finished,
+ Errored,
}
impl Download {
pub fn new(id: usize) -> Self {
Download {
id,
- state: State::Idle {
- button: button::State::new(),
- },
+ state: State::Idle,
}
}
@@ -131,8 +128,8 @@ impl Download {
}
pub fn progress(&mut self, new_progress: download::Progress) {
- match &mut self.state {
- State::Downloading { progress } => match new_progress {
+ if let State::Downloading { progress } = &mut self.state {
+ match new_progress {
download::Progress::Started => {
*progress = 0.0;
}
@@ -140,17 +137,12 @@ impl Download {
*progress = percentage;
}
download::Progress::Finished => {
- self.state = State::Finished {
- button: button::State::new(),
- }
+ self.state = State::Finished;
}
download::Progress::Errored => {
- self.state = State::Errored {
- button: button::State::new(),
- };
+ self.state = State::Errored;
}
- },
- _ => {}
+ }
}
}
@@ -164,7 +156,7 @@ impl Download {
}
}
- pub fn view(&mut self) -> Element<Message> {
+ pub fn view(&self) -> Element<Message> {
let current_progress = match &self.state {
State::Idle { .. } => 0.0,
State::Downloading { progress } => *progress,
@@ -172,36 +164,28 @@ impl Download {
State::Errored { .. } => 0.0,
};
- let progress_bar = ProgressBar::new(0.0..=100.0, current_progress);
+ let progress_bar = progress_bar(0.0..=100.0, current_progress);
- let control: Element<_> = match &mut self.state {
- State::Idle { button } => {
- Button::new(button, Text::new("Start the download!"))
- .on_press(Message::Download(self.id))
+ let control: Element<_> = match &self.state {
+ State::Idle => button("Start the download!")
+ .on_press(Message::Download(self.id))
+ .into(),
+ State::Finished => {
+ column!["Download finished!", button("Start again")]
+ .spacing(10)
+ .align_items(Alignment::Center)
.into()
}
- State::Finished { button } => Column::new()
- .spacing(10)
- .align_items(Alignment::Center)
- .push(Text::new("Download finished!"))
- .push(
- Button::new(button, Text::new("Start again"))
- .on_press(Message::Download(self.id)),
- )
- .into(),
State::Downloading { .. } => {
- Text::new(format!("Downloading... {:.2}%", current_progress))
- .into()
+ text(format!("Downloading... {:.2}%", current_progress)).into()
}
- State::Errored { button } => Column::new()
- .spacing(10)
- .align_items(Alignment::Center)
- .push(Text::new("Something went wrong :("))
- .push(
- Button::new(button, Text::new("Try again"))
- .on_press(Message::Download(self.id)),
- )
- .into(),
+ State::Errored => column![
+ "Something went wrong :(",
+ button("Try again").on_press(Message::Download(self.id)),
+ ]
+ .spacing(10)
+ .align_items(Alignment::Center)
+ .into(),
};
Column::new()
diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs
index 7f024c56..234e1423 100644
--- a/examples/events/src/main.rs
+++ b/examples/events/src/main.rs
@@ -1,6 +1,9 @@
+use iced::alignment;
+use iced::executor;
+use iced::widget::{button, checkbox, container, text, Column};
use iced::{
- alignment, button, executor, Alignment, Application, Button, Checkbox,
- Column, Command, Container, Element, Length, Settings, Subscription, Text,
+ Alignment, Application, Command, Element, Length, Settings, Subscription,
+ Theme,
};
use iced_native::{window, Event};
@@ -15,7 +18,6 @@ pub fn main() -> iced::Result {
struct Events {
last: Vec<iced_native::Event>,
enabled: bool,
- exit: button::State,
should_exit: bool,
}
@@ -27,8 +29,9 @@ enum Message {
}
impl Application for Events {
- type Executor = executor::Default;
type Message = Message;
+ type Theme = Theme;
+ type Executor = executor::Default;
type Flags = ();
fn new(_flags: ()) -> (Events, Command<Message>) {
@@ -72,23 +75,23 @@ impl Application for Events {
self.should_exit
}
- fn view(&mut self) -> Element<Message> {
- let events = self.last.iter().fold(
- Column::new().spacing(10),
- |column, event| {
- column.push(Text::new(format!("{:?}", event)).size(40))
- },
+ fn view(&self) -> Element<Message> {
+ let events = Column::with_children(
+ self.last
+ .iter()
+ .map(|event| text(format!("{:?}", event)).size(40))
+ .map(Element::from)
+ .collect(),
);
- let toggle = Checkbox::new(
- self.enabled,
+ let toggle = checkbox(
"Listen to runtime events",
+ self.enabled,
Message::Toggled,
);
- let exit = Button::new(
- &mut self.exit,
- Text::new("Exit")
+ let exit = button(
+ text("Exit")
.width(Length::Fill)
.horizontal_alignment(alignment::Horizontal::Center),
)
@@ -103,7 +106,7 @@ impl Application for Events {
.push(toggle)
.push(exit);
- Container::new(content)
+ container(content)
.width(Length::Fill)
.height(Length::Fill)
.center_x()
diff --git a/examples/exit/src/main.rs b/examples/exit/src/main.rs
index c45a8205..5d518d2f 100644
--- a/examples/exit/src/main.rs
+++ b/examples/exit/src/main.rs
@@ -1,7 +1,5 @@
-use iced::{
- button, Alignment, Button, Column, Container, Element, Length, Sandbox,
- Settings, Text,
-};
+use iced::widget::{button, column, container};
+use iced::{Alignment, Element, Length, Sandbox, Settings};
pub fn main() -> iced::Result {
Exit::run(Settings::default())
@@ -11,8 +9,6 @@ pub fn main() -> iced::Result {
struct Exit {
show_confirm: bool,
exit: bool,
- confirm_button: button::State,
- exit_button: button::State,
}
#[derive(Debug, Clone, Copy)]
@@ -47,33 +43,24 @@ impl Sandbox for Exit {
}
}
- fn view(&mut self) -> Element<Message> {
+ fn view(&self) -> Element<Message> {
let content = if self.show_confirm {
- Column::new()
- .spacing(10)
- .align_items(Alignment::Center)
- .push(Text::new("Are you sure you want to exit?"))
- .push(
- Button::new(
- &mut self.confirm_button,
- Text::new("Yes, exit now"),
- )
+ column![
+ "Are you sure you want to exit?",
+ button("Yes, exit now")
.padding([10, 20])
.on_press(Message::Confirm),
- )
+ ]
} else {
- Column::new()
- .spacing(10)
- .align_items(Alignment::Center)
- .push(Text::new("Click the button to exit"))
- .push(
- Button::new(&mut self.exit_button, Text::new("Exit"))
- .padding([10, 20])
- .on_press(Message::Exit),
- )
- };
+ column![
+ "Click the button to exit",
+ button("Exit").padding([10, 20]).on_press(Message::Exit),
+ ]
+ }
+ .spacing(10)
+ .align_items(Alignment::Center);
- Container::new(content)
+ container(content)
.width(Length::Fill)
.height(Length::Fill)
.padding(20)
diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs
index ab8b80e4..a2030275 100644
--- a/examples/game_of_life/src/main.rs
+++ b/examples/game_of_life/src/main.rs
@@ -1,20 +1,20 @@
//! This example showcases an interactive version of the Game of Life, invented
//! by John Conway. It leverages a `Canvas` together with other widgets.
mod preset;
-mod style;
use grid::Grid;
-use iced::button::{self, Button};
+use preset::Preset;
+
use iced::executor;
-use iced::pick_list::{self, PickList};
-use iced::slider::{self, Slider};
+use iced::theme::{self, Theme};
use iced::time;
+use iced::widget::{
+ button, checkbox, column, container, pick_list, row, slider, text,
+};
use iced::window;
use iced::{
- Alignment, Application, Checkbox, Column, Command, Container, Element,
- Length, Row, Settings, Subscription, Text,
+ Alignment, Application, Command, Element, Length, Settings, Subscription,
};
-use preset::Preset;
use std::time::{Duration, Instant};
pub fn main() -> iced::Result {
@@ -33,7 +33,6 @@ pub fn main() -> iced::Result {
#[derive(Default)]
struct GameOfLife {
grid: Grid,
- controls: Controls,
is_playing: bool,
queued_ticks: usize,
speed: usize,
@@ -55,6 +54,7 @@ enum Message {
impl Application for GameOfLife {
type Message = Message;
+ type Theme = Theme;
type Executor = executor::Default;
type Flags = ();
@@ -131,39 +131,87 @@ impl Application for GameOfLife {
}
}
- fn view(&mut self) -> Element<Message> {
+ fn view(&self) -> Element<Message> {
let version = self.version;
let selected_speed = self.next_speed.unwrap_or(self.speed);
- let controls = self.controls.view(
+ let controls = view_controls(
self.is_playing,
self.grid.are_lines_visible(),
selected_speed,
self.grid.preset(),
);
- let content = Column::new()
- .push(
- self.grid
- .view()
- .map(move |message| Message::Grid(message, version)),
- )
- .push(controls);
+ let content = column![
+ self.grid
+ .view()
+ .map(move |message| Message::Grid(message, version)),
+ controls
+ ];
- Container::new(content)
+ container(content)
.width(Length::Fill)
.height(Length::Fill)
- .style(style::Container)
.into()
}
+
+ fn theme(&self) -> Theme {
+ Theme::Dark
+ }
+}
+
+fn view_controls<'a>(
+ is_playing: bool,
+ is_grid_enabled: bool,
+ speed: usize,
+ preset: Preset,
+) -> Element<'a, Message> {
+ let playback_controls = row![
+ button(if is_playing { "Pause" } else { "Play" })
+ .on_press(Message::TogglePlayback),
+ button("Next")
+ .on_press(Message::Next)
+ .style(theme::Button::Secondary),
+ ]
+ .spacing(10);
+
+ let speed_controls = row![
+ slider(1.0..=1000.0, speed as f32, Message::SpeedChanged),
+ text(format!("x{}", speed)).size(16),
+ ]
+ .width(Length::Fill)
+ .align_items(Alignment::Center)
+ .spacing(10);
+
+ row![
+ playback_controls,
+ speed_controls,
+ checkbox("Grid", is_grid_enabled, Message::ToggleGrid)
+ .size(16)
+ .spacing(5)
+ .text_size(16),
+ pick_list(preset::ALL, Some(preset), Message::PresetPicked)
+ .padding(8)
+ .text_size(16),
+ button("Clear")
+ .on_press(Message::Clear)
+ .style(theme::Button::Destructive),
+ ]
+ .padding(10)
+ .spacing(20)
+ .align_items(Alignment::Center)
+ .into()
}
mod grid {
use crate::Preset;
+ use iced::widget::canvas;
+ use iced::widget::canvas::event::{self, Event};
+ use iced::widget::canvas::{
+ Cache, Canvas, Cursor, Frame, Geometry, Path, Text,
+ };
use iced::{
- alignment,
- canvas::event::{self, Event},
- canvas::{self, Cache, Canvas, Cursor, Frame, Geometry, Path, Text},
- mouse, Color, Element, Length, Point, Rectangle, Size, Vector,
+ alignment, mouse, Color, Element, Length, Point, Rectangle, Size,
+ Theme, Vector,
};
use rustc_hash::{FxHashMap, FxHashSet};
use std::future::Future;
@@ -173,7 +221,6 @@ mod grid {
pub struct Grid {
state: State,
preset: Preset,
- interaction: Interaction,
life_cache: Cache,
grid_cache: Cache,
translation: Vector,
@@ -187,6 +234,8 @@ mod grid {
pub enum Message {
Populate(Cell),
Unpopulate(Cell),
+ Translated(Vector),
+ Scaled(f32, Option<Vector>),
Ticked {
result: Result<Life, TickError>,
tick_duration: Duration,
@@ -218,7 +267,6 @@ mod grid {
.collect(),
),
preset,
- interaction: Interaction::None,
life_cache: Cache::default(),
grid_cache: Cache::default(),
translation: Vector::default(),
@@ -263,6 +311,22 @@ mod grid {
self.preset = Preset::Custom;
}
+ Message::Translated(translation) => {
+ self.translation = translation;
+
+ self.life_cache.clear();
+ self.grid_cache.clear();
+ }
+ Message::Scaled(scaling, translation) => {
+ self.scaling = scaling;
+
+ if let Some(translation) = translation {
+ self.translation = translation;
+ }
+
+ self.life_cache.clear();
+ self.grid_cache.clear();
+ }
Message::Ticked {
result: Ok(life),
tick_duration,
@@ -280,7 +344,7 @@ mod grid {
}
}
- pub fn view<'a>(&'a mut self) -> Element<'a, Message> {
+ pub fn view(&self) -> Element<Message> {
Canvas::new(self)
.width(Length::Fill)
.height(Length::Fill)
@@ -328,15 +392,18 @@ mod grid {
}
}
- impl<'a> canvas::Program<Message> for Grid {
+ impl canvas::Program<Message> for Grid {
+ type State = Interaction;
+
fn update(
- &mut self,
+ &self,
+ interaction: &mut Interaction,
event: Event,
bounds: Rectangle,
cursor: Cursor,
) -> (event::Status, Option<Message>) {
if let Event::Mouse(mouse::Event::ButtonReleased(_)) = event {
- self.interaction = Interaction::None;
+ *interaction = Interaction::None;
}
let cursor_position =
@@ -360,7 +427,7 @@ mod grid {
mouse::Event::ButtonPressed(button) => {
let message = match button {
mouse::Button::Left => {
- self.interaction = if is_populated {
+ *interaction = if is_populated {
Interaction::Erasing
} else {
Interaction::Drawing
@@ -369,7 +436,7 @@ mod grid {
populate.or(unpopulate)
}
mouse::Button::Right => {
- self.interaction = Interaction::Panning {
+ *interaction = Interaction::Panning {
translation: self.translation,
start: cursor_position,
};
@@ -382,23 +449,20 @@ mod grid {
(event::Status::Captured, message)
}
mouse::Event::CursorMoved { .. } => {
- let message = match self.interaction {
+ let message = match *interaction {
Interaction::Drawing => populate,
Interaction::Erasing => unpopulate,
Interaction::Panning { translation, start } => {
- self.translation = translation
- + (cursor_position - start)
- * (1.0 / self.scaling);
-
- self.life_cache.clear();
- self.grid_cache.clear();
-
- None
+ Some(Message::Translated(
+ translation
+ + (cursor_position - start)
+ * (1.0 / self.scaling),
+ ))
}
_ => None,
};
- let event_status = match self.interaction {
+ let event_status = match interaction {
Interaction::None => event::Status::Ignored,
_ => event::Status::Captured,
};
@@ -413,30 +477,38 @@ mod grid {
{
let old_scaling = self.scaling;
- self.scaling = (self.scaling
- * (1.0 + y / 30.0))
+ let scaling = (self.scaling * (1.0 + y / 30.0))
.max(Self::MIN_SCALING)
.min(Self::MAX_SCALING);
- if let Some(cursor_to_center) =
- cursor.position_from(bounds.center())
- {
- let factor = self.scaling - old_scaling;
-
- self.translation = self.translation
- - Vector::new(
- cursor_to_center.x * factor
- / (old_scaling * old_scaling),
- cursor_to_center.y * factor
- / (old_scaling * old_scaling),
- );
- }
-
- self.life_cache.clear();
- self.grid_cache.clear();
+ let translation =
+ if let Some(cursor_to_center) =
+ cursor.position_from(bounds.center())
+ {
+ let factor = scaling - old_scaling;
+
+ Some(
+ self.translation
+ - Vector::new(
+ cursor_to_center.x * factor
+ / (old_scaling
+ * old_scaling),
+ cursor_to_center.y * factor
+ / (old_scaling
+ * old_scaling),
+ ),
+ )
+ } else {
+ None
+ };
+
+ (
+ event::Status::Captured,
+ Some(Message::Scaled(scaling, translation)),
+ )
+ } else {
+ (event::Status::Captured, None)
}
-
- (event::Status::Captured, None)
}
},
_ => (event::Status::Ignored, None),
@@ -445,7 +517,13 @@ mod grid {
}
}
- fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec<Geometry> {
+ fn draw(
+ &self,
+ _interaction: &Interaction,
+ _theme: &Theme,
+ bounds: Rectangle,
+ cursor: Cursor,
+ ) -> Vec<Geometry> {
let center = Vector::new(bounds.width / 2.0, bounds.height / 2.0);
let life = self.life_cache.draw(bounds.size(), |frame| {
@@ -571,10 +649,11 @@ mod grid {
fn mouse_interaction(
&self,
+ interaction: &Interaction,
bounds: Rectangle,
cursor: Cursor,
) -> mouse::Interaction {
- match self.interaction {
+ match interaction {
Interaction::Drawing => mouse::Interaction::Crosshair,
Interaction::Erasing => mouse::Interaction::Crosshair,
Interaction::Panning { .. } => mouse::Interaction::Grabbing,
@@ -803,90 +882,16 @@ mod grid {
}
}
- enum Interaction {
+ pub enum Interaction {
None,
Drawing,
Erasing,
Panning { translation: Vector, start: Point },
}
-}
-
-#[derive(Default)]
-struct Controls {
- toggle_button: button::State,
- next_button: button::State,
- clear_button: button::State,
- speed_slider: slider::State,
- preset_list: pick_list::State<Preset>,
-}
-impl Controls {
- fn view<'a>(
- &'a mut self,
- is_playing: bool,
- is_grid_enabled: bool,
- speed: usize,
- preset: Preset,
- ) -> Element<'a, Message> {
- let playback_controls = Row::new()
- .spacing(10)
- .push(
- Button::new(
- &mut self.toggle_button,
- Text::new(if is_playing { "Pause" } else { "Play" }),
- )
- .on_press(Message::TogglePlayback)
- .style(style::Button),
- )
- .push(
- Button::new(&mut self.next_button, Text::new("Next"))
- .on_press(Message::Next)
- .style(style::Button),
- );
-
- let speed_controls = Row::new()
- .width(Length::Fill)
- .align_items(Alignment::Center)
- .spacing(10)
- .push(
- Slider::new(
- &mut self.speed_slider,
- 1.0..=1000.0,
- speed as f32,
- Message::SpeedChanged,
- )
- .style(style::Slider),
- )
- .push(Text::new(format!("x{}", speed)).size(16));
-
- Row::new()
- .padding(10)
- .spacing(20)
- .align_items(Alignment::Center)
- .push(playback_controls)
- .push(speed_controls)
- .push(
- Checkbox::new(is_grid_enabled, "Grid", Message::ToggleGrid)
- .size(16)
- .spacing(5)
- .text_size(16),
- )
- .push(
- PickList::new(
- &mut self.preset_list,
- preset::ALL,
- Some(preset),
- Message::PresetPicked,
- )
- .padding(8)
- .text_size(16)
- .style(style::PickList),
- )
- .push(
- Button::new(&mut self.clear_button, Text::new("Clear"))
- .on_press(Message::Clear)
- .style(style::Clear),
- )
- .into()
+ impl Default for Interaction {
+ fn default() -> Self {
+ Self::None
+ }
}
}
diff --git a/examples/game_of_life/src/preset.rs b/examples/game_of_life/src/preset.rs
index 05157b6a..964b9120 100644
--- a/examples/game_of_life/src/preset.rs
+++ b/examples/game_of_life/src/preset.rs
@@ -1,7 +1,7 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Preset {
Custom,
- XKCD,
+ Xkcd,
Glider,
SmallExploder,
Exploder,
@@ -14,7 +14,7 @@ pub enum Preset {
pub static ALL: &[Preset] = &[
Preset::Custom,
- Preset::XKCD,
+ Preset::Xkcd,
Preset::Glider,
Preset::SmallExploder,
Preset::Exploder,
@@ -30,7 +30,7 @@ impl Preset {
#[rustfmt::skip]
let cells = match self {
Preset::Custom => vec![],
- Preset::XKCD => vec![
+ Preset::Xkcd => vec![
" xxx ",
" x x ",
" x x ",
@@ -116,7 +116,7 @@ impl Preset {
impl Default for Preset {
fn default() -> Preset {
- Preset::XKCD
+ Preset::Xkcd
}
}
@@ -127,7 +127,7 @@ impl std::fmt::Display for Preset {
"{}",
match self {
Preset::Custom => "Custom",
- Preset::XKCD => "xkcd #2293",
+ Preset::Xkcd => "xkcd #2293",
Preset::Glider => "Glider",
Preset::SmallExploder => "Small Exploder",
Preset::Exploder => "Exploder",
diff --git a/examples/game_of_life/src/style.rs b/examples/game_of_life/src/style.rs
deleted file mode 100644
index be9a0e96..00000000
--- a/examples/game_of_life/src/style.rs
+++ /dev/null
@@ -1,189 +0,0 @@
-use iced::{button, container, pick_list, slider, Background, Color};
-
-const ACTIVE: Color = Color::from_rgb(
- 0x72 as f32 / 255.0,
- 0x89 as f32 / 255.0,
- 0xDA as f32 / 255.0,
-);
-
-const DESTRUCTIVE: Color = Color::from_rgb(
- 0xC0 as f32 / 255.0,
- 0x47 as f32 / 255.0,
- 0x47 as f32 / 255.0,
-);
-
-const HOVERED: Color = Color::from_rgb(
- 0x67 as f32 / 255.0,
- 0x7B as f32 / 255.0,
- 0xC4 as f32 / 255.0,
-);
-
-const BACKGROUND: Color = Color::from_rgb(
- 0x2F as f32 / 255.0,
- 0x31 as f32 / 255.0,
- 0x36 as f32 / 255.0,
-);
-
-pub struct Container;
-
-impl container::StyleSheet for Container {
- fn style(&self) -> container::Style {
- container::Style {
- background: Some(Background::Color(Color::from_rgb8(
- 0x36, 0x39, 0x3F,
- ))),
- text_color: Some(Color::WHITE),
- ..container::Style::default()
- }
- }
-}
-
-pub struct Button;
-
-impl button::StyleSheet for Button {
- fn active(&self) -> button::Style {
- button::Style {
- background: Some(Background::Color(ACTIVE)),
- border_radius: 3.0,
- text_color: Color::WHITE,
- ..button::Style::default()
- }
- }
-
- fn hovered(&self) -> button::Style {
- button::Style {
- background: Some(Background::Color(HOVERED)),
- text_color: Color::WHITE,
- ..self.active()
- }
- }
-
- fn pressed(&self) -> button::Style {
- button::Style {
- border_width: 1.0,
- border_color: Color::WHITE,
- ..self.hovered()
- }
- }
-}
-
-pub struct Clear;
-
-impl button::StyleSheet for Clear {
- fn active(&self) -> button::Style {
- button::Style {
- background: Some(Background::Color(DESTRUCTIVE)),
- border_radius: 3.0,
- text_color: Color::WHITE,
- ..button::Style::default()
- }
- }
-
- fn hovered(&self) -> button::Style {
- button::Style {
- background: Some(Background::Color(Color {
- a: 0.5,
- ..DESTRUCTIVE
- })),
- text_color: Color::WHITE,
- ..self.active()
- }
- }
-
- fn pressed(&self) -> button::Style {
- button::Style {
- border_width: 1.0,
- border_color: Color::WHITE,
- ..self.hovered()
- }
- }
-}
-
-pub struct Slider;
-
-impl slider::StyleSheet for Slider {
- fn active(&self) -> slider::Style {
- slider::Style {
- rail_colors: (ACTIVE, Color { a: 0.1, ..ACTIVE }),
- handle: slider::Handle {
- shape: slider::HandleShape::Circle { radius: 9.0 },
- color: ACTIVE,
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- },
- }
- }
-
- fn hovered(&self) -> slider::Style {
- let active = self.active();
-
- slider::Style {
- handle: slider::Handle {
- color: HOVERED,
- ..active.handle
- },
- ..active
- }
- }
-
- fn dragging(&self) -> slider::Style {
- let active = self.active();
-
- slider::Style {
- handle: slider::Handle {
- color: Color::from_rgb(0.85, 0.85, 0.85),
- ..active.handle
- },
- ..active
- }
- }
-}
-
-pub struct PickList;
-
-impl pick_list::StyleSheet for PickList {
- fn menu(&self) -> pick_list::Menu {
- pick_list::Menu {
- text_color: Color::WHITE,
- background: BACKGROUND.into(),
- border_width: 1.0,
- border_color: Color {
- a: 0.7,
- ..Color::BLACK
- },
- selected_background: Color {
- a: 0.5,
- ..Color::BLACK
- }
- .into(),
- selected_text_color: Color::WHITE,
- }
- }
-
- fn active(&self) -> pick_list::Style {
- pick_list::Style {
- text_color: Color::WHITE,
- background: BACKGROUND.into(),
- border_width: 1.0,
- border_color: Color {
- a: 0.6,
- ..Color::BLACK
- },
- border_radius: 2.0,
- icon_size: 0.5,
- ..pick_list::Style::default()
- }
- }
-
- fn hovered(&self) -> pick_list::Style {
- let active = self.active();
-
- pick_list::Style {
- border_color: Color {
- a: 0.9,
- ..Color::BLACK
- },
- ..active
- }
- }
-}
diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs
index 58dfa3ad..d8b99ab3 100644
--- a/examples/geometry/src/main.rs
+++ b/examples/geometry/src/main.rs
@@ -13,10 +13,12 @@ mod rainbow {
use iced_graphics::renderer::{self, Renderer};
use iced_graphics::{Backend, Primitive};
+ use iced_native::widget::{self, Widget};
use iced_native::{
- layout, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget,
+ layout, Element, Layout, Length, Point, Rectangle, Size, Vector,
};
+ #[derive(Default)]
pub struct Rainbow;
impl Rainbow {
@@ -25,7 +27,11 @@ mod rainbow {
}
}
- impl<Message, B> Widget<Message, Renderer<B>> for Rainbow
+ pub fn rainbow() -> Rainbow {
+ Rainbow
+ }
+
+ impl<Message, B, T> Widget<Message, Renderer<B, T>> for Rainbow
where
B: Backend,
{
@@ -39,7 +45,7 @@ mod rainbow {
fn layout(
&self,
- _renderer: &Renderer<B>,
+ _renderer: &Renderer<B, T>,
limits: &layout::Limits,
) -> layout::Node {
let size = limits.width(Length::Fill).resolve(Size::ZERO);
@@ -49,7 +55,9 @@ mod rainbow {
fn draw(
&self,
- renderer: &mut Renderer<B>,
+ _tree: &widget::Tree,
+ renderer: &mut Renderer<B, T>,
+ _theme: &T,
_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
@@ -147,37 +155,31 @@ mod rainbow {
}
}
- impl<'a, Message, B> Into<Element<'a, Message, Renderer<B>>> for Rainbow
+ impl<'a, Message, B, T> From<Rainbow> for Element<'a, Message, Renderer<B, T>>
where
B: Backend,
{
- fn into(self) -> Element<'a, Message, Renderer<B>> {
- Element::new(self)
+ fn from(rainbow: Rainbow) -> Self {
+ Self::new(rainbow)
}
}
}
-use iced::{
- scrollable, Alignment, Column, Container, Element, Length, Sandbox,
- Scrollable, Settings, Text,
-};
-use rainbow::Rainbow;
+use iced::widget::{column, container, scrollable};
+use iced::{Alignment, Element, Length, Sandbox, Settings};
+use rainbow::rainbow;
pub fn main() -> iced::Result {
Example::run(Settings::default())
}
-struct Example {
- scroll: scrollable::State,
-}
+struct Example;
impl Sandbox for Example {
type Message = ();
fn new() -> Self {
- Example {
- scroll: scrollable::State::new(),
- }
+ Example
}
fn title(&self) -> String {
@@ -186,32 +188,27 @@ impl Sandbox for Example {
fn update(&mut self, _: ()) {}
- fn view(&mut self) -> Element<()> {
- let content = Column::new()
- .padding(20)
- .spacing(20)
- .max_width(500)
- .align_items(Alignment::Start)
- .push(Rainbow::new())
- .push(Text::new(
- "In this example we draw a custom widget Rainbow, using \
+ fn view(&self) -> Element<()> {
+ let content = column![
+ rainbow(),
+ "In this example we draw a custom widget Rainbow, using \
the Mesh2D primitive. This primitive supplies a list of \
triangles, expressed as vertices and indices.",
- ))
- .push(Text::new(
- "Move your cursor over it, and see the center vertex \
+ "Move your cursor over it, and see the center vertex \
follow you!",
- ))
- .push(Text::new(
- "Every Vertex2D defines its own color. You could use the \
+ "Every Vertex2D defines its own color. You could use the \
Mesh2D primitive to render virtually any two-dimensional \
geometry for your widget.",
- ));
+ ]
+ .padding(20)
+ .spacing(20)
+ .max_width(500)
+ .align_items(Alignment::Start);
- let scrollable = Scrollable::new(&mut self.scroll)
- .push(Container::new(content).width(Length::Fill).center_x());
+ let scrollable =
+ scrollable(container(content).width(Length::Fill).center_x());
- Container::new(scrollable)
+ container(scrollable)
.width(Length::Fill)
.height(Length::Fill)
.center_y()
diff --git a/examples/integration_opengl/src/controls.rs b/examples/integration_opengl/src/controls.rs
index f387b4e5..076d37d3 100644
--- a/examples/integration_opengl/src/controls.rs
+++ b/examples/integration_opengl/src/controls.rs
@@ -1,11 +1,10 @@
use iced_glow::Renderer;
-use iced_glutin::widget::slider::{self, Slider};
+use iced_glutin::widget::Slider;
use iced_glutin::widget::{Column, Row, Text};
use iced_glutin::{Alignment, Color, Command, Element, Length, Program};
pub struct Controls {
background_color: Color,
- sliders: [slider::State; 3],
}
#[derive(Debug, Clone)]
@@ -17,7 +16,6 @@ impl Controls {
pub fn new() -> Controls {
Controls {
background_color: Color::BLACK,
- sliders: Default::default(),
}
}
@@ -40,15 +38,14 @@ impl Program for Controls {
Command::none()
}
- fn view(&mut self) -> Element<Message, Renderer> {
- let [r, g, b] = &mut self.sliders;
+ fn view(&self) -> Element<Message, Renderer> {
let background_color = self.background_color;
let sliders = Row::new()
.width(Length::Units(500))
.spacing(20)
.push(
- Slider::new(r, 0.0..=1.0, background_color.r, move |r| {
+ Slider::new(0.0..=1.0, background_color.r, move |r| {
Message::BackgroundColorChanged(Color {
r,
..background_color
@@ -57,7 +54,7 @@ impl Program for Controls {
.step(0.01),
)
.push(
- Slider::new(g, 0.0..=1.0, background_color.g, move |g| {
+ Slider::new(0.0..=1.0, background_color.g, move |g| {
Message::BackgroundColorChanged(Color {
g,
..background_color
@@ -66,7 +63,7 @@ impl Program for Controls {
.step(0.01),
)
.push(
- Slider::new(b, 0.0..=1.0, background_color.b, move |b| {
+ Slider::new(0.0..=1.0, background_color.b, move |b| {
Message::BackgroundColorChanged(Color {
b,
..background_color
@@ -89,13 +86,13 @@ impl Program for Controls {
.spacing(10)
.push(
Text::new("Background color")
- .color(Color::WHITE),
+ .style(Color::WHITE),
)
.push(sliders)
.push(
Text::new(format!("{:?}", background_color))
.size(14)
- .color(Color::WHITE),
+ .style(Color::WHITE),
),
),
)
diff --git a/examples/integration_opengl/src/main.rs b/examples/integration_opengl/src/main.rs
index 1007b90f..f161c8a0 100644
--- a/examples/integration_opengl/src/main.rs
+++ b/examples/integration_opengl/src/main.rs
@@ -12,7 +12,8 @@ use iced_glow::glow;
use iced_glow::{Backend, Renderer, Settings, Viewport};
use iced_glutin::conversion;
use iced_glutin::glutin;
-use iced_glutin::{program, Clipboard, Debug, Size};
+use iced_glutin::renderer;
+use iced_glutin::{program, Clipboard, Color, Debug, Size};
pub fn main() {
env_logger::init();
@@ -57,7 +58,7 @@ pub fn main() {
let mut cursor_position = PhysicalPosition::new(-1.0, -1.0);
let mut modifiers = ModifiersState::default();
- let mut clipboard = Clipboard::connect(&windowed_context.window());
+ let mut clipboard = Clipboard::connect(windowed_context.window());
let mut renderer = Renderer::new(Backend::new(&gl, Settings::default()));
@@ -72,13 +73,12 @@ pub fn main() {
);
let mut resized = false;
- let scene = Scene::new(&gl, &shader_version);
+ let scene = Scene::new(&gl, shader_version);
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
- Event::LoopDestroyed => return,
Event::WindowEvent { event, .. } => {
match event {
WindowEvent::CursorMoved { position, .. } => {
@@ -125,6 +125,10 @@ pub fn main() {
viewport.scale_factor(),
),
&mut renderer,
+ &iced_glow::Theme::Dark,
+ &renderer::Style {
+ text_color: Color::WHITE,
+ },
&mut clipboard,
&mut debug,
);
diff --git a/examples/integration_wgpu/README.md b/examples/integration_wgpu/README.md
index faefa153..ece9ba1e 100644
--- a/examples/integration_wgpu/README.md
+++ b/examples/integration_wgpu/README.md
@@ -12,7 +12,7 @@ The __[`main`]__ file contains all the code of the example.
You can run it with `cargo run`:
```
-cargo run --package integration
+cargo run --package integration_wgpu
```
### How to run this example with WebGL backend
diff --git a/examples/integration_wgpu/src/controls.rs b/examples/integration_wgpu/src/controls.rs
index 9bca40eb..6c41738c 100644
--- a/examples/integration_wgpu/src/controls.rs
+++ b/examples/integration_wgpu/src/controls.rs
@@ -1,14 +1,10 @@
use iced_wgpu::Renderer;
-use iced_winit::widget::slider::{self, Slider};
-use iced_winit::widget::text_input::{self, TextInput};
-use iced_winit::widget::{Column, Row, Text};
+use iced_winit::widget::{slider, text_input, Column, Row, Text};
use iced_winit::{Alignment, Color, Command, Element, Length, Program};
pub struct Controls {
background_color: Color,
text: String,
- sliders: [slider::State; 3],
- text_input: text_input::State,
}
#[derive(Debug, Clone)]
@@ -22,8 +18,6 @@ impl Controls {
Controls {
background_color: Color::BLACK,
text: Default::default(),
- sliders: Default::default(),
- text_input: Default::default(),
}
}
@@ -49,9 +43,7 @@ impl Program for Controls {
Command::none()
}
- fn view(&mut self) -> Element<Message, Renderer> {
- let [r, g, b] = &mut self.sliders;
- let t = &mut self.text_input;
+ fn view(&self) -> Element<Message, Renderer> {
let background_color = self.background_color;
let text = &self.text;
@@ -59,7 +51,7 @@ impl Program for Controls {
.width(Length::Units(500))
.spacing(20)
.push(
- Slider::new(r, 0.0..=1.0, background_color.r, move |r| {
+ slider(0.0..=1.0, background_color.r, move |r| {
Message::BackgroundColorChanged(Color {
r,
..background_color
@@ -68,7 +60,7 @@ impl Program for Controls {
.step(0.01),
)
.push(
- Slider::new(g, 0.0..=1.0, background_color.g, move |g| {
+ slider(0.0..=1.0, background_color.g, move |g| {
Message::BackgroundColorChanged(Color {
g,
..background_color
@@ -77,7 +69,7 @@ impl Program for Controls {
.step(0.01),
)
.push(
- Slider::new(b, 0.0..=1.0, background_color.b, move |b| {
+ slider(0.0..=1.0, background_color.b, move |b| {
Message::BackgroundColorChanged(Color {
b,
..background_color
@@ -100,19 +92,18 @@ impl Program for Controls {
.spacing(10)
.push(
Text::new("Background color")
- .color(Color::WHITE),
+ .style(Color::WHITE),
)
.push(sliders)
.push(
Text::new(format!("{:?}", background_color))
.size(14)
- .color(Color::WHITE),
+ .style(Color::WHITE),
)
- .push(TextInput::new(
- t,
+ .push(text_input(
"Placeholder",
text,
- move |text| Message::TextChanged(text),
+ Message::TextChanged,
)),
),
)
diff --git a/examples/integration_wgpu/src/main.rs b/examples/integration_wgpu/src/main.rs
index 045ee0d3..86a0d6a4 100644
--- a/examples/integration_wgpu/src/main.rs
+++ b/examples/integration_wgpu/src/main.rs
@@ -5,9 +5,11 @@ use controls::Controls;
use scene::Scene;
use iced_wgpu::{wgpu, Backend, Renderer, Settings, Viewport};
-use iced_winit::{conversion, futures, program, winit, Clipboard, Debug, Size};
+use iced_winit::{
+ conversion, futures, program, renderer, winit, Clipboard, Color, Debug,
+ Size,
+};
-use futures::task::SpawnExt;
use winit::{
dpi::PhysicalPosition,
event::{Event, ModifiersState, WindowEvent},
@@ -71,7 +73,7 @@ pub fn main() {
let instance = wgpu::Instance::new(backend);
let surface = unsafe { instance.create_surface(&window) };
- let (format, (mut device, queue)) = futures::executor::block_on(async {
+ let (format, (device, queue)) = futures::executor::block_on(async {
let adapter = wgpu::util::initialize_adapter_from_env_or_default(
&instance,
backend,
@@ -91,7 +93,9 @@ pub fn main() {
(
surface
- .get_preferred_format(&adapter)
+ .get_supported_formats(&adapter)
+ .first()
+ .copied()
.expect("Get preferred format"),
adapter
.request_device(
@@ -114,24 +118,23 @@ pub fn main() {
format,
width: physical_size.width,
height: physical_size.height,
- present_mode: wgpu::PresentMode::Mailbox,
+ present_mode: wgpu::PresentMode::AutoVsync,
},
);
let mut resized = false;
- // Initialize staging belt and local pool
+ // Initialize staging belt
let mut staging_belt = wgpu::util::StagingBelt::new(5 * 1024);
- let mut local_pool = futures::executor::LocalPool::new();
// Initialize scene and GUI controls
- let scene = Scene::new(&mut device, format);
+ let scene = Scene::new(&device, format);
let controls = Controls::new();
// Initialize iced
let mut debug = Debug::new();
let mut renderer =
- Renderer::new(Backend::new(&mut device, Settings::default(), format));
+ Renderer::new(Backend::new(&device, Settings::default(), format));
let mut state = program::State::new(
controls,
@@ -188,6 +191,8 @@ pub fn main() {
viewport.scale_factor(),
),
&mut renderer,
+ &iced_wgpu::Theme::Dark,
+ &renderer::Style { text_color: Color::WHITE },
&mut clipboard,
&mut debug,
);
@@ -203,11 +208,11 @@ pub fn main() {
surface.configure(
&device,
&wgpu::SurfaceConfiguration {
+ format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
- format: format,
width: size.width,
height: size.height,
- present_mode: wgpu::PresentMode::Mailbox,
+ present_mode: wgpu::PresentMode::AutoVsync,
},
);
@@ -239,7 +244,7 @@ pub fn main() {
// And then iced on top
renderer.with_primitives(|backend, primitive| {
backend.present(
- &mut device,
+ &device,
&mut staging_belt,
&mut encoder,
&view,
@@ -262,12 +267,8 @@ pub fn main() {
);
// And recall staging buffers
- local_pool
- .spawner()
- .spawn(staging_belt.recall())
- .expect("Recall staging buffers");
+ staging_belt.recall();
- local_pool.run_until_stalled();
}
Err(error) => match error {
wgpu::SurfaceError::OutOfMemory => {
diff --git a/examples/integration_wgpu/src/scene.rs b/examples/integration_wgpu/src/scene.rs
index fbda1326..3e41fbda 100644
--- a/examples/integration_wgpu/src/scene.rs
+++ b/examples/integration_wgpu/src/scene.rs
@@ -23,7 +23,7 @@ impl Scene {
) -> wgpu::RenderPass<'a> {
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
- color_attachments: &[wgpu::RenderPassColorAttachment {
+ color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: target,
resolve_target: None,
ops: wgpu::Operations {
@@ -39,7 +39,7 @@ impl Scene {
}),
store: true,
},
- }],
+ })],
depth_stencil_attachment: None,
})
}
@@ -55,8 +55,8 @@ fn build_pipeline(
texture_format: wgpu::TextureFormat,
) -> wgpu::RenderPipeline {
let (vs_module, fs_module) = (
- device.create_shader_module(&wgpu::include_wgsl!("shader/vert.wgsl")),
- device.create_shader_module(&wgpu::include_wgsl!("shader/frag.wgsl")),
+ device.create_shader_module(wgpu::include_wgsl!("shader/vert.wgsl")),
+ device.create_shader_module(wgpu::include_wgsl!("shader/frag.wgsl")),
);
let pipeline_layout =
@@ -66,40 +66,37 @@ fn build_pipeline(
bind_group_layouts: &[],
});
- let pipeline =
- device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
- label: None,
- layout: Some(&pipeline_layout),
- vertex: wgpu::VertexState {
- module: &vs_module,
- entry_point: "main",
- buffers: &[],
- },
- fragment: Some(wgpu::FragmentState {
- module: &fs_module,
- entry_point: "main",
- targets: &[wgpu::ColorTargetState {
- format: texture_format,
- blend: Some(wgpu::BlendState {
- color: wgpu::BlendComponent::REPLACE,
- alpha: wgpu::BlendComponent::REPLACE,
- }),
- write_mask: wgpu::ColorWrites::ALL,
- }],
- }),
- primitive: wgpu::PrimitiveState {
- topology: wgpu::PrimitiveTopology::TriangleList,
- front_face: wgpu::FrontFace::Ccw,
- ..Default::default()
- },
- depth_stencil: None,
- multisample: wgpu::MultisampleState {
- count: 1,
- mask: !0,
- alpha_to_coverage_enabled: false,
- },
- multiview: None,
- });
-
- pipeline
+ device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
+ label: None,
+ layout: Some(&pipeline_layout),
+ vertex: wgpu::VertexState {
+ module: &vs_module,
+ entry_point: "main",
+ buffers: &[],
+ },
+ fragment: Some(wgpu::FragmentState {
+ module: &fs_module,
+ entry_point: "main",
+ targets: &[Some(wgpu::ColorTargetState {
+ format: texture_format,
+ blend: Some(wgpu::BlendState {
+ color: wgpu::BlendComponent::REPLACE,
+ alpha: wgpu::BlendComponent::REPLACE,
+ }),
+ write_mask: wgpu::ColorWrites::ALL,
+ })],
+ }),
+ primitive: wgpu::PrimitiveState {
+ topology: wgpu::PrimitiveTopology::TriangleList,
+ front_face: wgpu::FrontFace::Ccw,
+ ..Default::default()
+ },
+ depth_stencil: None,
+ multisample: wgpu::MultisampleState {
+ count: 1,
+ mask: !0,
+ alpha_to_coverage_enabled: false,
+ },
+ multiview: None,
+ })
}
diff --git a/examples/integration_wgpu/src/shader/frag.wgsl b/examples/integration_wgpu/src/shader/frag.wgsl
index a6f61336..cf27bb56 100644
--- a/examples/integration_wgpu/src/shader/frag.wgsl
+++ b/examples/integration_wgpu/src/shader/frag.wgsl
@@ -1,4 +1,4 @@
-[[stage(fragment)]]
-fn main() -> [[location(0)]] vec4<f32> {
+@fragment
+fn main() -> @location(0) vec4<f32> {
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
}
diff --git a/examples/integration_wgpu/src/shader/vert.wgsl b/examples/integration_wgpu/src/shader/vert.wgsl
index 7ef47fb2..e353e6ba 100644
--- a/examples/integration_wgpu/src/shader/vert.wgsl
+++ b/examples/integration_wgpu/src/shader/vert.wgsl
@@ -1,5 +1,5 @@
-[[stage(vertex)]]
-fn main([[builtin(vertex_index)]] in_vertex_index: u32) -> [[builtin(position)]] vec4<f32> {
+@vertex
+fn main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4<f32> {
let x = f32(1 - i32(in_vertex_index)) * 0.5;
let y = f32(1 - i32(in_vertex_index & 1u) * 2) * 0.5;
return vec4<f32>(x, y, 0.0, 1.0);
diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs
index 2962ca25..ae8fa22b 100644
--- a/examples/pane_grid/src/main.rs
+++ b/examples/pane_grid/src/main.rs
@@ -1,14 +1,13 @@
use iced::alignment::{self, Alignment};
-use iced::button::{self, Button};
use iced::executor;
use iced::keyboard;
-use iced::pane_grid::{self, PaneGrid};
-use iced::scrollable::{self, Scrollable};
+use iced::theme::{self, Theme};
+use iced::widget::pane_grid::{self, PaneGrid};
+use iced::widget::{button, column, container, row, scrollable, text};
use iced::{
- Application, Color, Column, Command, Container, Element, Length, Row,
- Settings, Size, Subscription, Text,
+ Application, Color, Command, Element, Length, Settings, Size, Subscription,
};
-use iced_lazy::responsive::{self, Responsive};
+use iced_lazy::responsive;
use iced_native::{event, subscription, Event};
pub fn main() -> iced::Result {
@@ -36,6 +35,7 @@ enum Message {
impl Application for Example {
type Message = Message;
+ type Theme = Theme;
type Executor = executor::Default;
type Flags = ();
@@ -153,57 +153,47 @@ impl Application for Example {
})
}
- fn view(&mut self) -> Element<Message> {
+ fn view(&self) -> Element<Message> {
let focus = self.focus;
let total_panes = self.panes.len();
- let pane_grid = PaneGrid::new(&mut self.panes, |id, pane| {
+ let pane_grid = PaneGrid::new(&self.panes, |id, pane| {
let is_focused = focus == Some(id);
- let Pane {
- responsive,
+ let pin_button = button(
+ text(if pane.is_pinned { "Unpin" } else { "Pin" }).size(14),
+ )
+ .on_press(Message::TogglePin(id))
+ .padding(3);
+
+ let title = row![
pin_button,
- is_pinned,
- content,
- ..
- } = pane;
-
- let text = if *is_pinned { "Unpin" } else { "Pin" };
- let pin_button = Button::new(pin_button, Text::new(text).size(14))
- .on_press(Message::TogglePin(id))
- .style(style::Button::Pin)
- .padding(3);
-
- let title = Row::with_children(vec![
- pin_button.into(),
- Text::new("Pane").into(),
- Text::new(content.id.to_string())
- .color(if is_focused {
- PANE_ID_COLOR_FOCUSED
- } else {
- PANE_ID_COLOR_UNFOCUSED
- })
- .into(),
- ])
+ "Pane",
+ text(pane.id.to_string()).style(if is_focused {
+ PANE_ID_COLOR_FOCUSED
+ } else {
+ PANE_ID_COLOR_UNFOCUSED
+ }),
+ ]
.spacing(5);
let title_bar = pane_grid::TitleBar::new(title)
- .controls(pane.controls.view(id, total_panes, *is_pinned))
+ .controls(view_controls(id, total_panes, pane.is_pinned))
.padding(10)
.style(if is_focused {
- style::TitleBar::Focused
+ style::title_bar_focused
} else {
- style::TitleBar::Active
+ style::title_bar_active
});
- pane_grid::Content::new(Responsive::new(responsive, move |size| {
- content.view(id, total_panes, *is_pinned, size)
+ pane_grid::Content::new(responsive(move |size| {
+ view_content(id, total_panes, pane.is_pinned, size)
}))
.title_bar(title_bar)
.style(if is_focused {
- style::Pane::Focused
+ style::pane_focused
} else {
- style::Pane::Active
+ style::pane_active
})
})
.width(Length::Fill)
@@ -213,7 +203,7 @@ impl Application for Example {
.on_drag(Message::Dragged)
.on_resize(10, Message::Resized);
- Container::new(pane_grid)
+ container(pane_grid)
.width(Length::Fill)
.height(Length::Fill)
.padding(10)
@@ -253,247 +243,132 @@ fn handle_hotkey(key_code: keyboard::KeyCode) -> Option<Message> {
}
struct Pane {
- pub responsive: responsive::State,
- pub is_pinned: bool,
- pub pin_button: button::State,
- pub content: Content,
- pub controls: Controls,
-}
-
-struct Content {
id: usize,
- scroll: scrollable::State,
- split_horizontally: button::State,
- split_vertically: button::State,
- close: button::State,
-}
-
-struct Controls {
- close: button::State,
+ pub is_pinned: bool,
}
impl Pane {
fn new(id: usize) -> Self {
Self {
- responsive: responsive::State::new(),
+ id,
is_pinned: false,
- pin_button: button::State::new(),
- content: Content::new(id),
- controls: Controls::new(),
}
}
}
-impl Content {
- fn new(id: usize) -> Self {
- Content {
- id,
- scroll: scrollable::State::new(),
- split_horizontally: button::State::new(),
- split_vertically: button::State::new(),
- close: button::State::new(),
- }
+fn view_content<'a>(
+ pane: pane_grid::Pane,
+ total_panes: usize,
+ is_pinned: bool,
+ size: Size,
+) -> Element<'a, Message> {
+ let button = |label, message| {
+ button(
+ text(label)
+ .width(Length::Fill)
+ .horizontal_alignment(alignment::Horizontal::Center)
+ .size(16),
+ )
+ .width(Length::Fill)
+ .padding(8)
+ .on_press(message)
+ };
+
+ let mut controls = column![
+ button(
+ "Split horizontally",
+ Message::Split(pane_grid::Axis::Horizontal, pane),
+ ),
+ button(
+ "Split vertically",
+ Message::Split(pane_grid::Axis::Vertical, pane),
+ )
+ ]
+ .spacing(5)
+ .max_width(150);
+
+ if total_panes > 1 && !is_pinned {
+ controls = controls.push(
+ button("Close", Message::Close(pane))
+ .style(theme::Button::Destructive),
+ );
}
- fn view(
- &mut self,
- pane: pane_grid::Pane,
- total_panes: usize,
- is_pinned: bool,
- size: Size,
- ) -> Element<Message> {
- let Content {
- scroll,
- split_horizontally,
- split_vertically,
- close,
- ..
- } = self;
-
- let button = |state, label, message, style| {
- Button::new(
- state,
- Text::new(label)
- .width(Length::Fill)
- .horizontal_alignment(alignment::Horizontal::Center)
- .size(16),
- )
- .width(Length::Fill)
- .padding(8)
- .on_press(message)
- .style(style)
- };
-
- let mut controls = Column::new()
- .spacing(5)
- .max_width(150)
- .push(button(
- split_horizontally,
- "Split horizontally",
- Message::Split(pane_grid::Axis::Horizontal, pane),
- style::Button::Primary,
- ))
- .push(button(
- split_vertically,
- "Split vertically",
- Message::Split(pane_grid::Axis::Vertical, pane),
- style::Button::Primary,
- ));
-
- if total_panes > 1 && !is_pinned {
- controls = controls.push(button(
- close,
- "Close",
- Message::Close(pane),
- style::Button::Destructive,
- ));
- }
- let content = Scrollable::new(scroll)
- .width(Length::Fill)
- .spacing(10)
- .align_items(Alignment::Center)
- .push(Text::new(format!("{}x{}", size.width, size.height)).size(24))
- .push(controls);
+ let content = column![
+ text(format!("{}x{}", size.width, size.height)).size(24),
+ controls,
+ ]
+ .width(Length::Fill)
+ .spacing(10)
+ .align_items(Alignment::Center);
- Container::new(content)
- .width(Length::Fill)
- .height(Length::Fill)
- .padding(5)
- .center_y()
- .into()
- }
+ container(scrollable(content))
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .padding(5)
+ .center_y()
+ .into()
}
-impl Controls {
- fn new() -> Self {
- Self {
- close: button::State::new(),
- }
+fn view_controls<'a>(
+ pane: pane_grid::Pane,
+ total_panes: usize,
+ is_pinned: bool,
+) -> Element<'a, Message> {
+ let mut button = button(text("Close").size(14))
+ .style(theme::Button::Destructive)
+ .padding(3);
+
+ if total_panes > 1 && !is_pinned {
+ button = button.on_press(Message::Close(pane));
}
- pub fn view(
- &mut self,
- pane: pane_grid::Pane,
- total_panes: usize,
- is_pinned: bool,
- ) -> Element<Message> {
- let mut button =
- Button::new(&mut self.close, Text::new("Close").size(14))
- .style(style::Button::Control)
- .padding(3);
- if total_panes > 1 && !is_pinned {
- button = button.on_press(Message::Close(pane));
- }
- button.into()
- }
+ button.into()
}
mod style {
- use crate::PANE_ID_COLOR_FOCUSED;
- use iced::{button, container, Background, Color, Vector};
-
- const SURFACE: Color = Color::from_rgb(
- 0xF2 as f32 / 255.0,
- 0xF3 as f32 / 255.0,
- 0xF5 as f32 / 255.0,
- );
-
- const ACTIVE: Color = Color::from_rgb(
- 0x72 as f32 / 255.0,
- 0x89 as f32 / 255.0,
- 0xDA as f32 / 255.0,
- );
-
- const HOVERED: Color = Color::from_rgb(
- 0x67 as f32 / 255.0,
- 0x7B as f32 / 255.0,
- 0xC4 as f32 / 255.0,
- );
-
- pub enum TitleBar {
- Active,
- Focused,
- }
+ use iced::widget::container;
+ use iced::Theme;
- impl container::StyleSheet for TitleBar {
- fn style(&self) -> container::Style {
- let pane = match self {
- Self::Active => Pane::Active,
- Self::Focused => Pane::Focused,
- }
- .style();
+ pub fn title_bar_active(theme: &Theme) -> container::Appearance {
+ let palette = theme.extended_palette();
- container::Style {
- text_color: Some(Color::WHITE),
- background: Some(pane.border_color.into()),
- ..Default::default()
- }
+ container::Appearance {
+ text_color: Some(palette.background.strong.text),
+ background: Some(palette.background.strong.color.into()),
+ ..Default::default()
}
}
- pub enum Pane {
- Active,
- Focused,
- }
+ pub fn title_bar_focused(theme: &Theme) -> container::Appearance {
+ let palette = theme.extended_palette();
- impl container::StyleSheet for Pane {
- fn style(&self) -> container::Style {
- container::Style {
- background: Some(Background::Color(SURFACE)),
- border_width: 2.0,
- border_color: match self {
- Self::Active => Color::from_rgb(0.7, 0.7, 0.7),
- Self::Focused => Color::BLACK,
- },
- ..Default::default()
- }
+ container::Appearance {
+ text_color: Some(palette.primary.strong.text),
+ background: Some(palette.primary.strong.color.into()),
+ ..Default::default()
}
}
- pub enum Button {
- Primary,
- Destructive,
- Control,
- Pin,
- }
+ pub fn pane_active(theme: &Theme) -> container::Appearance {
+ let palette = theme.extended_palette();
- impl button::StyleSheet for Button {
- fn active(&self) -> button::Style {
- let (background, text_color) = match self {
- Button::Primary => (Some(ACTIVE), Color::WHITE),
- Button::Destructive => {
- (None, Color::from_rgb8(0xFF, 0x47, 0x47))
- }
- Button::Control => (Some(PANE_ID_COLOR_FOCUSED), Color::WHITE),
- Button::Pin => (Some(ACTIVE), Color::WHITE),
- };
-
- button::Style {
- text_color,
- background: background.map(Background::Color),
- border_radius: 5.0,
- shadow_offset: Vector::new(0.0, 0.0),
- ..button::Style::default()
- }
+ container::Appearance {
+ background: Some(palette.background.weak.color.into()),
+ border_width: 2.0,
+ border_color: palette.background.strong.color,
+ ..Default::default()
}
+ }
- fn hovered(&self) -> button::Style {
- let active = self.active();
+ pub fn pane_focused(theme: &Theme) -> container::Appearance {
+ let palette = theme.extended_palette();
- let background = match self {
- Button::Primary => Some(HOVERED),
- Button::Destructive => Some(Color {
- a: 0.2,
- ..active.text_color
- }),
- Button::Control => Some(PANE_ID_COLOR_FOCUSED),
- Button::Pin => Some(HOVERED),
- };
-
- button::Style {
- background: background.map(Background::Color),
- ..active
- }
+ container::Appearance {
+ background: Some(palette.background.weak.color.into()),
+ border_width: 2.0,
+ border_color: palette.primary.strong.color,
+ ..Default::default()
}
}
}
diff --git a/examples/pick_list/src/main.rs b/examples/pick_list/src/main.rs
index 52303d70..9df1f5c7 100644
--- a/examples/pick_list/src/main.rs
+++ b/examples/pick_list/src/main.rs
@@ -1,7 +1,5 @@
-use iced::{
- pick_list, scrollable, Alignment, Container, Element, Length, PickList,
- Sandbox, Scrollable, Settings, Space, Text,
-};
+use iced::widget::{column, container, pick_list, scrollable, vertical_space};
+use iced::{Alignment, Element, Length, Sandbox, Settings};
pub fn main() -> iced::Result {
Example::run(Settings::default())
@@ -9,8 +7,6 @@ pub fn main() -> iced::Result {
#[derive(Default)]
struct Example {
- scroll: scrollable::State,
- pick_list: pick_list::State<Language>,
selected_language: Option<Language>,
}
@@ -38,26 +34,25 @@ impl Sandbox for Example {
}
}
- fn view(&mut self) -> Element<Message> {
- let pick_list = PickList::new(
- &mut self.pick_list,
+ fn view(&self) -> Element<Message> {
+ let pick_list = pick_list(
&Language::ALL[..],
self.selected_language,
Message::LanguageSelected,
)
.placeholder("Choose a language...");
- let mut content = Scrollable::new(&mut self.scroll)
- .width(Length::Fill)
- .align_items(Alignment::Center)
- .spacing(10)
- .push(Space::with_height(Length::Units(600)))
- .push(Text::new("Which is your favorite language?"))
- .push(pick_list);
-
- content = content.push(Space::with_height(Length::Units(600)));
+ let content = column![
+ vertical_space(Length::Units(600)),
+ "Which is your favorite language?",
+ pick_list,
+ vertical_space(Length::Units(600)),
+ ]
+ .width(Length::Fill)
+ .align_items(Alignment::Center)
+ .spacing(10);
- Container::new(content)
+ container(scrollable(content))
.width(Length::Fill)
.height(Length::Fill)
.center_x()
diff --git a/examples/pokedex/src/main.rs b/examples/pokedex/src/main.rs
index 85c26987..4fe2d07c 100644
--- a/examples/pokedex/src/main.rs
+++ b/examples/pokedex/src/main.rs
@@ -1,6 +1,7 @@
+use iced::futures;
+use iced::widget::{self, column, container, image, row, text};
use iced::{
- button, futures, image, Alignment, Application, Button, Column, Command,
- Container, Element, Length, Row, Settings, Text,
+ Alignment, Application, Color, Command, Element, Length, Settings, Theme,
};
pub fn main() -> iced::Result {
@@ -10,13 +11,8 @@ pub fn main() -> iced::Result {
#[derive(Debug)]
enum Pokedex {
Loading,
- Loaded {
- pokemon: Pokemon,
- search: button::State,
- },
- Errored {
- try_again: button::State,
- },
+ Loaded { pokemon: Pokemon },
+ Errored,
}
#[derive(Debug, Clone)]
@@ -26,8 +22,9 @@ enum Message {
}
impl Application for Pokedex {
- type Executor = iced::executor::Default;
type Message = Message;
+ type Theme = Theme;
+ type Executor = iced::executor::Default;
type Flags = ();
fn new(_flags: ()) -> (Pokedex, Command<Message>) {
@@ -50,17 +47,12 @@ impl Application for Pokedex {
fn update(&mut self, message: Message) -> Command<Message> {
match message {
Message::PokemonFound(Ok(pokemon)) => {
- *self = Pokedex::Loaded {
- pokemon,
- search: button::State::new(),
- };
+ *self = Pokedex::Loaded { pokemon };
Command::none()
}
Message::PokemonFound(Err(_error)) => {
- *self = Pokedex::Errored {
- try_again: button::State::new(),
- };
+ *self = Pokedex::Errored;
Command::none()
}
@@ -75,27 +67,28 @@ impl Application for Pokedex {
}
}
- fn view(&mut self) -> Element<Message> {
+ fn view(&self) -> Element<Message> {
let content = match self {
- Pokedex::Loading => Column::new()
- .width(Length::Shrink)
- .push(Text::new("Searching for Pokémon...").size(40)),
- Pokedex::Loaded { pokemon, search } => Column::new()
- .max_width(500)
- .spacing(20)
- .align_items(Alignment::End)
- .push(pokemon.view())
- .push(
- button(search, "Keep searching!").on_press(Message::Search),
- ),
- Pokedex::Errored { try_again, .. } => Column::new()
- .spacing(20)
- .align_items(Alignment::End)
- .push(Text::new("Whoops! Something went wrong...").size(40))
- .push(button(try_again, "Try again").on_press(Message::Search)),
+ Pokedex::Loading => {
+ column![text("Searching for Pokémon...").size(40),]
+ .width(Length::Shrink)
+ }
+ Pokedex::Loaded { pokemon } => column![
+ pokemon.view(),
+ button("Keep searching!").on_press(Message::Search)
+ ]
+ .max_width(500)
+ .spacing(20)
+ .align_items(Alignment::End),
+ Pokedex::Errored => column![
+ text("Whoops! Something went wrong...").size(40),
+ button("Try again").on_press(Message::Search)
+ ]
+ .spacing(20)
+ .align_items(Alignment::End),
};
- Container::new(content)
+ container(content)
.width(Length::Fill)
.height(Length::Fill)
.center_x()
@@ -110,41 +103,30 @@ struct Pokemon {
name: String,
description: String,
image: image::Handle,
- image_viewer: image::viewer::State,
}
impl Pokemon {
const TOTAL: u16 = 807;
- fn view(&mut self) -> Element<Message> {
- Row::new()
- .spacing(20)
- .align_items(Alignment::Center)
- .push(image::Viewer::new(
- &mut self.image_viewer,
- self.image.clone(),
- ))
- .push(
- Column::new()
- .spacing(20)
- .push(
- Row::new()
- .align_items(Alignment::Center)
- .spacing(20)
- .push(
- Text::new(&self.name)
- .size(30)
- .width(Length::Fill),
- )
- .push(
- Text::new(format!("#{}", self.number))
- .size(20)
- .color([0.5, 0.5, 0.5]),
- ),
- )
- .push(Text::new(&self.description)),
- )
- .into()
+ fn view(&self) -> Element<Message> {
+ row![
+ image::viewer(self.image.clone()),
+ column![
+ row![
+ text(&self.name).size(30).width(Length::Fill),
+ text(format!("#{}", self.number))
+ .size(20)
+ .style(Color::from([0.5, 0.5, 0.5])),
+ ]
+ .align_items(Alignment::Center)
+ .spacing(20),
+ self.description.as_ref(),
+ ]
+ .spacing(20),
+ ]
+ .spacing(20)
+ .align_items(Alignment::Center)
+ .into()
}
async fn search() -> Result<Pokemon, Error> {
@@ -188,8 +170,7 @@ impl Pokemon {
let description = entry
.flavor_text_entries
.iter()
- .filter(|text| text.language.name == "en")
- .next()
+ .find(|text| text.language.name == "en")
.ok_or(Error::LanguageError)?;
Ok(Pokemon {
@@ -201,7 +182,6 @@ impl Pokemon {
.map(|c| if c.is_control() { ' ' } else { c })
.collect(),
image,
- image_viewer: image::viewer::State::new(),
})
}
@@ -237,30 +217,6 @@ impl From<reqwest::Error> for Error {
}
}
-fn button<'a>(state: &'a mut button::State, text: &str) -> Button<'a, Message> {
- Button::new(state, Text::new(text))
- .padding(10)
- .style(style::Button::Primary)
-}
-
-mod style {
- use iced::{button, Background, Color, Vector};
-
- pub enum Button {
- Primary,
- }
-
- impl button::StyleSheet for Button {
- fn active(&self) -> button::Style {
- button::Style {
- background: Some(Background::Color(match self {
- Button::Primary => Color::from_rgb(0.11, 0.42, 0.87),
- })),
- border_radius: 12.0,
- shadow_offset: Vector::new(1.0, 1.0),
- text_color: Color::WHITE,
- ..button::Style::default()
- }
- }
- }
+fn button(text: &str) -> widget::Button<'_, Message> {
+ widget::button(text).padding(10)
}
diff --git a/examples/progress_bar/src/main.rs b/examples/progress_bar/src/main.rs
index c9a8e798..d4ebe4d3 100644
--- a/examples/progress_bar/src/main.rs
+++ b/examples/progress_bar/src/main.rs
@@ -1,4 +1,5 @@
-use iced::{slider, Column, Element, ProgressBar, Sandbox, Settings, Slider};
+use iced::widget::{column, progress_bar, slider};
+use iced::{Element, Sandbox, Settings};
pub fn main() -> iced::Result {
Progress::run(Settings::default())
@@ -7,7 +8,6 @@ pub fn main() -> iced::Result {
#[derive(Default)]
struct Progress {
value: f32,
- progress_bar_slider: slider::State,
}
#[derive(Debug, Clone, Copy)]
@@ -32,19 +32,12 @@ impl Sandbox for Progress {
}
}
- fn view(&mut self) -> Element<Message> {
- Column::new()
- .padding(20)
- .push(ProgressBar::new(0.0..=100.0, self.value))
- .push(
- Slider::new(
- &mut self.progress_bar_slider,
- 0.0..=100.0,
- self.value,
- Message::SliderChanged,
- )
- .step(0.01),
- )
- .into()
+ fn view(&self) -> Element<Message> {
+ column![
+ progress_bar(0.0..=100.0, self.value),
+ slider(0.0..=100.0, self.value, Message::SliderChanged).step(0.01)
+ ]
+ .padding(20)
+ .into()
}
}
diff --git a/examples/qr_code/src/main.rs b/examples/qr_code/src/main.rs
index 92c82d45..6f487e4c 100644
--- a/examples/qr_code/src/main.rs
+++ b/examples/qr_code/src/main.rs
@@ -1,8 +1,6 @@
-use iced::qr_code::{self, QRCode};
-use iced::text_input::{self, TextInput};
-use iced::{
- Alignment, Column, Container, Element, Length, Sandbox, Settings, Text,
-};
+use iced::widget::qr_code::{self, QRCode};
+use iced::widget::{column, container, text, text_input};
+use iced::{Alignment, Color, Element, Length, Sandbox, Settings};
pub fn main() -> iced::Result {
QRGenerator::run(Settings::default())
@@ -11,7 +9,6 @@ pub fn main() -> iced::Result {
#[derive(Default)]
struct QRGenerator {
data: String,
- input: text_input::State,
qr_code: Option<qr_code::State>,
}
@@ -45,13 +42,12 @@ impl Sandbox for QRGenerator {
}
}
- fn view(&mut self) -> Element<Message> {
- let title = Text::new("QR Code Generator")
+ fn view(&self) -> Element<Message> {
+ let title = text("QR Code Generator")
.size(70)
- .color([0.5, 0.5, 0.5]);
+ .style(Color::from([0.5, 0.5, 0.5]));
- let input = TextInput::new(
- &mut self.input,
+ let input = text_input(
"Type the data of your QR code here...",
&self.data,
Message::DataChanged,
@@ -59,18 +55,16 @@ impl Sandbox for QRGenerator {
.size(30)
.padding(15);
- let mut content = Column::new()
+ let mut content = column![title, input]
.width(Length::Units(700))
.spacing(20)
- .align_items(Alignment::Center)
- .push(title)
- .push(input);
+ .align_items(Alignment::Center);
- if let Some(qr_code) = self.qr_code.as_mut() {
+ if let Some(qr_code) = self.qr_code.as_ref() {
content = content.push(QRCode::new(qr_code).cell_size(10));
}
- Container::new(content)
+ container(content)
.width(Length::Fill)
.height(Length::Fill)
.padding(20)
diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs
index 8e027504..b7b3dedc 100644
--- a/examples/scrollable/src/main.rs
+++ b/examples/scrollable/src/main.rs
@@ -1,176 +1,182 @@
-mod style;
-
-use iced::{
- button, scrollable, Button, Column, Container, Element, Length,
- ProgressBar, Radio, Row, Rule, Sandbox, Scrollable, Settings, Space, Text,
+use iced::executor;
+use iced::widget::{
+ button, column, container, horizontal_rule, progress_bar, radio,
+ scrollable, text, vertical_space, Row,
};
+use iced::{Application, Command, Element, Length, Settings, Theme};
pub fn main() -> iced::Result {
ScrollableDemo::run(Settings::default())
}
struct ScrollableDemo {
- theme: style::Theme,
+ theme: Theme,
variants: Vec<Variant>,
}
#[derive(Debug, Clone)]
enum Message {
- ThemeChanged(style::Theme),
+ ThemeChanged(Theme),
ScrollToTop(usize),
ScrollToBottom(usize),
Scrolled(usize, f32),
}
-impl Sandbox for ScrollableDemo {
+impl Application for ScrollableDemo {
type Message = Message;
-
- fn new() -> Self {
- ScrollableDemo {
- theme: Default::default(),
- variants: Variant::all(),
- }
+ type Theme = Theme;
+ type Executor = executor::Default;
+ type Flags = ();
+
+ fn new(_flags: Self::Flags) -> (Self, Command<Message>) {
+ (
+ ScrollableDemo {
+ theme: Default::default(),
+ variants: Variant::all(),
+ },
+ Command::none(),
+ )
}
fn title(&self) -> String {
String::from("Scrollable - Iced")
}
- fn update(&mut self, message: Message) {
+ fn update(&mut self, message: Message) -> Command<Message> {
match message {
- Message::ThemeChanged(theme) => self.theme = theme,
+ Message::ThemeChanged(theme) => {
+ self.theme = theme;
+
+ Command::none()
+ }
Message::ScrollToTop(i) => {
if let Some(variant) = self.variants.get_mut(i) {
- variant.scrollable.snap_to(0.0);
-
variant.latest_offset = 0.0;
+
+ scrollable::snap_to(Variant::id(i), 0.0)
+ } else {
+ Command::none()
}
}
Message::ScrollToBottom(i) => {
if let Some(variant) = self.variants.get_mut(i) {
- variant.scrollable.snap_to(1.0);
-
variant.latest_offset = 1.0;
+
+ scrollable::snap_to(Variant::id(i), 1.0)
+ } else {
+ Command::none()
}
}
Message::Scrolled(i, offset) => {
if let Some(variant) = self.variants.get_mut(i) {
variant.latest_offset = offset;
}
+
+ Command::none()
}
}
}
- fn view(&mut self) -> Element<Message> {
+ fn view(&self) -> Element<Message> {
let ScrollableDemo {
theme, variants, ..
} = self;
- let choose_theme = style::Theme::ALL.iter().fold(
- Column::new().spacing(10).push(Text::new("Choose a theme:")),
+ let choose_theme = [Theme::Light, Theme::Dark].iter().fold(
+ column!["Choose a theme:"].spacing(10),
|column, option| {
- column.push(
- Radio::new(
- *option,
- format!("{:?}", option),
- Some(*theme),
- Message::ThemeChanged,
- )
- .style(*theme),
- )
+ column.push(radio(
+ format!("{:?}", option),
+ *option,
+ Some(*theme),
+ Message::ThemeChanged,
+ ))
},
);
let scrollable_row = Row::with_children(
variants
- .iter_mut()
+ .iter()
.enumerate()
.map(|(i, variant)| {
- let mut scrollable =
- Scrollable::new(&mut variant.scrollable)
- .padding(10)
- .spacing(10)
+ let mut contents = column![
+ variant.title,
+ button("Scroll to bottom",)
.width(Length::Fill)
- .height(Length::Fill)
- .on_scroll(move |offset| {
- Message::Scrolled(i, offset)
- })
- .style(*theme)
- .push(Text::new(variant.title))
- .push(
- Button::new(
- &mut variant.scroll_to_bottom,
- Text::new("Scroll to bottom"),
- )
- .width(Length::Fill)
- .padding(10)
- .on_press(Message::ScrollToBottom(i)),
- );
+ .padding(10)
+ .on_press(Message::ScrollToBottom(i)),
+ ]
+ .padding(10)
+ .spacing(10)
+ .width(Length::Fill);
if let Some(scrollbar_width) = variant.scrollbar_width {
- scrollable = scrollable
- .scrollbar_width(scrollbar_width)
- .push(Text::new(format!(
- "scrollbar_width: {:?}",
- scrollbar_width
- )));
+ contents = contents.push(text(format!(
+ "scrollbar_width: {:?}",
+ scrollbar_width
+ )));
}
if let Some(scrollbar_margin) = variant.scrollbar_margin {
- scrollable = scrollable
- .scrollbar_margin(scrollbar_margin)
- .push(Text::new(format!(
- "scrollbar_margin: {:?}",
- scrollbar_margin
- )));
+ contents = contents.push(text(format!(
+ "scrollbar_margin: {:?}",
+ scrollbar_margin
+ )));
}
if let Some(scroller_width) = variant.scroller_width {
- scrollable = scrollable
- .scroller_width(scroller_width)
- .push(Text::new(format!(
- "scroller_width: {:?}",
- scroller_width
- )));
+ contents = contents.push(text(format!(
+ "scroller_width: {:?}",
+ scroller_width
+ )));
}
- scrollable = scrollable
- .push(Space::with_height(Length::Units(100)))
- .push(Text::new(
+ contents = contents
+ .push(vertical_space(Length::Units(100)))
+ .push(
"Some content that should wrap within the \
scrollable. Let's output a lot of short words, so \
that we'll make sure to see how wrapping works \
with these scrollbars.",
- ))
- .push(Space::with_height(Length::Units(1200)))
- .push(Text::new("Middle"))
- .push(Space::with_height(Length::Units(1200)))
- .push(Text::new("The End."))
+ )
+ .push(vertical_space(Length::Units(1200)))
+ .push("Middle")
+ .push(vertical_space(Length::Units(1200)))
+ .push("The End.")
.push(
- Button::new(
- &mut variant.scroll_to_top,
- Text::new("Scroll to top"),
- )
- .width(Length::Fill)
- .padding(10)
- .on_press(Message::ScrollToTop(i)),
+ button("Scroll to top")
+ .width(Length::Fill)
+ .padding(10)
+ .on_press(Message::ScrollToTop(i)),
);
- Column::new()
- .width(Length::Fill)
+ let mut scrollable = scrollable(contents)
+ .id(Variant::id(i))
.height(Length::Fill)
- .spacing(10)
- .push(
- Container::new(scrollable)
- .width(Length::Fill)
- .height(Length::Fill)
- .style(*theme),
- )
- .push(ProgressBar::new(
- 0.0..=1.0,
- variant.latest_offset,
- ))
- .into()
+ .on_scroll(move |offset| Message::Scrolled(i, offset));
+
+ if let Some(scrollbar_width) = variant.scrollbar_width {
+ scrollable =
+ scrollable.scrollbar_width(scrollbar_width);
+ }
+
+ if let Some(scrollbar_margin) = variant.scrollbar_margin {
+ scrollable =
+ scrollable.scrollbar_margin(scrollbar_margin);
+ }
+
+ if let Some(scroller_width) = variant.scroller_width {
+ scrollable = scrollable.scroller_width(scroller_width);
+ }
+
+ column![
+ scrollable,
+ progress_bar(0.0..=1.0, variant.latest_offset,)
+ ]
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .spacing(10)
+ .into()
})
.collect(),
)
@@ -178,29 +184,27 @@ impl Sandbox for ScrollableDemo {
.width(Length::Fill)
.height(Length::Fill);
- let content = Column::new()
- .spacing(20)
- .padding(20)
- .push(choose_theme)
- .push(Rule::horizontal(20).style(self.theme))
- .push(scrollable_row);
+ let content =
+ column![choose_theme, horizontal_rule(20), scrollable_row]
+ .spacing(20)
+ .padding(20);
- Container::new(content)
+ container(content)
.width(Length::Fill)
.height(Length::Fill)
.center_x()
.center_y()
- .style(self.theme)
.into()
}
+
+ fn theme(&self) -> Theme {
+ self.theme
+ }
}
/// A version of a scrollable
struct Variant {
title: &'static str,
- scrollable: scrollable::State,
- scroll_to_top: button::State,
- scroll_to_bottom: button::State,
scrollbar_width: Option<u16>,
scrollbar_margin: Option<u16>,
scroller_width: Option<u16>,
@@ -212,9 +216,6 @@ impl Variant {
vec![
Self {
title: "Default Scrollbar",
- scrollable: scrollable::State::new(),
- scroll_to_top: button::State::new(),
- scroll_to_bottom: button::State::new(),
scrollbar_width: None,
scrollbar_margin: None,
scroller_width: None,
@@ -222,9 +223,6 @@ impl Variant {
},
Self {
title: "Slimmed & Margin",
- scrollable: scrollable::State::new(),
- scroll_to_top: button::State::new(),
- scroll_to_bottom: button::State::new(),
scrollbar_width: Some(4),
scrollbar_margin: Some(3),
scroller_width: Some(4),
@@ -232,9 +230,6 @@ impl Variant {
},
Self {
title: "Wide Scroller",
- scrollable: scrollable::State::new(),
- scroll_to_top: button::State::new(),
- scroll_to_bottom: button::State::new(),
scrollbar_width: Some(4),
scrollbar_margin: None,
scroller_width: Some(10),
@@ -242,9 +237,6 @@ impl Variant {
},
Self {
title: "Narrow Scroller",
- scrollable: scrollable::State::new(),
- scroll_to_top: button::State::new(),
- scroll_to_bottom: button::State::new(),
scrollbar_width: Some(10),
scrollbar_margin: None,
scroller_width: Some(4),
@@ -252,4 +244,8 @@ impl Variant {
},
]
}
+
+ pub fn id(i: usize) -> scrollable::Id {
+ scrollable::Id::new(format!("scrollable-{}", i))
+ }
}
diff --git a/examples/scrollable/src/style.rs b/examples/scrollable/src/style.rs
deleted file mode 100644
index 0ed38b00..00000000
--- a/examples/scrollable/src/style.rs
+++ /dev/null
@@ -1,191 +0,0 @@
-use iced::{container, radio, rule, scrollable};
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum Theme {
- Light,
- Dark,
-}
-
-impl Theme {
- pub const ALL: [Theme; 2] = [Theme::Light, Theme::Dark];
-}
-
-impl Default for Theme {
- fn default() -> Theme {
- Theme::Light
- }
-}
-
-impl<'a> From<Theme> for Box<dyn container::StyleSheet + 'a> {
- fn from(theme: Theme) -> Self {
- match theme {
- Theme::Light => Default::default(),
- Theme::Dark => dark::Container.into(),
- }
- }
-}
-
-impl<'a> From<Theme> for Box<dyn radio::StyleSheet + 'a> {
- fn from(theme: Theme) -> Self {
- match theme {
- Theme::Light => Default::default(),
- Theme::Dark => dark::Radio.into(),
- }
- }
-}
-
-impl<'a> From<Theme> for Box<dyn scrollable::StyleSheet + 'a> {
- fn from(theme: Theme) -> Self {
- match theme {
- Theme::Light => Default::default(),
- Theme::Dark => dark::Scrollable.into(),
- }
- }
-}
-
-impl From<Theme> for Box<dyn rule::StyleSheet> {
- fn from(theme: Theme) -> Self {
- match theme {
- Theme::Light => Default::default(),
- Theme::Dark => dark::Rule.into(),
- }
- }
-}
-
-mod dark {
- use iced::{container, radio, rule, scrollable, Color};
-
- const BACKGROUND: Color = Color::from_rgb(
- 0x36 as f32 / 255.0,
- 0x39 as f32 / 255.0,
- 0x3F as f32 / 255.0,
- );
-
- const SURFACE: Color = Color::from_rgb(
- 0x40 as f32 / 255.0,
- 0x44 as f32 / 255.0,
- 0x4B as f32 / 255.0,
- );
-
- const ACCENT: Color = Color::from_rgb(
- 0x6F as f32 / 255.0,
- 0xFF as f32 / 255.0,
- 0xE9 as f32 / 255.0,
- );
-
- const ACTIVE: Color = Color::from_rgb(
- 0x72 as f32 / 255.0,
- 0x89 as f32 / 255.0,
- 0xDA as f32 / 255.0,
- );
-
- const SCROLLBAR: Color = Color::from_rgb(
- 0x2E as f32 / 255.0,
- 0x33 as f32 / 255.0,
- 0x38 as f32 / 255.0,
- );
-
- const SCROLLER: Color = Color::from_rgb(
- 0x20 as f32 / 255.0,
- 0x22 as f32 / 255.0,
- 0x25 as f32 / 255.0,
- );
-
- pub struct Container;
-
- impl container::StyleSheet for Container {
- fn style(&self) -> container::Style {
- container::Style {
- background: Color {
- a: 0.99,
- ..BACKGROUND
- }
- .into(),
- text_color: Color::WHITE.into(),
- ..container::Style::default()
- }
- }
- }
-
- pub struct Radio;
-
- impl radio::StyleSheet for Radio {
- fn active(&self) -> radio::Style {
- radio::Style {
- background: SURFACE.into(),
- dot_color: ACTIVE,
- border_width: 1.0,
- border_color: ACTIVE,
- text_color: None,
- }
- }
-
- fn hovered(&self) -> radio::Style {
- radio::Style {
- background: Color { a: 0.5, ..SURFACE }.into(),
- ..self.active()
- }
- }
- }
-
- pub struct Scrollable;
-
- impl scrollable::StyleSheet for Scrollable {
- fn active(&self) -> scrollable::Scrollbar {
- scrollable::Scrollbar {
- background: Color {
- a: 0.8,
- ..SCROLLBAR
- }
- .into(),
- border_radius: 2.0,
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- scroller: scrollable::Scroller {
- color: Color { a: 0.7, ..SCROLLER },
- border_radius: 2.0,
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- },
- }
- }
-
- fn hovered(&self) -> scrollable::Scrollbar {
- let active = self.active();
-
- scrollable::Scrollbar {
- background: SCROLLBAR.into(),
- scroller: scrollable::Scroller {
- color: SCROLLER,
- ..active.scroller
- },
- ..active
- }
- }
-
- fn dragging(&self) -> scrollable::Scrollbar {
- let hovered = self.hovered();
-
- scrollable::Scrollbar {
- scroller: scrollable::Scroller {
- color: ACCENT,
- ..hovered.scroller
- },
- ..hovered
- }
- }
- }
-
- pub struct Rule;
-
- impl rule::StyleSheet for Rule {
- fn style(&self) -> rule::Style {
- rule::Style {
- color: SURFACE,
- width: 2,
- radius: 1.0,
- fill_mode: rule::FillMode::Percent(30.0),
- }
- }
- }
-}
diff --git a/examples/sierpinski_triangle/Cargo.toml b/examples/sierpinski_triangle/Cargo.toml
new file mode 100644
index 00000000..39d45f64
--- /dev/null
+++ b/examples/sierpinski_triangle/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "sierpinski_triangle"
+version = "0.1.0"
+authors = ["xkenmon <xkenmon@gmail.com>"]
+edition = "2018"
+publish = false
+
+[dependencies]
+iced = { path = "../..", features = ["canvas", "debug"] }
+rand = "0.8.4"
diff --git a/examples/sierpinski_triangle/README.md b/examples/sierpinski_triangle/README.md
new file mode 100644
index 00000000..9fd18257
--- /dev/null
+++ b/examples/sierpinski_triangle/README.md
@@ -0,0 +1,16 @@
+## Sierpinski Triangle Emulator
+
+A simple [Sierpiński triangle](https://en.wikipedia.org/wiki/Sierpi%C5%84ski_triangle) Emulator, use canvas and slider.
+
+Left-click add fixed point, right-click remove fixed point.
+
+<div align="center">
+ <a href="https://gfycat.com/flippantrectangularechidna">
+ <img src="https://thumbs.gfycat.com/FlippantRectangularEchidna-size_restricted.gif">
+ </a>
+</div>
+
+You can run with cargo:
+```
+cargo run --package sierpinski_triangle
+``` \ No newline at end of file
diff --git a/examples/sierpinski_triangle/src/main.rs b/examples/sierpinski_triangle/src/main.rs
new file mode 100644
index 00000000..1d25d171
--- /dev/null
+++ b/examples/sierpinski_triangle/src/main.rs
@@ -0,0 +1,193 @@
+use std::fmt::Debug;
+
+use iced::executor;
+use iced::widget::canvas::event::{self, Event};
+use iced::widget::canvas::{self, Canvas};
+use iced::widget::{column, row, slider, text};
+use iced::{
+ Application, Color, Command, Length, Point, Rectangle, Settings, Size,
+ Theme,
+};
+
+use rand::Rng;
+
+fn main() -> iced::Result {
+ SierpinskiEmulator::run(Settings {
+ antialiasing: true,
+ ..Settings::default()
+ })
+}
+
+#[derive(Debug)]
+struct SierpinskiEmulator {
+ graph: SierpinskiGraph,
+}
+
+#[derive(Debug, Clone)]
+pub enum Message {
+ IterationSet(i32),
+ PointAdded(Point),
+ PointRemoved,
+}
+
+impl Application for SierpinskiEmulator {
+ type Executor = executor::Default;
+ type Message = Message;
+ type Theme = Theme;
+ type Flags = ();
+
+ fn new(_flags: Self::Flags) -> (Self, iced::Command<Self::Message>) {
+ let emulator = SierpinskiEmulator {
+ graph: SierpinskiGraph::new(),
+ };
+ (emulator, Command::none())
+ }
+
+ fn title(&self) -> String {
+ "Sierpinski Triangle Emulator".to_string()
+ }
+
+ fn update(
+ &mut self,
+ message: Self::Message,
+ ) -> iced::Command<Self::Message> {
+ match message {
+ Message::IterationSet(cur_iter) => {
+ self.graph.iteration = cur_iter;
+ }
+ Message::PointAdded(point) => {
+ self.graph.fix_points.push(point);
+ self.graph.random_points.clear();
+ }
+ Message::PointRemoved => {
+ self.graph.fix_points.pop();
+ self.graph.random_points.clear();
+ }
+ }
+
+ self.graph.redraw();
+
+ Command::none()
+ }
+
+ fn view(&self) -> iced::Element<'_, Self::Message> {
+ column![
+ Canvas::new(&self.graph)
+ .width(Length::Fill)
+ .height(Length::Fill),
+ row![
+ text(format!("Iteration: {:?}", self.graph.iteration)),
+ slider(0..=10000, self.graph.iteration, Message::IterationSet)
+ .width(Length::Fill)
+ ]
+ .padding(10)
+ .spacing(20),
+ ]
+ .width(Length::Fill)
+ .align_items(iced::Alignment::Center)
+ .into()
+ }
+}
+
+#[derive(Default, Debug)]
+struct SierpinskiGraph {
+ iteration: i32,
+ fix_points: Vec<Point>,
+ random_points: Vec<Point>,
+ cache: canvas::Cache,
+}
+
+impl canvas::Program<Message> for SierpinskiGraph {
+ type State = ();
+
+ fn update(
+ &self,
+ _state: &mut Self::State,
+ event: Event,
+ bounds: Rectangle,
+ cursor: canvas::Cursor,
+ ) -> (event::Status, Option<Message>) {
+ let cursor_position =
+ if let Some(position) = cursor.position_in(&bounds) {
+ position
+ } else {
+ return (event::Status::Ignored, None);
+ };
+
+ match event {
+ Event::Mouse(mouse_event) => {
+ let message = match mouse_event {
+ iced::mouse::Event::ButtonPressed(
+ iced::mouse::Button::Left,
+ ) => Some(Message::PointAdded(cursor_position)),
+ iced::mouse::Event::ButtonPressed(
+ iced::mouse::Button::Right,
+ ) => Some(Message::PointRemoved),
+ _ => None,
+ };
+ (event::Status::Captured, message)
+ }
+ _ => (event::Status::Ignored, None),
+ }
+ }
+
+ fn draw(
+ &self,
+ _state: &Self::State,
+ _theme: &Theme,
+ bounds: Rectangle,
+ _cursor: canvas::Cursor,
+ ) -> Vec<canvas::Geometry> {
+ let geom = self.cache.draw(bounds.size(), |frame| {
+ frame.stroke(
+ &canvas::Path::rectangle(Point::ORIGIN, frame.size()),
+ canvas::Stroke::default(),
+ );
+
+ if self.fix_points.is_empty() {
+ return;
+ }
+
+ let mut last = None;
+
+ for _ in 0..self.iteration {
+ let p = self.gen_rand_point(last);
+ let path = canvas::Path::rectangle(p, Size::new(1_f32, 1_f32));
+
+ frame.stroke(&path, canvas::Stroke::default());
+
+ last = Some(p);
+ }
+
+ self.fix_points.iter().for_each(|p| {
+ let path = canvas::Path::circle(*p, 5.0);
+ frame.fill(&path, Color::from_rgb8(0x12, 0x93, 0xD8));
+ });
+ });
+
+ vec![geom]
+ }
+}
+
+impl SierpinskiGraph {
+ fn new() -> SierpinskiGraph {
+ SierpinskiGraph::default()
+ }
+
+ fn redraw(&mut self) {
+ self.cache.clear();
+ }
+
+ fn gen_rand_point(&self, last: Option<Point>) -> Point {
+ let dest_point_idx =
+ rand::thread_rng().gen_range(0..self.fix_points.len());
+
+ let dest_point = self.fix_points[dest_point_idx];
+ let cur_point = last.or_else(|| Some(self.fix_points[0])).unwrap();
+
+ Point::new(
+ (dest_point.x + cur_point.x) / 2_f32,
+ (dest_point.y + cur_point.y) / 2_f32,
+ )
+ }
+}
diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs
index 12184dd1..c59d73a8 100644
--- a/examples/solar_system/src/main.rs
+++ b/examples/solar_system/src/main.rs
@@ -6,10 +6,16 @@
//! Inspired by the example found in the MDN docs[1].
//!
//! [1]: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations#An_animated_solar_system
+use iced::application;
+use iced::executor;
+use iced::theme::{self, Theme};
+use iced::time;
+use iced::widget::canvas;
+use iced::widget::canvas::{Cursor, Path, Stroke};
+use iced::window;
use iced::{
- canvas::{self, Cursor, Path, Stroke},
- executor, time, window, Application, Canvas, Color, Command, Element,
- Length, Point, Rectangle, Settings, Size, Subscription, Vector,
+ Application, Color, Command, Element, Length, Point, Rectangle, Settings,
+ Size, Subscription, Vector,
};
use std::time::Instant;
@@ -31,8 +37,9 @@ enum Message {
}
impl Application for SolarSystem {
- type Executor = executor::Default;
type Message = Message;
+ type Theme = Theme;
+ type Executor = executor::Default;
type Flags = ();
fn new(_flags: ()) -> (Self, Command<Message>) {
@@ -59,16 +66,26 @@ impl Application for SolarSystem {
}
fn subscription(&self) -> Subscription<Message> {
- time::every(std::time::Duration::from_millis(10))
- .map(|instant| Message::Tick(instant))
+ time::every(std::time::Duration::from_millis(10)).map(Message::Tick)
}
- fn view(&mut self) -> Element<Message> {
- Canvas::new(&mut self.state)
+ fn view(&self) -> Element<Message> {
+ canvas(&self.state)
.width(Length::Fill)
.height(Length::Fill)
.into()
}
+
+ fn theme(&self) -> Theme {
+ Theme::Dark
+ }
+
+ fn style(&self) -> theme::Application {
+ theme::Application::Custom(|_theme| application::Appearance {
+ background_color: Color::BLACK,
+ text_color: Color::WHITE,
+ })
+ }
}
#[derive(Debug)]
@@ -129,24 +146,24 @@ impl State {
}
impl<Message> canvas::Program<Message> for State {
+ type State = ();
+
fn draw(
&self,
+ _state: &Self::State,
+ _theme: &Theme,
bounds: Rectangle,
_cursor: Cursor,
) -> Vec<canvas::Geometry> {
use std::f32::consts::PI;
let background = self.space_cache.draw(bounds.size(), |frame| {
- let space = Path::rectangle(Point::new(0.0, 0.0), frame.size());
-
let stars = Path::new(|path| {
for (p, size) in &self.stars {
path.rectangle(*p, Size::new(*size, *size));
}
});
- frame.fill(&space, Color::BLACK);
-
frame.translate(frame.center() - Point::ORIGIN);
frame.fill(&stars, Color::WHITE);
});
diff --git a/examples/stopwatch/src/main.rs b/examples/stopwatch/src/main.rs
index dc8a4de7..b8cee807 100644
--- a/examples/stopwatch/src/main.rs
+++ b/examples/stopwatch/src/main.rs
@@ -1,7 +1,12 @@
+use iced::alignment;
+use iced::executor;
+use iced::theme::{self, Theme};
+use iced::time;
+use iced::widget::{button, column, container, row, text};
use iced::{
- alignment, button, executor, time, Alignment, Application, Button, Column,
- Command, Container, Element, Length, Row, Settings, Subscription, Text,
+ Alignment, Application, Command, Element, Length, Settings, Subscription,
};
+
use std::time::{Duration, Instant};
pub fn main() -> iced::Result {
@@ -11,8 +16,6 @@ pub fn main() -> iced::Result {
struct Stopwatch {
duration: Duration,
state: State,
- toggle: button::State,
- reset: button::State,
}
enum State {
@@ -28,8 +31,9 @@ enum Message {
}
impl Application for Stopwatch {
- type Executor = executor::Default;
type Message = Message;
+ type Theme = Theme;
+ type Executor = executor::Default;
type Flags = ();
fn new(_flags: ()) -> (Stopwatch, Command<Message>) {
@@ -37,8 +41,6 @@ impl Application for Stopwatch {
Stopwatch {
duration: Duration::default(),
state: State::Idle,
- toggle: button::State::new(),
- reset: button::State::new(),
},
Command::none(),
)
@@ -60,13 +62,12 @@ impl Application for Stopwatch {
self.state = State::Idle;
}
},
- Message::Tick(now) => match &mut self.state {
- State::Ticking { last_tick } => {
+ Message::Tick(now) => {
+ if let State::Ticking { last_tick } = &mut self.state {
self.duration += now - *last_tick;
*last_tick = now;
}
- _ => {}
- },
+ }
Message::Reset => {
self.duration = Duration::default();
}
@@ -84,13 +85,13 @@ impl Application for Stopwatch {
}
}
- fn view(&mut self) -> Element<Message> {
+ fn view(&self) -> Element<Message> {
const MINUTE: u64 = 60;
const HOUR: u64 = 60 * MINUTE;
let seconds = self.duration.as_secs();
- let duration = Text::new(format!(
+ let duration = text(format!(
"{:0>2}:{:0>2}:{:0>2}.{:0>2}",
seconds / HOUR,
(seconds % HOUR) / MINUTE,
@@ -99,42 +100,34 @@ impl Application for Stopwatch {
))
.size(40);
- let button = |state, label, style| {
- Button::new(
- state,
- Text::new(label)
- .horizontal_alignment(alignment::Horizontal::Center),
+ let button = |label| {
+ button(
+ text(label).horizontal_alignment(alignment::Horizontal::Center),
)
- .min_width(80)
.padding(10)
- .style(style)
+ .width(Length::Units(80))
};
let toggle_button = {
- let (label, color) = match self.state {
- State::Idle => ("Start", style::Button::Primary),
- State::Ticking { .. } => ("Stop", style::Button::Destructive),
+ let label = match self.state {
+ State::Idle => "Start",
+ State::Ticking { .. } => "Stop",
};
- button(&mut self.toggle, label, color).on_press(Message::Toggle)
+ button(label).on_press(Message::Toggle)
};
- let reset_button =
- button(&mut self.reset, "Reset", style::Button::Secondary)
- .on_press(Message::Reset);
+ let reset_button = button("Reset")
+ .style(theme::Button::Destructive)
+ .on_press(Message::Reset);
- let controls = Row::new()
- .spacing(20)
- .push(toggle_button)
- .push(reset_button);
+ let controls = row![toggle_button, reset_button].spacing(20);
- let content = Column::new()
+ let content = column![duration, controls]
.align_items(Alignment::Center)
- .spacing(20)
- .push(duration)
- .push(controls);
+ .spacing(20);
- Container::new(content)
+ container(content)
.width(Length::Fill)
.height(Length::Fill)
.center_x()
@@ -142,29 +135,3 @@ impl Application for Stopwatch {
.into()
}
}
-
-mod style {
- use iced::{button, Background, Color, Vector};
-
- pub enum Button {
- Primary,
- Secondary,
- Destructive,
- }
-
- impl button::StyleSheet for Button {
- fn active(&self) -> button::Style {
- button::Style {
- background: Some(Background::Color(match self {
- Button::Primary => Color::from_rgb(0.11, 0.42, 0.87),
- Button::Secondary => Color::from_rgb(0.5, 0.5, 0.5),
- Button::Destructive => Color::from_rgb(0.8, 0.2, 0.2),
- })),
- border_radius: 12.0,
- shadow_offset: Vector::new(1.0, 1.0),
- text_color: Color::WHITE,
- ..button::Style::default()
- }
- }
- }
-}
diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs
index b4ef3e87..cda53e87 100644
--- a/examples/styling/src/main.rs
+++ b/examples/styling/src/main.rs
@@ -1,8 +1,9 @@
-use iced::{
- button, scrollable, slider, text_input, Alignment, Button, Checkbox,
- Column, Container, Element, Length, ProgressBar, Radio, Row, Rule, Sandbox,
- Scrollable, Settings, Slider, Space, Text, TextInput, Toggler,
+use iced::widget::{
+ button, checkbox, column, container, horizontal_rule, progress_bar, radio,
+ row, scrollable, slider, text, text_input, toggler, vertical_rule,
+ vertical_space,
};
+use iced::{Alignment, Element, Length, Sandbox, Settings, Theme};
pub fn main() -> iced::Result {
Styling::run(Settings::default())
@@ -10,12 +11,8 @@ pub fn main() -> iced::Result {
#[derive(Default)]
struct Styling {
- theme: style::Theme,
- scroll: scrollable::State,
- input: text_input::State,
+ theme: Theme,
input_value: String,
- button: button::State,
- slider: slider::State,
slider_value: f32,
checkbox_value: bool,
toggler_value: bool,
@@ -23,7 +20,7 @@ struct Styling {
#[derive(Debug, Clone)]
enum Message {
- ThemeChanged(style::Theme),
+ ThemeChanged(Theme),
InputChanged(String),
ButtonPressed,
SliderChanged(f32),
@@ -53,541 +50,88 @@ impl Sandbox for Styling {
}
}
- fn view(&mut self) -> Element<Message> {
- let choose_theme = style::Theme::ALL.iter().fold(
- Column::new().spacing(10).push(Text::new("Choose a theme:")),
+ fn view(&self) -> Element<Message> {
+ let choose_theme = [Theme::Light, Theme::Dark].iter().fold(
+ column![text("Choose a theme:")].spacing(10),
|column, theme| {
- column.push(
- Radio::new(
- *theme,
- format!("{:?}", theme),
- Some(self.theme),
- Message::ThemeChanged,
- )
- .style(self.theme),
- )
+ column.push(radio(
+ format!("{:?}", theme),
+ *theme,
+ Some(self.theme),
+ Message::ThemeChanged,
+ ))
},
);
- let text_input = TextInput::new(
- &mut self.input,
+ let text_input = text_input(
"Type something...",
&self.input_value,
Message::InputChanged,
)
.padding(10)
- .size(20)
- .style(self.theme);
+ .size(20);
- let button = Button::new(&mut self.button, Text::new("Submit"))
+ let button = button("Submit")
.padding(10)
- .on_press(Message::ButtonPressed)
- .style(self.theme);
+ .on_press(Message::ButtonPressed);
- let slider = Slider::new(
- &mut self.slider,
- 0.0..=100.0,
- self.slider_value,
- Message::SliderChanged,
- )
- .style(self.theme);
+ let slider =
+ slider(0.0..=100.0, self.slider_value, Message::SliderChanged);
- let progress_bar =
- ProgressBar::new(0.0..=100.0, self.slider_value).style(self.theme);
+ let progress_bar = progress_bar(0.0..=100.0, self.slider_value);
- let scrollable = Scrollable::new(&mut self.scroll)
- .width(Length::Fill)
- .height(Length::Units(100))
- .style(self.theme)
- .push(Text::new("Scroll me!"))
- .push(Space::with_height(Length::Units(800)))
- .push(Text::new("You did it!"));
+ let scrollable = scrollable(
+ column![
+ "Scroll me!",
+ vertical_space(Length::Units(800)),
+ "You did it!"
+ ]
+ .width(Length::Fill),
+ )
+ .height(Length::Units(100));
- let checkbox = Checkbox::new(
- self.checkbox_value,
+ let checkbox = checkbox(
"Check me!",
+ self.checkbox_value,
Message::CheckboxToggled,
- )
- .style(self.theme);
+ );
- let toggler = Toggler::new(
- self.toggler_value,
+ let toggler = toggler(
String::from("Toggle me!"),
+ self.toggler_value,
Message::TogglerToggled,
)
.width(Length::Shrink)
- .spacing(10)
- .style(self.theme);
-
- let content = Column::new()
- .spacing(20)
- .padding(20)
- .max_width(600)
- .push(choose_theme)
- .push(Rule::horizontal(38).style(self.theme))
- .push(Row::new().spacing(10).push(text_input).push(button))
- .push(slider)
- .push(progress_bar)
- .push(
- Row::new()
- .spacing(10)
- .height(Length::Units(100))
- .align_items(Alignment::Center)
- .push(scrollable)
- .push(Rule::vertical(38).style(self.theme))
- .push(
- Column::new()
- .width(Length::Shrink)
- .spacing(20)
- .push(checkbox)
- .push(toggler),
- ),
- );
+ .spacing(10);
+
+ let content = column![
+ choose_theme,
+ horizontal_rule(38),
+ row![text_input, button].spacing(10),
+ slider,
+ progress_bar,
+ row![
+ scrollable,
+ vertical_rule(38),
+ column![checkbox, toggler].spacing(20)
+ ]
+ .spacing(10)
+ .height(Length::Units(100))
+ .align_items(Alignment::Center),
+ ]
+ .spacing(20)
+ .padding(20)
+ .max_width(600);
- Container::new(content)
+ container(content)
.width(Length::Fill)
.height(Length::Fill)
.center_x()
.center_y()
- .style(self.theme)
.into()
}
-}
-
-mod style {
- use iced::{
- button, checkbox, container, progress_bar, radio, rule, scrollable,
- slider, text_input, toggler,
- };
-
- #[derive(Debug, Clone, Copy, PartialEq, Eq)]
- pub enum Theme {
- Light,
- Dark,
- }
-
- impl Theme {
- pub const ALL: [Theme; 2] = [Theme::Light, Theme::Dark];
- }
-
- impl Default for Theme {
- fn default() -> Theme {
- Theme::Light
- }
- }
-
- impl<'a> From<Theme> for Box<dyn container::StyleSheet + 'a> {
- fn from(theme: Theme) -> Self {
- match theme {
- Theme::Light => Default::default(),
- Theme::Dark => dark::Container.into(),
- }
- }
- }
-
- impl<'a> From<Theme> for Box<dyn radio::StyleSheet + 'a> {
- fn from(theme: Theme) -> Self {
- match theme {
- Theme::Light => Default::default(),
- Theme::Dark => dark::Radio.into(),
- }
- }
- }
-
- impl<'a> From<Theme> for Box<dyn text_input::StyleSheet + 'a> {
- fn from(theme: Theme) -> Self {
- match theme {
- Theme::Light => Default::default(),
- Theme::Dark => dark::TextInput.into(),
- }
- }
- }
-
- impl<'a> From<Theme> for Box<dyn button::StyleSheet + 'a> {
- fn from(theme: Theme) -> Self {
- match theme {
- Theme::Light => light::Button.into(),
- Theme::Dark => dark::Button.into(),
- }
- }
- }
-
- impl<'a> From<Theme> for Box<dyn scrollable::StyleSheet + 'a> {
- fn from(theme: Theme) -> Self {
- match theme {
- Theme::Light => Default::default(),
- Theme::Dark => dark::Scrollable.into(),
- }
- }
- }
-
- impl<'a> From<Theme> for Box<dyn slider::StyleSheet + 'a> {
- fn from(theme: Theme) -> Self {
- match theme {
- Theme::Light => Default::default(),
- Theme::Dark => dark::Slider.into(),
- }
- }
- }
-
- impl From<Theme> for Box<dyn progress_bar::StyleSheet> {
- fn from(theme: Theme) -> Self {
- match theme {
- Theme::Light => Default::default(),
- Theme::Dark => dark::ProgressBar.into(),
- }
- }
- }
-
- impl<'a> From<Theme> for Box<dyn checkbox::StyleSheet + 'a> {
- fn from(theme: Theme) -> Self {
- match theme {
- Theme::Light => Default::default(),
- Theme::Dark => dark::Checkbox.into(),
- }
- }
- }
-
- impl From<Theme> for Box<dyn toggler::StyleSheet> {
- fn from(theme: Theme) -> Self {
- match theme {
- Theme::Light => Default::default(),
- Theme::Dark => dark::Toggler.into(),
- }
- }
- }
-
- impl From<Theme> for Box<dyn rule::StyleSheet> {
- fn from(theme: Theme) -> Self {
- match theme {
- Theme::Light => Default::default(),
- Theme::Dark => dark::Rule.into(),
- }
- }
- }
-
- mod light {
- use iced::{button, Color, Vector};
-
- pub struct Button;
-
- impl button::StyleSheet for Button {
- fn active(&self) -> button::Style {
- button::Style {
- background: Color::from_rgb(0.11, 0.42, 0.87).into(),
- border_radius: 12.0,
- shadow_offset: Vector::new(1.0, 1.0),
- text_color: Color::from_rgb8(0xEE, 0xEE, 0xEE),
- ..button::Style::default()
- }
- }
-
- fn hovered(&self) -> button::Style {
- button::Style {
- text_color: Color::WHITE,
- shadow_offset: Vector::new(1.0, 2.0),
- ..self.active()
- }
- }
- }
- }
- mod dark {
- use iced::{
- button, checkbox, container, progress_bar, radio, rule, scrollable,
- slider, text_input, toggler, Color,
- };
-
- const SURFACE: Color = Color::from_rgb(
- 0x40 as f32 / 255.0,
- 0x44 as f32 / 255.0,
- 0x4B as f32 / 255.0,
- );
-
- const ACCENT: Color = Color::from_rgb(
- 0x6F as f32 / 255.0,
- 0xFF as f32 / 255.0,
- 0xE9 as f32 / 255.0,
- );
-
- const ACTIVE: Color = Color::from_rgb(
- 0x72 as f32 / 255.0,
- 0x89 as f32 / 255.0,
- 0xDA as f32 / 255.0,
- );
-
- const HOVERED: Color = Color::from_rgb(
- 0x67 as f32 / 255.0,
- 0x7B as f32 / 255.0,
- 0xC4 as f32 / 255.0,
- );
-
- pub struct Container;
-
- impl container::StyleSheet for Container {
- fn style(&self) -> container::Style {
- container::Style {
- background: Color::from_rgb8(0x36, 0x39, 0x3F).into(),
- text_color: Color::WHITE.into(),
- ..container::Style::default()
- }
- }
- }
-
- pub struct Radio;
-
- impl radio::StyleSheet for Radio {
- fn active(&self) -> radio::Style {
- radio::Style {
- background: SURFACE.into(),
- dot_color: ACTIVE,
- border_width: 1.0,
- border_color: ACTIVE,
- text_color: None,
- }
- }
-
- fn hovered(&self) -> radio::Style {
- radio::Style {
- background: Color { a: 0.5, ..SURFACE }.into(),
- ..self.active()
- }
- }
- }
-
- pub struct TextInput;
-
- impl text_input::StyleSheet for TextInput {
- fn active(&self) -> text_input::Style {
- text_input::Style {
- background: SURFACE.into(),
- border_radius: 2.0,
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- }
- }
-
- fn focused(&self) -> text_input::Style {
- text_input::Style {
- border_width: 1.0,
- border_color: ACCENT,
- ..self.active()
- }
- }
-
- fn hovered(&self) -> text_input::Style {
- text_input::Style {
- border_width: 1.0,
- border_color: Color { a: 0.3, ..ACCENT },
- ..self.focused()
- }
- }
-
- fn placeholder_color(&self) -> Color {
- Color::from_rgb(0.4, 0.4, 0.4)
- }
-
- fn value_color(&self) -> Color {
- Color::WHITE
- }
-
- fn selection_color(&self) -> Color {
- ACTIVE
- }
- }
-
- pub struct Button;
-
- impl button::StyleSheet for Button {
- fn active(&self) -> button::Style {
- button::Style {
- background: ACTIVE.into(),
- border_radius: 3.0,
- text_color: Color::WHITE,
- ..button::Style::default()
- }
- }
-
- fn hovered(&self) -> button::Style {
- button::Style {
- background: HOVERED.into(),
- text_color: Color::WHITE,
- ..self.active()
- }
- }
-
- fn pressed(&self) -> button::Style {
- button::Style {
- border_width: 1.0,
- border_color: Color::WHITE,
- ..self.hovered()
- }
- }
- }
-
- pub struct Scrollable;
-
- impl scrollable::StyleSheet for Scrollable {
- fn active(&self) -> scrollable::Scrollbar {
- scrollable::Scrollbar {
- background: SURFACE.into(),
- border_radius: 2.0,
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- scroller: scrollable::Scroller {
- color: ACTIVE,
- border_radius: 2.0,
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- },
- }
- }
-
- fn hovered(&self) -> scrollable::Scrollbar {
- let active = self.active();
-
- scrollable::Scrollbar {
- background: Color { a: 0.5, ..SURFACE }.into(),
- scroller: scrollable::Scroller {
- color: HOVERED,
- ..active.scroller
- },
- ..active
- }
- }
-
- fn dragging(&self) -> scrollable::Scrollbar {
- let hovered = self.hovered();
-
- scrollable::Scrollbar {
- scroller: scrollable::Scroller {
- color: Color::from_rgb(0.85, 0.85, 0.85),
- ..hovered.scroller
- },
- ..hovered
- }
- }
- }
-
- pub struct Slider;
-
- impl slider::StyleSheet for Slider {
- fn active(&self) -> slider::Style {
- slider::Style {
- rail_colors: (ACTIVE, Color { a: 0.1, ..ACTIVE }),
- handle: slider::Handle {
- shape: slider::HandleShape::Circle { radius: 9.0 },
- color: ACTIVE,
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- },
- }
- }
-
- fn hovered(&self) -> slider::Style {
- let active = self.active();
-
- slider::Style {
- handle: slider::Handle {
- color: HOVERED,
- ..active.handle
- },
- ..active
- }
- }
-
- fn dragging(&self) -> slider::Style {
- let active = self.active();
-
- slider::Style {
- handle: slider::Handle {
- color: Color::from_rgb(0.85, 0.85, 0.85),
- ..active.handle
- },
- ..active
- }
- }
- }
-
- pub struct ProgressBar;
-
- impl progress_bar::StyleSheet for ProgressBar {
- fn style(&self) -> progress_bar::Style {
- progress_bar::Style {
- background: SURFACE.into(),
- bar: ACTIVE.into(),
- border_radius: 10.0,
- }
- }
- }
-
- pub struct Checkbox;
-
- impl checkbox::StyleSheet for Checkbox {
- fn active(&self, is_checked: bool) -> checkbox::Style {
- checkbox::Style {
- background: if is_checked { ACTIVE } else { SURFACE }
- .into(),
- checkmark_color: Color::WHITE,
- border_radius: 2.0,
- border_width: 1.0,
- border_color: ACTIVE,
- text_color: None,
- }
- }
-
- fn hovered(&self, is_checked: bool) -> checkbox::Style {
- checkbox::Style {
- background: Color {
- a: 0.8,
- ..if is_checked { ACTIVE } else { SURFACE }
- }
- .into(),
- ..self.active(is_checked)
- }
- }
- }
-
- pub struct Toggler;
-
- impl toggler::StyleSheet for Toggler {
- fn active(&self, is_active: bool) -> toggler::Style {
- toggler::Style {
- background: if is_active { ACTIVE } else { SURFACE },
- background_border: None,
- foreground: if is_active { Color::WHITE } else { ACTIVE },
- foreground_border: None,
- }
- }
-
- fn hovered(&self, is_active: bool) -> toggler::Style {
- toggler::Style {
- background: if is_active { ACTIVE } else { SURFACE },
- background_border: None,
- foreground: if is_active {
- Color {
- a: 0.5,
- ..Color::WHITE
- }
- } else {
- Color { a: 0.5, ..ACTIVE }
- },
- foreground_border: None,
- }
- }
- }
-
- pub struct Rule;
-
- impl rule::StyleSheet for Rule {
- fn style(&self) -> rule::Style {
- rule::Style {
- color: SURFACE,
- width: 2,
- radius: 1.0,
- fill_mode: rule::FillMode::Padded(15),
- }
- }
- }
+ fn theme(&self) -> Theme {
+ self.theme
}
}
diff --git a/examples/svg/src/main.rs b/examples/svg/src/main.rs
index 8707fa3b..27d175da 100644
--- a/examples/svg/src/main.rs
+++ b/examples/svg/src/main.rs
@@ -1,4 +1,5 @@
-use iced::{Container, Element, Length, Sandbox, Settings, Svg};
+use iced::widget::{container, svg};
+use iced::{Element, Length, Sandbox, Settings};
pub fn main() -> iced::Result {
Tiger::run(Settings::default())
@@ -19,15 +20,15 @@ impl Sandbox for Tiger {
fn update(&mut self, _message: ()) {}
- fn view(&mut self) -> Element<()> {
- let svg = Svg::from_path(format!(
+ fn view(&self) -> Element<()> {
+ let svg = svg(svg::Handle::from_path(format!(
"{}/resources/tiger.svg",
env!("CARGO_MANIFEST_DIR")
- ))
+ )))
.width(Length::Fill)
.height(Length::Fill);
- Container::new(svg)
+ container(svg)
.width(Length::Fill)
.height(Length::Fill)
.padding(20)
diff --git a/examples/system_information/Cargo.toml b/examples/system_information/Cargo.toml
new file mode 100644
index 00000000..7d1e4b94
--- /dev/null
+++ b/examples/system_information/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "system_information"
+version = "0.1.0"
+authors = ["Richard <richardsoncusto@gmail.com>"]
+edition = "2021"
+publish = false
+
+[dependencies]
+iced = { path = "../..", features = ["system"] }
+bytesize = { version = "1.1.0" }
diff --git a/examples/system_information/src/main.rs b/examples/system_information/src/main.rs
new file mode 100644
index 00000000..af67742f
--- /dev/null
+++ b/examples/system_information/src/main.rs
@@ -0,0 +1,149 @@
+use iced::widget::{button, column, container, text};
+use iced::{
+ executor, system, Application, Command, Element, Length, Settings, Theme,
+};
+
+use bytesize::ByteSize;
+
+pub fn main() -> iced::Result {
+ Example::run(Settings::default())
+}
+
+enum Example {
+ Loading,
+ Loaded { information: system::Information },
+}
+
+#[derive(Clone, Debug)]
+enum Message {
+ InformationReceived(system::Information),
+ Refresh,
+}
+
+impl Application for Example {
+ type Message = Message;
+ type Theme = Theme;
+ type Executor = executor::Default;
+ type Flags = ();
+
+ fn new(_flags: ()) -> (Self, Command<Message>) {
+ (
+ Self::Loading,
+ system::fetch_information(Message::InformationReceived),
+ )
+ }
+
+ fn title(&self) -> String {
+ String::from("System Information - Iced")
+ }
+
+ fn update(&mut self, message: Message) -> Command<Message> {
+ match message {
+ Message::Refresh => {
+ *self = Self::Loading;
+
+ return system::fetch_information(Message::InformationReceived);
+ }
+ Message::InformationReceived(information) => {
+ *self = Self::Loaded { information };
+ }
+ }
+
+ Command::none()
+ }
+
+ fn view(&self) -> Element<Message> {
+ let content: Element<_> = match self {
+ Example::Loading => text("Loading...").size(40).into(),
+ Example::Loaded { information } => {
+ let system_name = text(format!(
+ "System name: {}",
+ information
+ .system_name
+ .as_ref()
+ .unwrap_or(&"unknown".to_string())
+ ));
+
+ let system_kernel = text(format!(
+ "System kernel: {}",
+ information
+ .system_kernel
+ .as_ref()
+ .unwrap_or(&"unknown".to_string())
+ ));
+
+ let system_version = text(format!(
+ "System version: {}",
+ information
+ .system_version
+ .as_ref()
+ .unwrap_or(&"unknown".to_string())
+ ));
+
+ let cpu_brand =
+ text(format!("Processor brand: {}", information.cpu_brand));
+
+ let cpu_cores = text(format!(
+ "Processor cores: {}",
+ information
+ .cpu_cores
+ .map_or("unknown".to_string(), |cores| cores
+ .to_string())
+ ));
+
+ let memory_readable =
+ ByteSize::kb(information.memory_total).to_string();
+
+ let memory_total = text(format!(
+ "Memory (total): {} kb ({})",
+ information.memory_total, memory_readable
+ ));
+
+ let memory_text = if let Some(memory_used) =
+ information.memory_used
+ {
+ let memory_readable = ByteSize::kb(memory_used).to_string();
+
+ format!("{} kb ({})", memory_used, memory_readable)
+ } else {
+ String::from("None")
+ };
+
+ let memory_used =
+ text(format!("Memory (used): {}", memory_text));
+
+ let graphics_adapter = text(format!(
+ "Graphics adapter: {}",
+ information.graphics_adapter
+ ));
+
+ let graphics_backend = text(format!(
+ "Graphics backend: {}",
+ information.graphics_backend
+ ));
+
+ column![
+ system_name.size(30),
+ system_kernel.size(30),
+ system_version.size(30),
+ cpu_brand.size(30),
+ cpu_cores.size(30),
+ memory_total.size(30),
+ memory_used.size(30),
+ graphics_adapter.size(30),
+ graphics_backend.size(30),
+ button("Refresh").on_press(Message::Refresh)
+ ]
+ .spacing(10)
+ .into()
+ }
+ };
+
+ container(content)
+ .center_x()
+ .center_y()
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .into()
+ }
+}
diff --git a/examples/todos/Cargo.toml b/examples/todos/Cargo.toml
index 5781ddef..2326ffc6 100644
--- a/examples/todos/Cargo.toml
+++ b/examples/todos/Cargo.toml
@@ -9,6 +9,7 @@ publish = false
iced = { path = "../..", features = ["async-std", "debug"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
+lazy_static = "1.4"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
async-std = "1.0"
@@ -20,6 +21,6 @@ wasm-timer = "0.2"
[package.metadata.deb]
assets = [
- ["target/release/todos", "usr/bin/iced-todos", "755"],
+ ["target/release-opt/todos", "usr/bin/iced-todos", "755"],
["iced-todos.desktop", "usr/share/applications/", "644"],
]
diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs
index 0b889407..bddc0e71 100644
--- a/examples/todos/src/main.rs
+++ b/examples/todos/src/main.rs
@@ -1,15 +1,31 @@
use iced::alignment::{self, Alignment};
-use iced::button::{self, Button};
-use iced::scrollable::{self, Scrollable};
-use iced::text_input::{self, TextInput};
-use iced::{
- Application, Checkbox, Column, Command, Container, Element, Font, Length,
- Row, Settings, Text,
+use iced::event::{self, Event};
+use iced::keyboard;
+use iced::subscription;
+use iced::theme::{self, Theme};
+use iced::widget::{
+ self, button, checkbox, column, container, row, scrollable, text,
+ text_input, Text,
};
+use iced::window;
+use iced::{Application, Element};
+use iced::{Color, Command, Font, Length, Settings, Subscription};
+
+use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
+lazy_static! {
+ static ref INPUT_ID: text_input::Id = text_input::Id::unique();
+}
+
pub fn main() -> iced::Result {
- Todos::run(Settings::default())
+ Todos::run(Settings {
+ window: window::Settings {
+ size: (500, 800),
+ ..window::Settings::default()
+ },
+ ..Settings::default()
+ })
}
#[derive(Debug)]
@@ -20,12 +36,9 @@ enum Todos {
#[derive(Debug, Default)]
struct State {
- scroll: scrollable::State,
- input: text_input::State,
input_value: String,
filter: Filter,
tasks: Vec<Task>,
- controls: Controls,
dirty: bool,
saving: bool,
}
@@ -38,11 +51,13 @@ enum Message {
CreateTask,
FilterChanged(Filter),
TaskMessage(usize, TaskMessage),
+ TabPressed { shift: bool },
}
impl Application for Todos {
- type Executor = iced::executor::Default;
type Message = Message;
+ type Theme = Theme;
+ type Executor = iced::executor::Default;
type Flags = ();
fn new(_flags: ()) -> (Todos, Command<Message>) {
@@ -79,14 +94,16 @@ impl Application for Todos {
_ => {}
}
- Command::none()
+ text_input::focus(INPUT_ID.clone())
}
Todos::Loaded(state) => {
let mut saved = false;
- match message {
+ let command = match message {
Message::InputChanged(value) => {
state.input_value = value;
+
+ Command::none()
}
Message::CreateTask => {
if !state.input_value.is_empty() {
@@ -95,30 +112,56 @@ impl Application for Todos {
.push(Task::new(state.input_value.clone()));
state.input_value.clear();
}
+
+ Command::none()
}
Message::FilterChanged(filter) => {
state.filter = filter;
+
+ Command::none()
}
Message::TaskMessage(i, TaskMessage::Delete) => {
state.tasks.remove(i);
+
+ Command::none()
}
Message::TaskMessage(i, task_message) => {
if let Some(task) = state.tasks.get_mut(i) {
+ let should_focus =
+ matches!(task_message, TaskMessage::Edit);
+
task.update(task_message);
+
+ if should_focus {
+ text_input::focus(Task::text_input_id(i))
+ } else {
+ Command::none()
+ }
+ } else {
+ Command::none()
}
}
Message::Saved(_) => {
state.saving = false;
saved = true;
+
+ Command::none()
}
- _ => {}
- }
+ Message::TabPressed { shift } => {
+ if shift {
+ widget::focus_previous()
+ } else {
+ widget::focus_next()
+ }
+ }
+ _ => Command::none(),
+ };
if !saved {
state.dirty = true;
}
- if state.dirty && !state.saving {
+ let save = if state.dirty && !state.saving {
state.dirty = false;
state.saving = true;
@@ -133,54 +176,57 @@ impl Application for Todos {
)
} else {
Command::none()
- }
+ };
+
+ Command::batch(vec![command, save])
}
}
}
- fn view(&mut self) -> Element<Message> {
+ fn view(&self) -> Element<Message> {
match self {
Todos::Loading => loading_message(),
Todos::Loaded(State {
- scroll,
- input,
input_value,
filter,
tasks,
- controls,
..
}) => {
- let title = Text::new("todos")
+ let title = text("todos")
.width(Length::Fill)
.size(100)
- .color([0.5, 0.5, 0.5])
+ .style(Color::from([0.5, 0.5, 0.5]))
.horizontal_alignment(alignment::Horizontal::Center);
- let input = TextInput::new(
- input,
+ let input = text_input(
"What needs to be done?",
input_value,
Message::InputChanged,
)
+ .id(INPUT_ID.clone())
.padding(15)
.size(30)
.on_submit(Message::CreateTask);
- let controls = controls.view(&tasks, *filter);
+ let controls = view_controls(tasks, *filter);
let filtered_tasks =
tasks.iter().filter(|task| filter.matches(task));
let tasks: Element<_> = if filtered_tasks.count() > 0 {
- tasks
- .iter_mut()
- .enumerate()
- .filter(|(_, task)| filter.matches(task))
- .fold(Column::new().spacing(20), |column, (i, task)| {
- column.push(task.view().map(move |message| {
- Message::TaskMessage(i, message)
- }))
- })
- .into()
+ column(
+ tasks
+ .iter()
+ .enumerate()
+ .filter(|(_, task)| filter.matches(task))
+ .map(|(i, task)| {
+ task.view(i).map(move |message| {
+ Message::TaskMessage(i, message)
+ })
+ })
+ .collect(),
+ )
+ .spacing(10)
+ .into()
} else {
empty_message(match filter {
Filter::All => "You have not created a task yet...",
@@ -191,23 +237,36 @@ impl Application for Todos {
})
};
- let content = Column::new()
- .max_width(800)
+ let content = column![title, input, controls, tasks]
.spacing(20)
- .push(title)
- .push(input)
- .push(controls)
- .push(tasks);
-
- Scrollable::new(scroll)
- .padding(40)
- .push(
- Container::new(content).width(Length::Fill).center_x(),
- )
- .into()
+ .max_width(800);
+
+ scrollable(
+ container(content)
+ .width(Length::Fill)
+ .padding(40)
+ .center_x(),
+ )
+ .into()
}
}
}
+
+ fn subscription(&self) -> Subscription<Message> {
+ subscription::events_with(|event, status| match (event, status) {
+ (
+ Event::Keyboard(keyboard::Event::KeyPressed {
+ key_code: keyboard::KeyCode::Tab,
+ modifiers,
+ ..
+ }),
+ event::Status::Ignored,
+ ) => Some(Message::TabPressed {
+ shift: modifiers.shift(),
+ }),
+ _ => None,
+ })
+ }
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -221,20 +280,13 @@ struct Task {
#[derive(Debug, Clone)]
pub enum TaskState {
- Idle {
- edit_button: button::State,
- },
- Editing {
- text_input: text_input::State,
- delete_button: button::State,
- },
+ Idle,
+ Editing,
}
impl Default for TaskState {
fn default() -> Self {
- TaskState::Idle {
- edit_button: button::State::new(),
- }
+ Self::Idle
}
}
@@ -248,13 +300,15 @@ pub enum TaskMessage {
}
impl Task {
+ fn text_input_id(i: usize) -> text_input::Id {
+ text_input::Id::new(format!("task-{}", i))
+ }
+
fn new(description: String) -> Self {
Task {
description,
completed: false,
- state: TaskState::Idle {
- edit_button: button::State::new(),
- },
+ state: TaskState::Idle,
}
}
@@ -264,150 +318,100 @@ impl Task {
self.completed = completed;
}
TaskMessage::Edit => {
- let mut text_input = text_input::State::focused();
- text_input.select_all();
-
- self.state = TaskState::Editing {
- text_input,
- delete_button: button::State::new(),
- };
+ self.state = TaskState::Editing;
}
TaskMessage::DescriptionEdited(new_description) => {
self.description = new_description;
}
TaskMessage::FinishEdition => {
if !self.description.is_empty() {
- self.state = TaskState::Idle {
- edit_button: button::State::new(),
- }
+ self.state = TaskState::Idle;
}
}
TaskMessage::Delete => {}
}
}
- fn view(&mut self) -> Element<TaskMessage> {
- match &mut self.state {
- TaskState::Idle { edit_button } => {
- let checkbox = Checkbox::new(
- self.completed,
+ fn view(&self, i: usize) -> Element<TaskMessage> {
+ match &self.state {
+ TaskState::Idle => {
+ let checkbox = checkbox(
&self.description,
+ self.completed,
TaskMessage::Completed,
)
.width(Length::Fill);
- Row::new()
- .spacing(20)
- .align_items(Alignment::Center)
- .push(checkbox)
- .push(
- Button::new(edit_button, edit_icon())
- .on_press(TaskMessage::Edit)
- .padding(10)
- .style(style::Button::Icon),
- )
- .into()
+ row![
+ checkbox,
+ button(edit_icon())
+ .on_press(TaskMessage::Edit)
+ .padding(10)
+ .style(theme::Button::Text),
+ ]
+ .spacing(20)
+ .align_items(Alignment::Center)
+ .into()
}
- TaskState::Editing {
- text_input,
- delete_button,
- } => {
- let text_input = TextInput::new(
- text_input,
+ TaskState::Editing => {
+ let text_input = text_input(
"Describe your task...",
&self.description,
TaskMessage::DescriptionEdited,
)
+ .id(Self::text_input_id(i))
.on_submit(TaskMessage::FinishEdition)
.padding(10);
- Row::new()
- .spacing(20)
- .align_items(Alignment::Center)
- .push(text_input)
- .push(
- Button::new(
- delete_button,
- Row::new()
- .spacing(10)
- .push(delete_icon())
- .push(Text::new("Delete")),
- )
+ row![
+ text_input,
+ button(row![delete_icon(), "Delete"].spacing(10))
.on_press(TaskMessage::Delete)
.padding(10)
- .style(style::Button::Destructive),
- )
- .into()
+ .style(theme::Button::Destructive)
+ ]
+ .spacing(20)
+ .align_items(Alignment::Center)
+ .into()
}
}
}
}
-#[derive(Debug, Default, Clone)]
-pub struct Controls {
- all_button: button::State,
- active_button: button::State,
- completed_button: button::State,
-}
+fn view_controls(tasks: &[Task], current_filter: Filter) -> Element<Message> {
+ let tasks_left = tasks.iter().filter(|task| !task.completed).count();
-impl Controls {
- fn view(&mut self, tasks: &[Task], current_filter: Filter) -> Row<Message> {
- let Controls {
- all_button,
- active_button,
- completed_button,
- } = self;
-
- let tasks_left = tasks.iter().filter(|task| !task.completed).count();
-
- let filter_button = |state, label, filter, current_filter| {
- let label = Text::new(label).size(16);
- let button =
- Button::new(state, label).style(if filter == current_filter {
- style::Button::FilterSelected
- } else {
- style::Button::FilterActive
- });
+ let filter_button = |label, filter, current_filter| {
+ let label = text(label).size(16);
- button.on_press(Message::FilterChanged(filter)).padding(8)
- };
-
- Row::new()
- .spacing(20)
- .align_items(Alignment::Center)
- .push(
- Text::new(format!(
- "{} {} left",
- tasks_left,
- if tasks_left == 1 { "task" } else { "tasks" }
- ))
- .width(Length::Fill)
- .size(16),
- )
- .push(
- Row::new()
- .width(Length::Shrink)
- .spacing(10)
- .push(filter_button(
- all_button,
- "All",
- Filter::All,
- current_filter,
- ))
- .push(filter_button(
- active_button,
- "Active",
- Filter::Active,
- current_filter,
- ))
- .push(filter_button(
- completed_button,
- "Completed",
- Filter::Completed,
- current_filter,
- )),
- )
- }
+ let button = button(label).style(if filter == current_filter {
+ theme::Button::Primary
+ } else {
+ theme::Button::Text
+ });
+
+ button.on_press(Message::FilterChanged(filter)).padding(8)
+ };
+
+ row![
+ text(format!(
+ "{} {} left",
+ tasks_left,
+ if tasks_left == 1 { "task" } else { "tasks" }
+ ))
+ .width(Length::Fill)
+ .size(16),
+ row![
+ filter_button("All", Filter::All, current_filter),
+ filter_button("Active", Filter::Active, current_filter),
+ filter_button("Completed", Filter::Completed, current_filter,),
+ ]
+ .width(Length::Shrink)
+ .spacing(10)
+ ]
+ .spacing(20)
+ .align_items(Alignment::Center)
+ .into()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
@@ -434,8 +438,8 @@ impl Filter {
}
fn loading_message<'a>() -> Element<'a, Message> {
- Container::new(
- Text::new("Loading...")
+ container(
+ text("Loading...")
.horizontal_alignment(alignment::Horizontal::Center)
.size(50),
)
@@ -445,13 +449,13 @@ fn loading_message<'a>() -> Element<'a, Message> {
.into()
}
-fn empty_message<'a>(message: &str) -> Element<'a, Message> {
- Container::new(
- Text::new(message)
+fn empty_message(message: &str) -> Element<'_, Message> {
+ container(
+ text(message)
.width(Length::Fill)
.size(25)
.horizontal_alignment(alignment::Horizontal::Center)
- .color([0.7, 0.7, 0.7]),
+ .style(Color::from([0.7, 0.7, 0.7])),
)
.width(Length::Fill)
.height(Length::Units(200))
@@ -462,22 +466,22 @@ fn empty_message<'a>(message: &str) -> Element<'a, Message> {
// Fonts
const ICONS: Font = Font::External {
name: "Icons",
- bytes: include_bytes!("../fonts/icons.ttf"),
+ bytes: include_bytes!("../../todos/fonts/icons.ttf"),
};
-fn icon(unicode: char) -> Text {
- Text::new(unicode.to_string())
+fn icon(unicode: char) -> Text<'static> {
+ text(unicode.to_string())
.font(ICONS)
.width(Length::Units(20))
.horizontal_alignment(alignment::Horizontal::Center)
.size(20)
}
-fn edit_icon() -> Text {
+fn edit_icon() -> Text<'static> {
icon('\u{F303}')
}
-fn delete_icon() -> Text {
+fn delete_icon() -> Text<'static> {
icon('\u{F1F8}')
}
@@ -491,15 +495,15 @@ struct SavedState {
#[derive(Debug, Clone)]
enum LoadError {
- FileError,
- FormatError,
+ File,
+ Format,
}
#[derive(Debug, Clone)]
enum SaveError {
- FileError,
- WriteError,
- FormatError,
+ File,
+ Write,
+ Format,
}
#[cfg(not(target_arch = "wasm32"))]
@@ -510,7 +514,7 @@ impl SavedState {
{
project_dirs.data_dir().into()
} else {
- std::env::current_dir().unwrap_or(std::path::PathBuf::new())
+ std::env::current_dir().unwrap_or_default()
};
path.push("todos.json");
@@ -525,37 +529,37 @@ impl SavedState {
let mut file = async_std::fs::File::open(Self::path())
.await
- .map_err(|_| LoadError::FileError)?;
+ .map_err(|_| LoadError::File)?;
file.read_to_string(&mut contents)
.await
- .map_err(|_| LoadError::FileError)?;
+ .map_err(|_| LoadError::File)?;
- serde_json::from_str(&contents).map_err(|_| LoadError::FormatError)
+ serde_json::from_str(&contents).map_err(|_| LoadError::Format)
}
async fn save(self) -> Result<(), SaveError> {
use async_std::prelude::*;
let json = serde_json::to_string_pretty(&self)
- .map_err(|_| SaveError::FormatError)?;
+ .map_err(|_| SaveError::Format)?;
let path = Self::path();
if let Some(dir) = path.parent() {
async_std::fs::create_dir_all(dir)
.await
- .map_err(|_| SaveError::FileError)?;
+ .map_err(|_| SaveError::File)?;
}
{
let mut file = async_std::fs::File::create(path)
.await
- .map_err(|_| SaveError::FileError)?;
+ .map_err(|_| SaveError::File)?;
file.write_all(json.as_bytes())
.await
- .map_err(|_| SaveError::WriteError)?;
+ .map_err(|_| SaveError::Write)?;
}
// This is a simple way to save at most once every couple seconds
@@ -574,82 +578,28 @@ impl SavedState {
}
async fn load() -> Result<SavedState, LoadError> {
- let storage = Self::storage().ok_or(LoadError::FileError)?;
+ let storage = Self::storage().ok_or(LoadError::File)?;
let contents = storage
.get_item("state")
- .map_err(|_| LoadError::FileError)?
- .ok_or(LoadError::FileError)?;
+ .map_err(|_| LoadError::File)?
+ .ok_or(LoadError::File)?;
- serde_json::from_str(&contents).map_err(|_| LoadError::FormatError)
+ serde_json::from_str(&contents).map_err(|_| LoadError::Format)
}
async fn save(self) -> Result<(), SaveError> {
- let storage = Self::storage().ok_or(SaveError::FileError)?;
+ let storage = Self::storage().ok_or(SaveError::File)?;
let json = serde_json::to_string_pretty(&self)
- .map_err(|_| SaveError::FormatError)?;
+ .map_err(|_| SaveError::Format)?;
storage
.set_item("state", &json)
- .map_err(|_| SaveError::WriteError)?;
+ .map_err(|_| SaveError::Write)?;
let _ = wasm_timer::Delay::new(std::time::Duration::from_secs(2)).await;
Ok(())
}
}
-
-mod style {
- use iced::{button, Background, Color, Vector};
-
- pub enum Button {
- FilterActive,
- FilterSelected,
- Icon,
- Destructive,
- }
-
- impl button::StyleSheet for Button {
- fn active(&self) -> button::Style {
- match self {
- Button::FilterActive => button::Style::default(),
- Button::FilterSelected => button::Style {
- background: Some(Background::Color(Color::from_rgb(
- 0.2, 0.2, 0.7,
- ))),
- border_radius: 10.0,
- text_color: Color::WHITE,
- ..button::Style::default()
- },
- Button::Icon => button::Style {
- text_color: Color::from_rgb(0.5, 0.5, 0.5),
- ..button::Style::default()
- },
- Button::Destructive => button::Style {
- background: Some(Background::Color(Color::from_rgb(
- 0.8, 0.2, 0.2,
- ))),
- border_radius: 5.0,
- text_color: Color::WHITE,
- shadow_offset: Vector::new(1.0, 1.0),
- ..button::Style::default()
- },
- }
- }
-
- fn hovered(&self) -> button::Style {
- let active = self.active();
-
- button::Style {
- text_color: match self {
- Button::Icon => Color::from_rgb(0.2, 0.2, 0.7),
- Button::FilterActive => Color::from_rgb(0.2, 0.2, 0.7),
- _ => active.text_color,
- },
- shadow_offset: active.shadow_offset + Vector::new(0.0, 1.0),
- ..active
- }
- }
- }
-}
diff --git a/examples/tooltip/src/main.rs b/examples/tooltip/src/main.rs
index cfeaf6a6..35b862a8 100644
--- a/examples/tooltip/src/main.rs
+++ b/examples/tooltip/src/main.rs
@@ -1,138 +1,75 @@
-use iced::tooltip::{self, Tooltip};
-use iced::{
- alignment, button, Alignment, Button, Column, Container, Element, Length,
- Row, Sandbox, Settings, Text,
-};
+use iced::theme;
+use iced::widget::tooltip::Position;
+use iced::widget::{button, container, tooltip};
+use iced::{Element, Length, Sandbox, Settings};
-pub fn main() {
- Example::run(Settings::default()).unwrap()
+pub fn main() -> iced::Result {
+ Example::run(Settings::default())
}
-#[derive(Default)]
struct Example {
- top: button::State,
- bottom: button::State,
- right: button::State,
- left: button::State,
- follow_cursor: button::State,
+ position: Position,
}
-#[derive(Debug, Clone, Copy)]
-struct Message;
+#[derive(Debug, Clone)]
+enum Message {
+ ChangePosition,
+}
impl Sandbox for Example {
type Message = Message;
fn new() -> Self {
- Self::default()
+ Self {
+ position: Position::Bottom,
+ }
}
fn title(&self) -> String {
String::from("Tooltip - Iced")
}
- fn update(&mut self, _message: Message) {}
-
- fn view(&mut self) -> Element<Message> {
- let top =
- tooltip("Tooltip at top", &mut self.top, tooltip::Position::Top);
-
- let bottom = tooltip(
- "Tooltip at bottom",
- &mut self.bottom,
- tooltip::Position::Bottom,
- );
-
- let left =
- tooltip("Tooltip at left", &mut self.left, tooltip::Position::Left);
-
- let right = tooltip(
- "Tooltip at right",
- &mut self.right,
- tooltip::Position::Right,
- );
-
- let fixed_tooltips = Row::with_children(vec![
- top.into(),
- bottom.into(),
- left.into(),
- right.into(),
- ])
- .width(Length::Fill)
- .height(Length::Fill)
- .align_items(Alignment::Center)
- .spacing(50);
-
- let follow_cursor = tooltip(
- "Tooltip follows cursor",
- &mut self.follow_cursor,
- tooltip::Position::FollowCursor,
- );
+ fn update(&mut self, message: Message) {
+ match message {
+ Message::ChangePosition => {
+ let position = match &self.position {
+ Position::FollowCursor => Position::Top,
+ Position::Top => Position::Bottom,
+ Position::Bottom => Position::Left,
+ Position::Left => Position::Right,
+ Position::Right => Position::FollowCursor,
+ };
+
+ self.position = position
+ }
+ }
+ }
- let content = Column::with_children(vec![
- Container::new(fixed_tooltips)
- .width(Length::Fill)
- .height(Length::Fill)
- .center_x()
- .center_y()
- .into(),
- follow_cursor.into(),
- ])
- .width(Length::Fill)
- .height(Length::Fill)
- .spacing(50);
+ fn view(&self) -> Element<Message> {
+ let tooltip = tooltip(
+ button("Press to change position")
+ .on_press(Message::ChangePosition),
+ position_to_text(self.position),
+ self.position,
+ )
+ .gap(10)
+ .style(theme::Container::Box);
- Container::new(content)
+ container(tooltip)
.width(Length::Fill)
.height(Length::Fill)
.center_x()
.center_y()
- .padding(50)
.into()
}
}
-fn tooltip<'a>(
- label: &str,
- button_state: &'a mut button::State,
- position: tooltip::Position,
-) -> Element<'a, Message> {
- Tooltip::new(
- Button::new(
- button_state,
- Text::new(label)
- .size(40)
- .width(Length::Fill)
- .height(Length::Fill)
- .horizontal_alignment(alignment::Horizontal::Center)
- .vertical_alignment(alignment::Vertical::Center),
- )
- .on_press(Message)
- .width(Length::Fill)
- .height(Length::Fill),
- "Tooltip",
- position,
- )
- .gap(5)
- .padding(10)
- .style(style::Tooltip)
- .into()
-}
-
-mod style {
- use iced::container;
- use iced::Color;
-
- pub struct Tooltip;
-
- impl container::StyleSheet for Tooltip {
- fn style(&self) -> container::Style {
- container::Style {
- text_color: Some(Color::from_rgb8(0xEE, 0xEE, 0xEE)),
- background: Some(Color::from_rgb(0.11, 0.42, 0.87).into()),
- border_radius: 12.0,
- ..container::Style::default()
- }
- }
+fn position_to_text<'a>(position: Position) -> &'a str {
+ match position {
+ Position::FollowCursor => "Follow Cursor",
+ Position::Top => "Top",
+ Position::Bottom => "Bottom",
+ Position::Left => "Left",
+ Position::Right => "Right",
}
}
diff --git a/examples/tour/README.md b/examples/tour/README.md
index e7cd2d5c..731e7e66 100644
--- a/examples/tour/README.md
+++ b/examples/tour/README.md
@@ -23,6 +23,11 @@ You can run the native version with `cargo run`:
cargo run --package tour
```
-The web version can be run by following [the usage instructions of `iced_web`] or by accessing [iced.rs](https://iced.rs/)!
+The web version can be run with [`trunk`]:
-[the usage instructions of `iced_web`]: https://github.com/iced-rs/iced_web#usage
+```
+cd examples/tour
+trunk serve
+```
+
+[`trunk`]: https://trunkrs.dev/
diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs
index e199c88c..378508e1 100644
--- a/examples/tour/src/main.rs
+++ b/examples/tour/src/main.rs
@@ -1,8 +1,11 @@
-use iced::{
- alignment, button, scrollable, slider, text_input, Button, Checkbox, Color,
- Column, Container, ContentFit, Element, Image, Length, Radio, Row, Sandbox,
- Scrollable, Settings, Slider, Space, Text, TextInput, Toggler,
+use iced::alignment;
+use iced::theme;
+use iced::widget::{
+ checkbox, column, container, horizontal_space, image, radio, row,
+ scrollable, slider, text, text_input, toggler, vertical_space,
};
+use iced::widget::{Button, Column, Container, Slider};
+use iced::{Color, Element, Length, Renderer, Sandbox, Settings};
pub fn main() -> iced::Result {
env_logger::init();
@@ -12,9 +15,6 @@ pub fn main() -> iced::Result {
pub struct Tour {
steps: Steps,
- scroll: scrollable::State,
- back_button: button::State,
- next_button: button::State,
debug: bool,
}
@@ -24,9 +24,6 @@ impl Sandbox for Tour {
fn new() -> Tour {
Tour {
steps: Steps::new(),
- scroll: scrollable::State::new(),
- back_button: button::State::new(),
- next_button: button::State::new(),
debug: false,
}
}
@@ -49,56 +46,49 @@ impl Sandbox for Tour {
}
}
- fn view(&mut self) -> Element<Message> {
- let Tour {
- steps,
- scroll,
- back_button,
- next_button,
- ..
- } = self;
+ fn view(&self) -> Element<Message> {
+ let Tour { steps, .. } = self;
- let mut controls = Row::new();
+ let mut controls = row![];
if steps.has_previous() {
controls = controls.push(
- button(back_button, "Back")
+ button("Back")
.on_press(Message::BackPressed)
- .style(style::Button::Secondary),
+ .style(theme::Button::Secondary),
);
}
- controls = controls.push(Space::with_width(Length::Fill));
+ controls = controls.push(horizontal_space(Length::Fill));
if steps.can_continue() {
controls = controls.push(
- button(next_button, "Next")
+ button("Next")
.on_press(Message::NextPressed)
- .style(style::Button::Primary),
+ .style(theme::Button::Primary),
);
}
- let content: Element<_> = Column::new()
- .max_width(540)
- .spacing(20)
- .padding(20)
- .push(steps.view(self.debug).map(Message::StepMessage))
- .push(controls)
- .into();
-
- let content = if self.debug {
- content.explain(Color::BLACK)
- } else {
- content
- };
-
- let scrollable = Scrollable::new(scroll)
- .push(Container::new(content).width(Length::Fill).center_x());
+ let content: Element<_> = column![
+ steps.view(self.debug).map(Message::StepMessage),
+ controls,
+ ]
+ .max_width(540)
+ .spacing(20)
+ .padding(20)
+ .into();
+
+ let scrollable = scrollable(
+ container(if self.debug {
+ content.explain(Color::BLACK)
+ } else {
+ content
+ })
+ .width(Length::Fill)
+ .center_x(),
+ );
- Container::new(scrollable)
- .height(Length::Fill)
- .center_y()
- .into()
+ container(scrollable).height(Length::Fill).center_y().into()
}
}
@@ -119,35 +109,24 @@ impl Steps {
Steps {
steps: vec![
Step::Welcome,
- Step::Slider {
- state: slider::State::new(),
- value: 50,
- },
+ Step::Slider { value: 50 },
Step::RowsAndColumns {
layout: Layout::Row,
- spacing_slider: slider::State::new(),
spacing: 20,
},
Step::Text {
- size_slider: slider::State::new(),
size: 30,
- color_sliders: [slider::State::new(); 3],
color: Color::BLACK,
},
Step::Radio { selection: None },
Step::Toggler {
can_continue: false,
},
- Step::Image {
- height: 200,
- current_fit: ContentFit::Contain,
- slider: slider::State::new(),
- },
+ Step::Image { width: 300 },
Step::Scrollable,
Step::TextInput {
value: String::new(),
is_secure: false,
- state: text_input::State::new(),
},
Step::Debugger,
Step::End,
@@ -160,7 +139,7 @@ impl Steps {
self.steps[self.current].update(msg, debug);
}
- fn view(&mut self, debug: bool) -> Element<StepMessage> {
+ fn view(&self, debug: bool) -> Element<StepMessage> {
self.steps[self.current].view(debug)
}
@@ -192,38 +171,14 @@ impl Steps {
enum Step {
Welcome,
- Slider {
- state: slider::State,
- value: u8,
- },
- RowsAndColumns {
- layout: Layout,
- spacing_slider: slider::State,
- spacing: u16,
- },
- Text {
- size_slider: slider::State,
- size: u16,
- color_sliders: [slider::State; 3],
- color: Color,
- },
- Radio {
- selection: Option<Language>,
- },
- Toggler {
- can_continue: bool,
- },
- Image {
- height: u16,
- slider: slider::State,
- current_fit: ContentFit,
- },
+ Slider { value: u8 },
+ RowsAndColumns { layout: Layout, spacing: u16 },
+ Text { size: u16, color: Color },
+ Radio { selection: Option<Language> },
+ Toggler { can_continue: bool },
+ Image { width: u16 },
Scrollable,
- TextInput {
- value: String,
- is_secure: bool,
- state: text_input::State,
- },
+ TextInput { value: String, is_secure: bool },
Debugger,
End,
}
@@ -236,8 +191,7 @@ pub enum StepMessage {
TextSizeChanged(u16),
TextColorChanged(Color),
LanguageSelected(Language),
- ImageHeightChanged(u16),
- ImageFitSelected(ContentFit),
+ ImageWidthChanged(u16),
InputChanged(String),
ToggleSecureInput(bool),
DebugToggled(bool),
@@ -282,14 +236,9 @@ impl<'a> Step {
*spacing = new_spacing;
}
}
- StepMessage::ImageHeightChanged(new_height) => {
- if let Step::Image { height, .. } = self {
- *height = new_height;
- }
- }
- StepMessage::ImageFitSelected(fit) => {
- if let Step::Image { current_fit, .. } = self {
- *current_fit = fit;
+ StepMessage::ImageWidthChanged(new_width) => {
+ if let Step::Image { width, .. } = self {
+ *width = new_width;
}
}
StepMessage::InputChanged(new_value) => {
@@ -342,34 +291,21 @@ impl<'a> Step {
}
}
- fn view(&mut self, debug: bool) -> Element<StepMessage> {
+ fn view(&self, debug: bool) -> Element<StepMessage> {
match self {
Step::Welcome => Self::welcome(),
Step::Radio { selection } => Self::radio(*selection),
Step::Toggler { can_continue } => Self::toggler(*can_continue),
- Step::Slider { state, value } => Self::slider(state, *value),
- Step::Text {
- size_slider,
- size,
- color_sliders,
- color,
- } => Self::text(size_slider, *size, color_sliders, *color),
- Step::Image {
- height,
- slider,
- current_fit,
- } => Self::image(*height, slider, *current_fit),
- Step::RowsAndColumns {
- layout,
- spacing_slider,
- spacing,
- } => Self::rows_and_columns(*layout, spacing_slider, *spacing),
+ Step::Slider { value } => Self::slider(*value),
+ Step::Text { size, color } => Self::text(*size, *color),
+ Step::Image { width } => Self::image(*width),
+ Step::RowsAndColumns { layout, spacing } => {
+ Self::rows_and_columns(*layout, *spacing)
+ }
Step::Scrollable => Self::scrollable(),
- Step::TextInput {
- value,
- is_secure,
- state,
- } => Self::text_input(value, *is_secure, state),
+ Step::TextInput { value, is_secure } => {
+ Self::text_input(value, *is_secure)
+ }
Step::Debugger => Self::debugger(debug),
Step::End => Self::end(),
}
@@ -377,59 +313,51 @@ impl<'a> Step {
}
fn container(title: &str) -> Column<'a, StepMessage> {
- Column::new().spacing(20).push(Text::new(title).size(50))
+ column![text(title).size(50)].spacing(20)
}
fn welcome() -> Column<'a, StepMessage> {
Self::container("Welcome!")
- .push(Text::new(
+ .push(
"This is a simple tour meant to showcase a bunch of widgets \
that can be easily implemented on top of Iced.",
- ))
- .push(Text::new(
+ )
+ .push(
"Iced is a cross-platform GUI library for Rust focused on \
simplicity and type-safety. It is heavily inspired by Elm.",
- ))
- .push(Text::new(
+ )
+ .push(
"It was originally born as part of Coffee, an opinionated \
2D game engine for Rust.",
- ))
- .push(Text::new(
+ )
+ .push(
"On native platforms, Iced provides by default a renderer \
built on top of wgpu, a graphics library supporting Vulkan, \
Metal, DX11, and DX12.",
- ))
- .push(Text::new(
+ )
+ .push(
"Additionally, this tour can also run on WebAssembly thanks \
to dodrio, an experimental VDOM library for Rust.",
- ))
- .push(Text::new(
+ )
+ .push(
"You will need to interact with the UI in order to reach the \
end!",
- ))
+ )
}
- fn slider(
- state: &'a mut slider::State,
- value: u8,
- ) -> Column<'a, StepMessage> {
+ fn slider(value: u8) -> Column<'a, StepMessage> {
Self::container("Slider")
- .push(Text::new(
+ .push(
"A slider allows you to smoothly select a value from a range \
of values.",
- ))
- .push(Text::new(
+ )
+ .push(
"The following slider lets you choose an integer from \
0 to 100:",
- ))
- .push(Slider::new(
- state,
- 0..=100,
- value,
- StepMessage::SliderChanged,
- ))
+ )
+ .push(slider(0..=100, value, StepMessage::SliderChanged))
.push(
- Text::new(value.to_string())
+ text(value.to_string())
.width(Length::Fill)
.horizontal_alignment(alignment::Horizontal::Center),
)
@@ -437,257 +365,198 @@ impl<'a> Step {
fn rows_and_columns(
layout: Layout,
- spacing_slider: &'a mut slider::State,
spacing: u16,
) -> Column<'a, StepMessage> {
- let row_radio = Radio::new(
- Layout::Row,
- "Row",
- Some(layout),
- StepMessage::LayoutChanged,
- );
+ let row_radio =
+ radio("Row", Layout::Row, Some(layout), StepMessage::LayoutChanged);
- let column_radio = Radio::new(
- Layout::Column,
+ let column_radio = radio(
"Column",
+ Layout::Column,
Some(layout),
StepMessage::LayoutChanged,
);
let layout_section: Element<_> = match layout {
- Layout::Row => Row::new()
- .spacing(spacing)
- .push(row_radio)
- .push(column_radio)
- .into(),
- Layout::Column => Column::new()
- .spacing(spacing)
- .push(row_radio)
- .push(column_radio)
- .into(),
+ Layout::Row => {
+ row![row_radio, column_radio].spacing(spacing).into()
+ }
+ Layout::Column => {
+ column![row_radio, column_radio].spacing(spacing).into()
+ }
};
- let spacing_section = Column::new()
- .spacing(10)
- .push(Slider::new(
- spacing_slider,
- 0..=80,
- spacing,
- StepMessage::SpacingChanged,
- ))
- .push(
- Text::new(format!("{} px", spacing))
- .width(Length::Fill)
- .horizontal_alignment(alignment::Horizontal::Center),
- );
+ let spacing_section = column![
+ slider(0..=80, spacing, StepMessage::SpacingChanged),
+ text(format!("{} px", spacing))
+ .width(Length::Fill)
+ .horizontal_alignment(alignment::Horizontal::Center),
+ ]
+ .spacing(10);
Self::container("Rows and columns")
.spacing(spacing)
- .push(Text::new(
+ .push(
"Iced uses a layout model based on flexbox to position UI \
elements.",
- ))
- .push(Text::new(
+ )
+ .push(
"Rows and columns can be used to distribute content \
horizontally or vertically, respectively.",
- ))
+ )
.push(layout_section)
- .push(Text::new(
- "You can also easily change the spacing between elements:",
- ))
+ .push("You can also easily change the spacing between elements:")
.push(spacing_section)
}
- fn text(
- size_slider: &'a mut slider::State,
- size: u16,
- color_sliders: &'a mut [slider::State; 3],
- color: Color,
- ) -> Column<'a, StepMessage> {
- let size_section = Column::new()
- .padding(20)
- .spacing(20)
- .push(Text::new("You can change its size:"))
- .push(Text::new(format!("This text is {} pixels", size)).size(size))
- .push(Slider::new(
- size_slider,
- 10..=70,
- size,
- StepMessage::TextSizeChanged,
- ));
-
- let [red, green, blue] = color_sliders;
-
- let color_sliders = Row::new()
- .spacing(10)
- .push(color_slider(red, color.r, move |r| Color { r, ..color }))
- .push(color_slider(green, color.g, move |g| Color { g, ..color }))
- .push(color_slider(blue, color.b, move |b| Color { b, ..color }));
+ fn text(size: u16, color: Color) -> Column<'a, StepMessage> {
+ let size_section = column![
+ "You can change its size:",
+ text(format!("This text is {} pixels", size)).size(size),
+ slider(10..=70, size, StepMessage::TextSizeChanged),
+ ]
+ .padding(20)
+ .spacing(20);
- let color_section = Column::new()
- .padding(20)
- .spacing(20)
- .push(Text::new("And its color:"))
- .push(Text::new(format!("{:?}", color)).color(color))
- .push(color_sliders);
+ let color_sliders = row![
+ color_slider(color.r, move |r| Color { r, ..color }),
+ color_slider(color.g, move |g| Color { g, ..color }),
+ color_slider(color.b, move |b| Color { b, ..color }),
+ ]
+ .spacing(10);
+
+ let color_section = column![
+ "And its color:",
+ text(format!("{:?}", color)).style(color),
+ color_sliders,
+ ]
+ .padding(20)
+ .spacing(20);
Self::container("Text")
- .push(Text::new(
+ .push(
"Text is probably the most essential widget for your UI. \
It will try to adapt to the dimensions of its container.",
- ))
+ )
.push(size_section)
.push(color_section)
}
fn radio(selection: Option<Language>) -> Column<'a, StepMessage> {
- let question = Column::new()
- .padding(20)
+ let question = column![
+ text("Iced is written in...").size(24),
+ column(
+ Language::all()
+ .iter()
+ .cloned()
+ .map(|language| {
+ radio(
+ language,
+ language,
+ selection,
+ StepMessage::LanguageSelected,
+ )
+ })
+ .map(Element::from)
+ .collect()
+ )
.spacing(10)
- .push(Text::new("Iced is written in...").size(24))
- .push(Language::all().iter().cloned().fold(
- Column::new().padding(10).spacing(20),
- |choices, language| {
- choices.push(Radio::new(
- language,
- language,
- selection,
- StepMessage::LanguageSelected,
- ))
- },
- ));
+ ]
+ .padding(20)
+ .spacing(10);
Self::container("Radio button")
- .push(Text::new(
+ .push(
"A radio button is normally used to represent a choice... \
Surprise test!",
- ))
+ )
.push(question)
- .push(Text::new(
+ .push(
"Iced works very well with iterators! The list above is \
basically created by folding a column over the different \
choices, creating a radio button for each one of them!",
- ))
+ )
}
fn toggler(can_continue: bool) -> Column<'a, StepMessage> {
Self::container("Toggler")
- .push(Text::new(
- "A toggler is mostly used to enable or disable something.",
- ))
+ .push("A toggler is mostly used to enable or disable something.")
.push(
- Container::new(Toggler::new(
+ Container::new(toggler(
+ "Toggle me to continue...".to_owned(),
can_continue,
- String::from("Toggle me to continue..."),
StepMessage::TogglerChanged,
))
.padding([0, 40]),
)
}
- fn image(
- height: u16,
- slider: &'a mut slider::State,
- current_fit: ContentFit,
- ) -> Column<'a, StepMessage> {
- const FIT_MODES: [(ContentFit, &str); 3] = [
- (ContentFit::Contain, "Contain"),
- (ContentFit::Cover, "Cover"),
- (ContentFit::Fill, "Fill"),
- ];
-
- let mode_selector = FIT_MODES.iter().fold(
- Column::new().padding(10).spacing(20),
- |choices, (mode, name)| {
- choices.push(Radio::new(
- *mode,
- *name,
- Some(current_fit),
- StepMessage::ImageFitSelected,
- ))
- },
- );
-
+ fn image(width: u16) -> Column<'a, StepMessage> {
Self::container("Image")
- .push(Text::new("Pictures of things in all shapes and sizes!"))
- .push(ferris(height, current_fit))
- .push(Slider::new(
- slider,
- 50..=500,
- height,
- StepMessage::ImageHeightChanged,
- ))
+ .push("An image that tries to keep its aspect ratio.")
+ .push(ferris(width))
+ .push(slider(100..=500, width, StepMessage::ImageWidthChanged))
.push(
- Text::new(format!("Height: {} px", height))
+ text(format!("Width: {} px", width))
.width(Length::Fill)
.horizontal_alignment(alignment::Horizontal::Center),
)
- .push(Text::new("Pick a content fit strategy:"))
- .push(mode_selector)
}
fn scrollable() -> Column<'a, StepMessage> {
Self::container("Scrollable")
- .push(Text::new(
+ .push(
"Iced supports scrollable content. Try it out! Find the \
button further below.",
- ))
+ )
.push(
- Text::new(
- "Tip: You can use the scrollbar to scroll down faster!",
- )
- .size(16),
+ text("Tip: You can use the scrollbar to scroll down faster!")
+ .size(16),
)
- .push(Column::new().height(Length::Units(4096)))
+ .push(vertical_space(Length::Units(4096)))
.push(
- Text::new("You are halfway there!")
+ text("You are halfway there!")
.width(Length::Fill)
.size(30)
.horizontal_alignment(alignment::Horizontal::Center),
)
- .push(Column::new().height(Length::Units(4096)))
- .push(ferris(200, ContentFit::Contain))
+ .push(vertical_space(Length::Units(4096)))
+ .push(ferris(300))
.push(
- Text::new("You made it!")
+ text("You made it!")
.width(Length::Fill)
.size(50)
.horizontal_alignment(alignment::Horizontal::Center),
)
}
- fn text_input(
- value: &str,
- is_secure: bool,
- state: &'a mut text_input::State,
- ) -> Column<'a, StepMessage> {
- let text_input = TextInput::new(
- state,
+ fn text_input(value: &str, is_secure: bool) -> Column<'a, StepMessage> {
+ let text_input = text_input(
"Type something to continue...",
value,
StepMessage::InputChanged,
)
.padding(10)
.size(30);
+
Self::container("Text input")
- .push(Text::new(
- "Use a text input to ask for different kinds of information.",
- ))
+ .push("Use a text input to ask for different kinds of information.")
.push(if is_secure {
text_input.password()
} else {
text_input
})
- .push(Checkbox::new(
- is_secure,
+ .push(checkbox(
"Enable password mode",
+ is_secure,
StepMessage::ToggleSecureInput,
))
- .push(Text::new(
+ .push(
"A text input produces a message every time it changes. It is \
very easy to keep track of its contents:",
- ))
+ )
.push(
- Text::new(if value.is_empty() {
+ text(if value.is_empty() {
"You have not typed anything yet..."
} else {
value
@@ -699,79 +568,62 @@ impl<'a> Step {
fn debugger(debug: bool) -> Column<'a, StepMessage> {
Self::container("Debugger")
- .push(Text::new(
+ .push(
"You can ask Iced to visually explain the layouting of the \
different elements comprising your UI!",
- ))
- .push(Text::new(
+ )
+ .push(
"Give it a shot! Check the following checkbox to be able to \
see element boundaries.",
- ))
+ )
.push(if cfg!(target_arch = "wasm32") {
Element::new(
- Text::new("Not available on web yet!")
- .color([0.7, 0.7, 0.7])
+ text("Not available on web yet!")
+ .style(Color::from([0.7, 0.7, 0.7]))
.horizontal_alignment(alignment::Horizontal::Center),
)
} else {
- Element::new(Checkbox::new(
- debug,
- "Explain layout",
- StepMessage::DebugToggled,
- ))
+ checkbox("Explain layout", debug, StepMessage::DebugToggled)
+ .into()
})
- .push(Text::new("Feel free to go back and take a look."))
+ .push("Feel free to go back and take a look.")
}
fn end() -> Column<'a, StepMessage> {
Self::container("You reached the end!")
- .push(Text::new(
- "This tour will be updated as more features are added.",
- ))
- .push(Text::new("Make sure to keep an eye on it!"))
+ .push("This tour will be updated as more features are added.")
+ .push("Make sure to keep an eye on it!")
}
}
-fn ferris<'a>(
- height: u16,
- content_fit: ContentFit,
-) -> Container<'a, StepMessage> {
- Container::new(
+fn ferris<'a>(width: u16) -> Container<'a, StepMessage> {
+ container(
// This should go away once we unify resource loading on native
// platforms
if cfg!(target_arch = "wasm32") {
- Image::new("tour/images/ferris.png")
+ image("tour/images/ferris.png")
} else {
- Image::new(format!(
- "{}/images/ferris.png",
- env!("CARGO_MANIFEST_DIR"),
- ))
+ image(format!("{}/images/ferris.png", env!("CARGO_MANIFEST_DIR")))
}
- .height(Length::Units(height))
- .content_fit(content_fit),
+ .width(Length::Units(width)),
)
.width(Length::Fill)
.center_x()
}
-fn button<'a, Message: Clone>(
- state: &'a mut button::State,
- label: &str,
-) -> Button<'a, Message> {
- Button::new(
- state,
- Text::new(label).horizontal_alignment(alignment::Horizontal::Center),
+fn button<'a, Message: Clone>(label: &str) -> Button<'a, Message> {
+ iced::widget::button(
+ text(label).horizontal_alignment(alignment::Horizontal::Center),
)
.padding(12)
- .min_width(100)
+ .width(Length::Units(100))
}
-fn color_slider(
- state: &mut slider::State,
+fn color_slider<'a>(
component: f32,
- update: impl Fn(f32) -> Color + 'static,
-) -> Slider<f64, StepMessage> {
- Slider::new(state, 0.0..=1.0, f64::from(component), move |c| {
+ update: impl Fn(f32) -> Color + 'a,
+) -> Slider<'a, f64, StepMessage, Renderer> {
+ slider(0.0..=1.0, f64::from(component), move |c| {
StepMessage::TextColorChanged(update(c as f32))
})
.step(0.01)
@@ -818,36 +670,3 @@ pub enum Layout {
Row,
Column,
}
-
-mod style {
- use iced::button;
- use iced::{Background, Color, Vector};
-
- pub enum Button {
- Primary,
- Secondary,
- }
-
- impl button::StyleSheet for Button {
- fn active(&self) -> button::Style {
- button::Style {
- background: Some(Background::Color(match self {
- Button::Primary => Color::from_rgb(0.11, 0.42, 0.87),
- Button::Secondary => Color::from_rgb(0.5, 0.5, 0.5),
- })),
- border_radius: 12.0,
- shadow_offset: Vector::new(1.0, 1.0),
- text_color: Color::from_rgb8(0xEE, 0xEE, 0xEE),
- ..button::Style::default()
- }
- }
-
- fn hovered(&self) -> button::Style {
- button::Style {
- text_color: Color::WHITE,
- shadow_offset: Vector::new(1.0, 2.0),
- ..self.active()
- }
- }
- }
-}
diff --git a/examples/url_handler/src/main.rs b/examples/url_handler/src/main.rs
index ee2d249a..3257b519 100644
--- a/examples/url_handler/src/main.rs
+++ b/examples/url_handler/src/main.rs
@@ -1,6 +1,7 @@
+use iced::executor;
+use iced::widget::{container, text};
use iced::{
- executor, Application, Command, Container, Element, Length, Settings,
- Subscription, Text,
+ Application, Command, Element, Length, Settings, Subscription, Theme,
};
use iced_native::{
event::{MacOS, PlatformSpecific},
@@ -22,8 +23,9 @@ enum Message {
}
impl Application for App {
- type Executor = executor::Default;
type Message = Message;
+ type Theme = Theme;
+ type Executor = executor::Default;
type Flags = ();
fn new(_flags: ()) -> (App, Command<Message>) {
@@ -53,13 +55,13 @@ impl Application for App {
iced_native::subscription::events().map(Message::EventOccurred)
}
- fn view(&mut self) -> Element<Message> {
+ fn view(&self) -> Element<Message> {
let content = match &self.url {
- Some(url) => Text::new(format!("{}", url)),
- None => Text::new("No URL received yet!"),
+ Some(url) => text(url),
+ None => text("No URL received yet!"),
};
- Container::new(content.size(48))
+ container(content.size(48))
.width(Length::Fill)
.height(Length::Fill)
.center_x()
diff --git a/examples/websocket/Cargo.toml b/examples/websocket/Cargo.toml
index db131dd7..3981f699 100644
--- a/examples/websocket/Cargo.toml
+++ b/examples/websocket/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "websocket"
-version = "0.1.0"
+version = "1.0.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
publish = false
@@ -9,6 +9,7 @@ publish = false
iced = { path = "../..", features = ["tokio", "debug"] }
iced_native = { path = "../../native" }
iced_futures = { path = "../../futures" }
+lazy_static = "1.4"
[dependencies.async-tungstenite]
version = "0.16"
diff --git a/examples/websocket/src/echo.rs b/examples/websocket/src/echo.rs
index 13596ddd..ae65e064 100644
--- a/examples/websocket/src/echo.rs
+++ b/examples/websocket/src/echo.rs
@@ -8,6 +8,7 @@ use futures::sink::SinkExt;
use futures::stream::StreamExt;
use async_tungstenite::tungstenite;
+use std::fmt;
pub fn connect() -> Subscription<Event> {
struct Connect;
@@ -32,7 +33,7 @@ pub fn connect() -> Subscription<Event> {
)
}
Err(_) => {
- let _ = tokio::time::sleep(
+ tokio::time::sleep(
tokio::time::Duration::from_secs(1),
)
.await;
@@ -63,7 +64,7 @@ pub fn connect() -> Subscription<Event> {
}
message = input.select_next_some() => {
- let result = websocket.send(tungstenite::Message::Text(String::from(message))).await;
+ let result = websocket.send(tungstenite::Message::Text(message.to_string())).await;
if result.is_ok() {
(None, State::Connected(websocket, input))
@@ -79,6 +80,7 @@ pub fn connect() -> Subscription<Event> {
}
#[derive(Debug)]
+#[allow(clippy::large_enum_variant)]
enum State {
Disconnected,
Connected(
@@ -101,8 +103,7 @@ pub struct Connection(mpsc::Sender<Message>);
impl Connection {
pub fn send(&mut self, message: Message) {
- let _ = self
- .0
+ self.0
.try_send(message)
.expect("Send message to echo server");
}
@@ -133,14 +134,14 @@ impl Message {
}
}
-impl From<Message> for String {
- fn from(message: Message) -> Self {
- match message {
- Message::Connected => String::from("Connected successfully!"),
+impl fmt::Display for Message {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Message::Connected => write!(f, "Connected successfully!"),
Message::Disconnected => {
- String::from("Connection lost... Retrying...")
+ write!(f, "Connection lost... Retrying...")
}
- Message::User(message) => message,
+ Message::User(message) => write!(f, "{}", message),
}
}
}
diff --git a/examples/websocket/src/echo/server.rs b/examples/websocket/src/echo/server.rs
index 7702d417..fef89a12 100644
--- a/examples/websocket/src/echo/server.rs
+++ b/examples/websocket/src/echo/server.rs
@@ -27,9 +27,9 @@ use warp::Filter;
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
pub async fn run() {
- let routes = warp::path::end().and(warp::ws()).map(|ws: warp::ws::Ws| {
- ws.on_upgrade(move |socket| user_connected(socket))
- });
+ let routes = warp::path::end()
+ .and(warp::ws())
+ .map(|ws: warp::ws::Ws| ws.on_upgrade(user_connected));
warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
}
@@ -40,7 +40,7 @@ async fn user_connected(ws: WebSocket) {
tokio::task::spawn(async move {
while let Some(message) = rx.next().await {
- let _ = user_ws_tx.send(message).await.unwrap_or_else(|e| {
+ user_ws_tx.send(message).await.unwrap_or_else(|e| {
eprintln!("websocket send error: {}", e);
});
}
diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs
index c03a9f3a..3902e04c 100644
--- a/examples/websocket/src/main.rs
+++ b/examples/websocket/src/main.rs
@@ -1,13 +1,12 @@
mod echo;
use iced::alignment::{self, Alignment};
-use iced::button::{self, Button};
use iced::executor;
-use iced::scrollable::{self, Scrollable};
-use iced::text_input::{self, TextInput};
+use iced::widget::{
+ button, column, container, row, scrollable, text, text_input, Column,
+};
use iced::{
- Application, Color, Column, Command, Container, Element, Length, Row,
- Settings, Subscription, Text,
+ Application, Color, Command, Element, Length, Settings, Subscription, Theme,
};
pub fn main() -> iced::Result {
@@ -17,10 +16,7 @@ pub fn main() -> iced::Result {
#[derive(Default)]
struct WebSocket {
messages: Vec<echo::Message>,
- message_log: scrollable::State,
new_message: String,
- new_message_state: text_input::State,
- new_message_button: button::State,
state: State,
}
@@ -34,6 +30,7 @@ enum Message {
impl Application for WebSocket {
type Message = Message;
+ type Theme = Theme;
type Flags = ();
type Executor = executor::Default;
@@ -52,46 +49,53 @@ impl Application for WebSocket {
match message {
Message::NewMessageChanged(new_message) => {
self.new_message = new_message;
+
+ Command::none()
}
Message::Send(message) => match &mut self.state {
State::Connected(connection) => {
self.new_message.clear();
connection.send(message);
+
+ Command::none()
}
- State::Disconnected => {}
+ State::Disconnected => Command::none(),
},
Message::Echo(event) => match event {
echo::Event::Connected(connection) => {
self.state = State::Connected(connection);
self.messages.push(echo::Message::connected());
+
+ Command::none()
}
echo::Event::Disconnected => {
self.state = State::Disconnected;
self.messages.push(echo::Message::disconnected());
+
+ Command::none()
}
echo::Event::MessageReceived(message) => {
self.messages.push(message);
- self.message_log.snap_to(1.0);
+
+ scrollable::snap_to(MESSAGE_LOG.clone(), 1.0)
}
},
- Message::Server => {}
+ Message::Server => Command::none(),
}
-
- Command::none()
}
fn subscription(&self) -> Subscription<Message> {
echo::connect().map(Message::Echo)
}
- fn view(&mut self) -> Element<Message> {
- let message_log = if self.messages.is_empty() {
- Container::new(
- Text::new("Your messages will appear here...")
- .color(Color::from_rgb8(0x88, 0x88, 0x88)),
+ fn view(&self) -> Element<Message> {
+ let message_log: Element<_> = if self.messages.is_empty() {
+ container(
+ text("Your messages will appear here...")
+ .style(Color::from_rgb8(0x88, 0x88, 0x88)),
)
.width(Length::Fill)
.height(Length::Fill)
@@ -99,31 +103,33 @@ impl Application for WebSocket {
.center_y()
.into()
} else {
- self.messages
- .iter()
- .cloned()
- .fold(
- Scrollable::new(&mut self.message_log),
- |scrollable, message| scrollable.push(Text::new(message)),
+ scrollable(
+ Column::with_children(
+ self.messages
+ .iter()
+ .cloned()
+ .map(text)
+ .map(Element::from)
+ .collect(),
)
.width(Length::Fill)
- .height(Length::Fill)
- .spacing(10)
- .into()
+ .spacing(10),
+ )
+ .id(MESSAGE_LOG.clone())
+ .height(Length::Fill)
+ .into()
};
let new_message_input = {
- let mut input = TextInput::new(
- &mut self.new_message_state,
+ let mut input = text_input(
"Type a message...",
&self.new_message,
Message::NewMessageChanged,
)
.padding(10);
- let mut button = Button::new(
- &mut self.new_message_button,
- Text::new("Send")
+ let mut button = button(
+ text("Send")
.height(Length::Fill)
.vertical_alignment(alignment::Vertical::Center),
)
@@ -136,12 +142,10 @@ impl Application for WebSocket {
}
}
- Row::with_children(vec![input.into(), button.into()])
- .spacing(10)
- .align_items(Alignment::Fill)
+ row![input, button].spacing(10).align_items(Alignment::Fill)
};
- Column::with_children(vec![message_log, new_message_input.into()])
+ column![message_log, new_message_input]
.width(Length::Fill)
.height(Length::Fill)
.padding(20)
@@ -160,3 +164,7 @@ impl Default for State {
Self::Disconnected
}
}
+
+lazy_static::lazy_static! {
+ static ref MESSAGE_LOG: scrollable::Id = scrollable::Id::unique();
+}