summaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
authorLibravatar Bingus <shankern@protonmail.com>2023-07-12 12:23:18 -0700
committerLibravatar Bingus <shankern@protonmail.com>2023-07-12 12:23:18 -0700
commit633f405f3f78bc7f82d2b2061491b0e011137451 (patch)
tree5ebfc1f45d216a5c14a90492563599e6969eab4d /examples
parent41836dd80d0534608e7aedfbf2319c540a23de1a (diff)
parent21bd51426d900e271206f314e0c915dd41065521 (diff)
downloadiced-633f405f3f78bc7f82d2b2061491b0e011137451.tar.gz
iced-633f405f3f78bc7f82d2b2061491b0e011137451.tar.bz2
iced-633f405f3f78bc7f82d2b2061491b0e011137451.zip
Merge remote-tracking branch 'origin/master' into feat/multi-window-support
# Conflicts: # Cargo.toml # core/src/window/icon.rs # core/src/window/id.rs # core/src/window/position.rs # core/src/window/settings.rs # examples/integration/src/main.rs # examples/integration_opengl/src/main.rs # glutin/src/application.rs # native/src/subscription.rs # native/src/window.rs # runtime/src/window/action.rs # src/lib.rs # src/window.rs # winit/Cargo.toml # winit/src/application.rs # winit/src/icon.rs # winit/src/settings.rs # winit/src/window.rs
Diffstat (limited to 'examples')
-rw-r--r--examples/README.md3
-rw-r--r--examples/arc/src/main.rs12
-rw-r--r--examples/bezier_tool/src/main.rs39
-rw-r--r--examples/checkbox/fonts/icons.ttfbin1272 -> 1784 bytes
-rw-r--r--examples/checkbox/src/main.rs32
-rw-r--r--examples/clock/src/main.rs16
-rw-r--r--examples/color_palette/Cargo.toml2
-rw-r--r--examples/color_palette/src/main.rs63
-rw-r--r--examples/component/Cargo.toml4
-rw-r--r--examples/component/src/main.rs24
-rw-r--r--examples/custom_quad/Cargo.toml3
-rw-r--r--examples/custom_quad/src/main.rs11
-rw-r--r--examples/custom_widget/Cargo.toml3
-rw-r--r--examples/custom_widget/src/main.rs11
-rw-r--r--examples/download_progress/Cargo.toml2
-rw-r--r--examples/download_progress/src/download.rs19
-rw-r--r--examples/events/Cargo.toml1
-rw-r--r--examples/events/src/main.rs9
-rw-r--r--examples/game_of_life/src/main.rs95
-rw-r--r--examples/geometry/Cargo.toml4
-rw-r--r--examples/geometry/src/main.rs121
-rw-r--r--examples/integration/.gitignore (renamed from examples/integration_wgpu/.gitignore)0
-rw-r--r--examples/integration/Cargo.toml (renamed from examples/integration_wgpu/Cargo.toml)8
-rw-r--r--examples/integration/README.md (renamed from examples/integration_wgpu/README.md)0
-rw-r--r--examples/integration/index.html (renamed from examples/integration_wgpu/index.html)4
-rw-r--r--examples/integration/src/controls.rs (renamed from examples/integration_wgpu/src/controls.rs)19
-rw-r--r--examples/integration/src/main.rs (renamed from examples/integration_wgpu/src/main.rs)182
-rw-r--r--examples/integration/src/scene.rs (renamed from examples/integration_wgpu/src/scene.rs)2
-rw-r--r--examples/integration/src/shader/frag.wgsl (renamed from examples/integration_wgpu/src/shader/frag.wgsl)0
-rw-r--r--examples/integration/src/shader/vert.wgsl (renamed from examples/integration_wgpu/src/shader/vert.wgsl)0
-rw-r--r--examples/integration_opengl/Cargo.toml12
-rw-r--r--examples/integration_opengl/README.md16
-rw-r--r--examples/integration_opengl/src/controls.rs101
-rw-r--r--examples/integration_opengl/src/main.rs188
-rw-r--r--examples/integration_opengl/src/scene.rs102
-rw-r--r--examples/lazy/Cargo.toml3
-rw-r--r--examples/lazy/src/main.rs12
-rw-r--r--examples/loading_spinners/Cargo.toml11
-rw-r--r--examples/loading_spinners/README.md14
-rw-r--r--examples/loading_spinners/src/circular.rs421
-rw-r--r--examples/loading_spinners/src/easing.rs133
-rw-r--r--examples/loading_spinners/src/linear.rs326
-rw-r--r--examples/loading_spinners/src/main.rs118
-rw-r--r--examples/modal/Cargo.toml3
-rw-r--r--examples/modal/src/main.rs152
-rw-r--r--examples/modern_art/Cargo.toml11
-rw-r--r--examples/modern_art/src/main.rs142
-rw-r--r--examples/multitouch/src/main.rs14
-rw-r--r--examples/pane_grid/Cargo.toml4
-rw-r--r--examples/pane_grid/src/main.rs13
-rw-r--r--examples/pick_list/src/main.rs9
-rw-r--r--examples/qr_code/src/main.rs12
-rw-r--r--examples/screenshot/Cargo.toml11
-rw-r--r--examples/screenshot/src/main.rs320
-rw-r--r--examples/scrollable/src/main.rs149
-rw-r--r--examples/sierpinski_triangle/src/main.rs24
-rw-r--r--examples/solar_system/Cargo.toml1
-rw-r--r--examples/solar_system/src/main.rs50
-rw-r--r--examples/styling/src/main.rs15
-rw-r--r--examples/toast/Cargo.toml3
-rw-r--r--examples/toast/src/main.rs81
-rw-r--r--examples/todos/fonts/icons.ttfbin5596 -> 5732 bytes
-rw-r--r--examples/todos/src/main.rs76
-rw-r--r--examples/tour/Cargo.toml2
-rw-r--r--examples/tour/src/main.rs91
-rw-r--r--examples/url_handler/Cargo.toml1
-rw-r--r--examples/url_handler/src/main.rs10
-rw-r--r--examples/websocket/Cargo.toml2
-rw-r--r--examples/websocket/src/echo.rs104
-rw-r--r--examples/websocket/src/echo/server.rs2
-rw-r--r--examples/websocket/src/main.rs9
71 files changed, 2194 insertions, 1263 deletions
diff --git a/examples/README.md b/examples/README.md
index 74cf145b..111e8910 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -93,8 +93,7 @@ A bunch of simpler examples exist:
- [`download_progress`](download_progress), a basic application that asynchronously downloads a dummy file of 100 MB and tracks the download progress.
- [`events`](events), a log of native events displayed using a conditional `Subscription`.
- [`geometry`](geometry), a custom widget showcasing how to draw geometry with the `Mesh2D` primitive in [`iced_wgpu`](../wgpu).
-- [`integration_opengl`](integration_opengl), a demonstration of how to integrate Iced in an existing OpenGL application.
-- [`integration_wgpu`](integration_wgpu), a demonstration of how to integrate Iced in an existing [`wgpu`] application.
+- [`integration`](integration), a demonstration of how to integrate Iced in an existing [`wgpu`] application.
- [`pane_grid`](pane_grid), a grid of panes that can be split, resized, and reorganized.
- [`pick_list`](pick_list), a dropdown list of selectable options.
- [`pokedex`](pokedex), an application that displays a random Pokédex entry (sprite included!) by using the [PokéAPI].
diff --git a/examples/arc/src/main.rs b/examples/arc/src/main.rs
index 7b6ea0e1..df565859 100644
--- a/examples/arc/src/main.rs
+++ b/examples/arc/src/main.rs
@@ -1,12 +1,13 @@
use std::{f32::consts::PI, time::Instant};
use iced::executor;
+use iced::mouse;
use iced::widget::canvas::{
- self, stroke, Cache, Canvas, Cursor, Geometry, Path, Stroke,
+ self, stroke, Cache, Canvas, Geometry, Path, Stroke,
};
use iced::{
- Application, Command, Element, Length, Point, Rectangle, Settings,
- Subscription, Theme,
+ Application, Command, Element, Length, Point, Rectangle, Renderer,
+ Settings, Subscription, Theme,
};
pub fn main() -> iced::Result {
@@ -75,11 +76,12 @@ impl<Message> canvas::Program<Message> for Arc {
fn draw(
&self,
_state: &Self::State,
+ renderer: &Renderer,
theme: &Theme,
bounds: Rectangle,
- _cursor: Cursor,
+ _cursor: mouse::Cursor,
) -> Vec<Geometry> {
- let geometry = self.cache.draw(bounds.size(), |frame| {
+ let geometry = self.cache.draw(renderer, bounds.size(), |frame| {
let palette = theme.palette();
let center = frame.center();
diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs
index 7c3916d4..310be28f 100644
--- a/examples/bezier_tool/src/main.rs
+++ b/examples/bezier_tool/src/main.rs
@@ -61,10 +61,8 @@ impl Sandbox for Example {
mod bezier {
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};
+ use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path, Stroke};
+ use iced::{Element, Length, Point, Rectangle, Renderer, Theme};
#[derive(Default)]
pub struct State {
@@ -100,10 +98,10 @@ mod bezier {
state: &mut Self::State,
event: Event,
bounds: Rectangle,
- cursor: Cursor,
+ cursor: mouse::Cursor,
) -> (event::Status, Option<Curve>) {
let cursor_position =
- if let Some(position) = cursor.position_in(&bounds) {
+ if let Some(position) = cursor.position_in(bounds) {
position
} else {
return (event::Status::Ignored, None);
@@ -152,22 +150,26 @@ mod bezier {
fn draw(
&self,
state: &Self::State,
+ renderer: &Renderer,
_theme: &Theme,
bounds: Rectangle,
- cursor: Cursor,
+ cursor: mouse::Cursor,
) -> Vec<Geometry> {
- let content =
- self.state.cache.draw(bounds.size(), |frame: &mut Frame| {
+ let content = self.state.cache.draw(
+ renderer,
+ bounds.size(),
+ |frame: &mut Frame| {
Curve::draw_all(self.curves, frame);
frame.stroke(
&Path::rectangle(Point::ORIGIN, frame.size()),
Stroke::default().with_width(2.0),
);
- });
+ },
+ );
if let Some(pending) = state {
- let pending_curve = pending.draw(bounds, cursor);
+ let pending_curve = pending.draw(renderer, bounds, cursor);
vec![content, pending_curve]
} else {
@@ -179,9 +181,9 @@ mod bezier {
&self,
_state: &Self::State,
bounds: Rectangle,
- cursor: Cursor,
+ cursor: mouse::Cursor,
) -> mouse::Interaction {
- if cursor.is_over(&bounds) {
+ if cursor.is_over(bounds) {
mouse::Interaction::Crosshair
} else {
mouse::Interaction::default()
@@ -216,10 +218,15 @@ mod bezier {
}
impl Pending {
- fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Geometry {
- let mut frame = Frame::new(bounds.size());
+ fn draw(
+ &self,
+ renderer: &Renderer,
+ bounds: Rectangle,
+ cursor: mouse::Cursor,
+ ) -> Geometry {
+ let mut frame = Frame::new(renderer, bounds.size());
- if let Some(cursor_position) = cursor.position_in(&bounds) {
+ if let Some(cursor_position) = cursor.position_in(bounds) {
match *self {
Pending::One { from } => {
let line = Path::line(from, cursor_position);
diff --git a/examples/checkbox/fonts/icons.ttf b/examples/checkbox/fonts/icons.ttf
index a2046844..82f28481 100644
--- a/examples/checkbox/fonts/icons.ttf
+++ b/examples/checkbox/fonts/icons.ttf
Binary files differ
diff --git a/examples/checkbox/src/main.rs b/examples/checkbox/src/main.rs
index 09950bb8..ef1a054d 100644
--- a/examples/checkbox/src/main.rs
+++ b/examples/checkbox/src/main.rs
@@ -1,10 +1,9 @@
-use iced::widget::{checkbox, column, container};
-use iced::{Element, Font, Length, Sandbox, Settings};
+use iced::executor;
+use iced::font::{self, Font};
+use iced::widget::{checkbox, column, container, text};
+use iced::{Application, Command, Element, Length, Settings, Theme};
-const ICON_FONT: Font = Font::External {
- name: "Icons",
- bytes: include_bytes!("../fonts/icons.ttf"),
-};
+const ICON_FONT: Font = Font::with_name("icons");
pub fn main() -> iced::Result {
Example::run(Settings::default())
@@ -20,24 +19,35 @@ struct Example {
enum Message {
DefaultChecked(bool),
CustomChecked(bool),
+ FontLoaded(Result<(), font::Error>),
}
-impl Sandbox for Example {
+impl Application for Example {
type Message = Message;
+ type Flags = ();
+ type Executor = executor::Default;
+ type Theme = Theme;
- fn new() -> Self {
- Default::default()
+ fn new(_flags: Self::Flags) -> (Self, Command<Message>) {
+ (
+ Self::default(),
+ font::load(include_bytes!("../fonts/icons.ttf").as_slice())
+ .map(Message::FontLoaded),
+ )
}
fn title(&self) -> String {
String::from("Checkbox - Iced")
}
- fn update(&mut self, message: Message) {
+ fn update(&mut self, message: Message) -> Command<Message> {
match message {
Message::DefaultChecked(value) => self.default_checkbox = value,
Message::CustomChecked(value) => self.custom_checkbox = value,
+ Message::FontLoaded(_) => (),
}
+
+ Command::none()
}
fn view(&self) -> Element<Message> {
@@ -49,6 +59,8 @@ impl Sandbox for Example {
font: ICON_FONT,
code_point: '\u{e901}',
size: None,
+ line_height: text::LineHeight::Relative(1.0),
+ shaping: text::Shaping::Basic,
});
let content = column![default_checkbox, custom_checkbox].spacing(22);
diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs
index a389c54f..fae77bc0 100644
--- a/examples/clock/src/main.rs
+++ b/examples/clock/src/main.rs
@@ -1,11 +1,10 @@
use iced::executor;
-use iced::widget::canvas::{
- stroke, Cache, Cursor, Geometry, LineCap, Path, Stroke,
-};
+use iced::mouse;
+use iced::widget::canvas::{stroke, Cache, Geometry, LineCap, Path, Stroke};
use iced::widget::{canvas, container};
use iced::{
- Application, Color, Command, Element, Length, Point, Rectangle, Settings,
- Subscription, Theme, Vector,
+ Application, Color, Command, Element, Length, Point, Rectangle, Renderer,
+ Settings, Subscription, Theme, Vector,
};
pub fn main() -> iced::Result {
@@ -83,17 +82,18 @@ impl Application for Clock {
}
}
-impl<Message> canvas::Program<Message> for Clock {
+impl<Message> canvas::Program<Message, Renderer> for Clock {
type State = ();
fn draw(
&self,
_state: &Self::State,
+ renderer: &Renderer,
_theme: &Theme,
bounds: Rectangle,
- _cursor: Cursor,
+ _cursor: mouse::Cursor,
) -> Vec<Geometry> {
- let clock = self.clock.draw(bounds.size(), |frame| {
+ let clock = self.clock.draw(renderer, 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 8fd37202..3be732bb 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.6.0"
+palette = "0.7.0"
diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs
index a2df36c2..736a9d53 100644
--- a/examples/color_palette/src/main.rs
+++ b/examples/color_palette/src/main.rs
@@ -1,10 +1,14 @@
-use iced::widget::canvas::{self, Canvas, Cursor, Frame, Geometry, Path};
+use iced::alignment::{self, Alignment};
+use iced::mouse;
+use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path};
use iced::widget::{column, row, text, Slider};
use iced::{
- alignment, Alignment, Color, Element, Length, Point, Rectangle, Sandbox,
- Settings, Size, Vector,
+ Color, Element, Length, Point, Rectangle, Renderer, Sandbox, Settings,
+ Size, Vector,
+};
+use palette::{
+ self, convert::FromColor, rgb::Rgb, Darken, Hsl, Lighten, ShiftHue,
};
-use palette::{self, convert::FromColor, Hsl, Srgb};
use std::marker::PhantomData;
use std::ops::RangeInclusive;
@@ -49,12 +53,12 @@ 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_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),
+ Message::RgbColorChanged(rgb) => Rgb::from(rgb),
+ Message::HslColorChanged(hsl) => Rgb::from_color(hsl),
+ Message::HsvColorChanged(hsv) => Rgb::from_color(hsv),
+ Message::HwbColorChanged(hwb) => Rgb::from_color(hwb),
+ Message::LabColorChanged(lab) => Rgb::from_color(lab),
+ Message::LchColorChanged(lch) => Rgb::from_color(lch),
};
self.theme = Theme::new(srgb);
@@ -63,7 +67,7 @@ impl Sandbox for ColorPalette {
fn view(&self) -> Element<Message> {
let base = self.theme.base;
- let srgb = palette::Srgb::from(base);
+ let srgb = Rgb::from(base);
let hsl = palette::Hsl::from_color(srgb);
let hsv = palette::Hsv::from_color(srgb);
let hwb = palette::Hwb::from_color(srgb);
@@ -95,12 +99,10 @@ struct Theme {
impl Theme {
pub fn new(base: impl Into<Color>) -> Theme {
- use palette::{Hue, Shade};
-
let base = base.into();
// Convert to HSL color for manipulation
- let hsl = Hsl::from_color(Srgb::from(base));
+ let hsl = Hsl::from_color(Rgb::from(base));
let lower = [
hsl.shift_hue(-135.0).lighten(0.075),
@@ -119,12 +121,12 @@ impl Theme {
Theme {
lower: lower
.iter()
- .map(|&color| Srgb::from_color(color).into())
+ .map(|&color| Rgb::from_color(color).into())
.collect(),
base,
higher: higher
.iter()
- .map(|&color| Srgb::from_color(color).into())
+ .map(|&color| Rgb::from_color(color).into())
.collect(),
canvas_cache: canvas::Cache::default(),
}
@@ -209,14 +211,14 @@ impl Theme {
text.vertical_alignment = alignment::Vertical::Bottom;
- let hsl = Hsl::from_color(Srgb::from(self.base));
+ let hsl = Hsl::from_color(Rgb::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_color(graded).into();
+ let color: Color = Rgb::from_color(graded).into();
let anchor = Point {
x: (i as f32) * box_size.width,
@@ -243,11 +245,12 @@ impl<Message> canvas::Program<Message> for Theme {
fn draw(
&self,
_state: &Self::State,
+ renderer: &Renderer,
_theme: &iced::Theme,
bounds: Rectangle,
- _cursor: Cursor,
+ _cursor: mouse::Cursor,
) -> Vec<Geometry> {
- let theme = self.canvas_cache.draw(bounds.size(), |frame| {
+ let theme = self.canvas_cache.draw(renderer, bounds.size(), |frame| {
self.draw(frame);
});
@@ -351,7 +354,7 @@ impl ColorSpace for palette::Hsl {
fn components(&self) -> [f32; 3] {
[
- self.hue.to_positive_degrees(),
+ self.hue.into_positive_degrees(),
self.saturation,
self.lightness,
]
@@ -360,7 +363,7 @@ impl ColorSpace for palette::Hsl {
fn to_string(&self) -> String {
format!(
"hsl({:.1}, {:.1}%, {:.1}%)",
- self.hue.to_positive_degrees(),
+ self.hue.into_positive_degrees(),
100.0 * self.saturation,
100.0 * self.lightness
)
@@ -377,13 +380,17 @@ impl ColorSpace for palette::Hsv {
}
fn components(&self) -> [f32; 3] {
- [self.hue.to_positive_degrees(), self.saturation, self.value]
+ [
+ self.hue.into_positive_degrees(),
+ self.saturation,
+ self.value,
+ ]
}
fn to_string(&self) -> String {
format!(
"hsv({:.1}, {:.1}%, {:.1}%)",
- self.hue.to_positive_degrees(),
+ self.hue.into_positive_degrees(),
100.0 * self.saturation,
100.0 * self.value
)
@@ -405,7 +412,7 @@ impl ColorSpace for palette::Hwb {
fn components(&self) -> [f32; 3] {
[
- self.hue.to_positive_degrees(),
+ self.hue.into_positive_degrees(),
self.whiteness,
self.blackness,
]
@@ -414,7 +421,7 @@ impl ColorSpace for palette::Hwb {
fn to_string(&self) -> String {
format!(
"hwb({:.1}, {:.1}%, {:.1}%)",
- self.hue.to_positive_degrees(),
+ self.hue.into_positive_degrees(),
100.0 * self.whiteness,
100.0 * self.blackness
)
@@ -449,7 +456,7 @@ impl ColorSpace for palette::Lch {
}
fn components(&self) -> [f32; 3] {
- [self.l, self.chroma, self.hue.to_positive_degrees()]
+ [self.l, self.chroma, self.hue.into_positive_degrees()]
}
fn to_string(&self) -> String {
@@ -457,7 +464,7 @@ impl ColorSpace for palette::Lch {
"Lch({:.1}, {:.1}, {:.1})",
self.l,
self.chroma,
- self.hue.to_positive_degrees()
+ self.hue.into_positive_degrees()
)
}
}
diff --git a/examples/component/Cargo.toml b/examples/component/Cargo.toml
index dd435201..9db1e6b4 100644
--- a/examples/component/Cargo.toml
+++ b/examples/component/Cargo.toml
@@ -6,6 +6,4 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["debug"] }
-iced_native = { path = "../../native" }
-iced_lazy = { path = "../../lazy" }
+iced = { path = "../..", features = ["debug", "lazy"] }
diff --git a/examples/component/src/main.rs b/examples/component/src/main.rs
index bbf549e7..010321a9 100644
--- a/examples/component/src/main.rs
+++ b/examples/component/src/main.rs
@@ -47,9 +47,8 @@ impl Sandbox for Component {
mod numeric_input {
use iced::alignment::{self, Alignment};
- use iced::widget::{self, button, row, text, text_input};
- use iced::{Element, Length};
- use iced_lazy::{self, Component};
+ use iced::widget::{button, component, row, text, text_input, Component};
+ use iced::{Element, Length, Renderer};
pub struct NumericInput<Message> {
value: Option<u32>,
@@ -82,13 +81,7 @@ mod numeric_input {
}
}
- impl<Message, Renderer> Component<Message, Renderer> for NumericInput<Message>
- where
- Renderer: iced_native::text::Renderer + 'static,
- Renderer::Theme: widget::button::StyleSheet
- + widget::text_input::StyleSheet
- + widget::text::StyleSheet,
- {
+ impl<Message> Component<Message, Renderer> for NumericInput<Message> {
type State = ();
type Event = Event;
@@ -141,8 +134,8 @@ mod numeric_input {
.map(u32::to_string)
.as_deref()
.unwrap_or(""),
- Event::InputChanged,
)
+ .on_input(Event::InputChanged)
.padding(10),
button("+", Event::IncrementPressed),
]
@@ -152,17 +145,12 @@ mod numeric_input {
}
}
- impl<'a, Message, Renderer> From<NumericInput<Message>>
- for Element<'a, Message, Renderer>
+ impl<'a, Message> From<NumericInput<Message>> for Element<'a, Message, Renderer>
where
Message: 'a,
- Renderer: 'static + iced_native::text::Renderer,
- Renderer::Theme: widget::button::StyleSheet
- + widget::text_input::StyleSheet
- + widget::text::StyleSheet,
{
fn from(numeric_input: NumericInput<Message>) -> Self {
- iced_lazy::component(numeric_input)
+ component(numeric_input)
}
}
}
diff --git a/examples/custom_quad/Cargo.toml b/examples/custom_quad/Cargo.toml
index 39154786..f097c2dd 100644
--- a/examples/custom_quad/Cargo.toml
+++ b/examples/custom_quad/Cargo.toml
@@ -6,5 +6,4 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../.." }
-iced_native = { path = "../../native" }
+iced = { path = "../..", features = ["advanced"] }
diff --git a/examples/custom_quad/src/main.rs b/examples/custom_quad/src/main.rs
index 6509887c..4b300116 100644
--- a/examples/custom_quad/src/main.rs
+++ b/examples/custom_quad/src/main.rs
@@ -1,9 +1,10 @@
//! This example showcases a drawing a quad.
mod quad {
- use iced_native::layout::{self, Layout};
- use iced_native::renderer;
- use iced_native::widget::{self, Widget};
- use iced_native::{Color, Element, Length, Point, Rectangle, Size};
+ use iced::advanced::layout::{self, Layout};
+ use iced::advanced::renderer;
+ use iced::advanced::widget::{self, Widget};
+ use iced::mouse;
+ use iced::{Color, Element, Length, Rectangle, Size};
pub struct CustomQuad {
size: f32,
@@ -48,7 +49,7 @@ mod quad {
_theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- _cursor_position: Point,
+ _cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
renderer.fill_quad(
diff --git a/examples/custom_widget/Cargo.toml b/examples/custom_widget/Cargo.toml
index 067aab4a..dda0efe8 100644
--- a/examples/custom_widget/Cargo.toml
+++ b/examples/custom_widget/Cargo.toml
@@ -6,5 +6,4 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../.." }
-iced_native = { path = "../../native" }
+iced = { path = "../..", features = ["advanced"] }
diff --git a/examples/custom_widget/src/main.rs b/examples/custom_widget/src/main.rs
index f6bb3b1e..713bc62d 100644
--- a/examples/custom_widget/src/main.rs
+++ b/examples/custom_widget/src/main.rs
@@ -9,10 +9,11 @@ mod circle {
// Of course, you can choose to make the implementation renderer-agnostic,
// if you wish to, by creating your own `Renderer` trait, which could be
// implemented by `iced_wgpu` and other renderers.
- use iced_native::layout::{self, Layout};
- use iced_native::renderer;
- use iced_native::widget::{self, Widget};
- use iced_native::{Color, Element, Length, Point, Rectangle, Size};
+ use iced::advanced::layout::{self, Layout};
+ use iced::advanced::renderer;
+ use iced::advanced::widget::{self, Widget};
+ use iced::mouse;
+ use iced::{Color, Element, Length, Rectangle, Size};
pub struct Circle {
radius: f32,
@@ -55,7 +56,7 @@ mod circle {
_theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- _cursor_position: Point,
+ _cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
renderer.fill_quad(
diff --git a/examples/download_progress/Cargo.toml b/examples/download_progress/Cargo.toml
index f38679ea..212832f4 100644
--- a/examples/download_progress/Cargo.toml
+++ b/examples/download_progress/Cargo.toml
@@ -7,8 +7,6 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["tokio"] }
-iced_native = { path = "../../native" }
-iced_futures = { path = "../../futures" }
[dependencies.reqwest]
version = "0.11"
diff --git a/examples/download_progress/src/download.rs b/examples/download_progress/src/download.rs
index 39dd843f..3b11cb76 100644
--- a/examples/download_progress/src/download.rs
+++ b/examples/download_progress/src/download.rs
@@ -1,4 +1,4 @@
-use iced_native::subscription;
+use iced::subscription;
use std::hash::Hash;
@@ -18,10 +18,7 @@ pub struct Download<I> {
url: String,
}
-async fn download<I: Copy>(
- id: I,
- state: State,
-) -> (Option<(I, Progress)>, State) {
+async fn download<I: Copy>(id: I, state: State) -> ((I, Progress), State) {
match state {
State::Ready(url) => {
let response = reqwest::get(&url).await;
@@ -30,7 +27,7 @@ async fn download<I: Copy>(
Ok(response) => {
if let Some(total) = response.content_length() {
(
- Some((id, Progress::Started)),
+ (id, Progress::Started),
State::Downloading {
response,
total,
@@ -38,10 +35,10 @@ async fn download<I: Copy>(
},
)
} else {
- (Some((id, Progress::Errored)), State::Finished)
+ ((id, Progress::Errored), State::Finished)
}
}
- Err(_) => (Some((id, Progress::Errored)), State::Finished),
+ Err(_) => ((id, Progress::Errored), State::Finished),
}
}
State::Downloading {
@@ -55,7 +52,7 @@ async fn download<I: Copy>(
let percentage = (downloaded as f32 / total as f32) * 100.0;
(
- Some((id, Progress::Advanced(percentage))),
+ (id, Progress::Advanced(percentage)),
State::Downloading {
response,
total,
@@ -63,8 +60,8 @@ async fn download<I: Copy>(
},
)
}
- Ok(None) => (Some((id, Progress::Finished)), State::Finished),
- Err(_) => (Some((id, Progress::Errored)), State::Finished),
+ Ok(None) => ((id, Progress::Finished), State::Finished),
+ Err(_) => ((id, Progress::Errored), State::Finished),
},
State::Finished => {
// We do not let the stream die, as it would start a
diff --git a/examples/events/Cargo.toml b/examples/events/Cargo.toml
index 8c56e471..15ffc0af 100644
--- a/examples/events/Cargo.toml
+++ b/examples/events/Cargo.toml
@@ -7,4 +7,3 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["debug"] }
-iced_native = { path = "../../native" }
diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs
index b57010c7..70659f52 100644
--- a/examples/events/src/main.rs
+++ b/examples/events/src/main.rs
@@ -1,12 +1,13 @@
use iced::alignment;
use iced::executor;
+use iced::subscription;
use iced::widget::{button, checkbox, container, text, Column};
use iced::window;
+use iced::Event;
use iced::{
Alignment, Application, Command, Element, Length, Settings, Subscription,
Theme,
};
-use iced_native::Event;
pub fn main() -> iced::Result {
Events::run(Settings {
@@ -17,13 +18,13 @@ pub fn main() -> iced::Result {
#[derive(Debug, Default)]
struct Events {
- last: Vec<iced_native::Event>,
+ last: Vec<Event>,
enabled: bool,
}
#[derive(Debug, Clone)]
enum Message {
- EventOccurred(iced_native::Event),
+ EventOccurred(Event),
Toggled(bool),
Exit(window::Id),
}
@@ -70,7 +71,7 @@ impl Application for Events {
}
fn subscription(&self) -> Subscription<Message> {
- iced_native::subscription::events().map(Message::EventOccurred)
+ subscription::events().map(Message::EventOccurred)
}
fn view(&self) -> Element<Message> {
diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs
index ed911160..e951d734 100644
--- a/examples/game_of_life/src/main.rs
+++ b/examples/game_of_life/src/main.rs
@@ -145,7 +145,7 @@ impl Application for GameOfLife {
self.grid
.view()
.map(move |message| Message::Grid(message, version)),
- controls
+ controls,
];
container(content)
@@ -204,15 +204,14 @@ fn view_controls<'a>(
mod grid {
use crate::Preset;
+ use iced::alignment;
+ use iced::mouse;
use iced::touch;
use iced::widget::canvas;
use iced::widget::canvas::event::{self, Event};
- use iced::widget::canvas::{
- Cache, Canvas, Cursor, Frame, Geometry, Path, Text,
- };
+ use iced::widget::canvas::{Cache, Canvas, Frame, Geometry, Path, Text};
use iced::{
- alignment, mouse, Color, Element, Length, Point, Rectangle, Size,
- Theme, Vector,
+ Color, Element, Length, Point, Rectangle, Renderer, Size, Theme, Vector,
};
use rustc_hash::{FxHashMap, FxHashSet};
use std::future::Future;
@@ -401,14 +400,14 @@ mod grid {
interaction: &mut Interaction,
event: Event,
bounds: Rectangle,
- cursor: Cursor,
+ cursor: mouse::Cursor,
) -> (event::Status, Option<Message>) {
if let Event::Mouse(mouse::Event::ButtonReleased(_)) = event {
*interaction = Interaction::None;
}
let cursor_position =
- if let Some(position) = cursor.position_in(&bounds) {
+ if let Some(position) = cursor.position_in(bounds) {
position
} else {
return (event::Status::Ignored, None);
@@ -536,13 +535,14 @@ mod grid {
fn draw(
&self,
_interaction: &Interaction,
+ renderer: &Renderer,
_theme: &Theme,
bounds: Rectangle,
- cursor: Cursor,
+ cursor: mouse::Cursor,
) -> Vec<Geometry> {
let center = Vector::new(bounds.width / 2.0, bounds.height / 2.0);
- let life = self.life_cache.draw(bounds.size(), |frame| {
+ let life = self.life_cache.draw(renderer, bounds.size(), |frame| {
let background = Path::rectangle(Point::ORIGIN, frame.size());
frame.fill(&background, Color::from_rgb8(0x40, 0x44, 0x4B));
@@ -565,12 +565,11 @@ mod grid {
});
let overlay = {
- let mut frame = Frame::new(bounds.size());
+ let mut frame = Frame::new(renderer, bounds.size());
- let hovered_cell =
- cursor.position_in(&bounds).map(|position| {
- Cell::at(self.project(position, frame.size()))
- });
+ let hovered_cell = cursor.position_in(bounds).map(|position| {
+ Cell::at(self.project(position, frame.size()))
+ });
if let Some(cell) = hovered_cell {
frame.with_save(|frame| {
@@ -626,38 +625,40 @@ mod grid {
if self.scaling < 0.2 || !self.show_lines {
vec![life, overlay]
} else {
- let grid = self.grid_cache.draw(bounds.size(), |frame| {
- frame.translate(center);
- frame.scale(self.scaling);
- frame.translate(self.translation);
- frame.scale(Cell::SIZE as f32);
-
- let region = self.visible_region(frame.size());
- let rows = region.rows();
- let columns = region.columns();
- let (total_rows, total_columns) =
- (rows.clone().count(), columns.clone().count());
- let width = 2.0 / Cell::SIZE as f32;
- let color = Color::from_rgb8(70, 74, 83);
-
- frame.translate(Vector::new(-width / 2.0, -width / 2.0));
+ let grid =
+ self.grid_cache.draw(renderer, bounds.size(), |frame| {
+ frame.translate(center);
+ frame.scale(self.scaling);
+ frame.translate(self.translation);
+ frame.scale(Cell::SIZE as f32);
- for row in region.rows() {
- frame.fill_rectangle(
- Point::new(*columns.start() as f32, row as f32),
- Size::new(total_columns as f32, width),
- color,
- );
- }
+ let region = self.visible_region(frame.size());
+ let rows = region.rows();
+ let columns = region.columns();
+ let (total_rows, total_columns) =
+ (rows.clone().count(), columns.clone().count());
+ let width = 2.0 / Cell::SIZE as f32;
+ let color = Color::from_rgb8(70, 74, 83);
+
+ frame
+ .translate(Vector::new(-width / 2.0, -width / 2.0));
+
+ for row in region.rows() {
+ frame.fill_rectangle(
+ Point::new(*columns.start() as f32, row as f32),
+ Size::new(total_columns as f32, width),
+ color,
+ );
+ }
- for column in region.columns() {
- frame.fill_rectangle(
- Point::new(column as f32, *rows.start() as f32),
- Size::new(width, total_rows as f32),
- color,
- );
- }
- });
+ for column in region.columns() {
+ frame.fill_rectangle(
+ Point::new(column as f32, *rows.start() as f32),
+ Size::new(width, total_rows as f32),
+ color,
+ );
+ }
+ });
vec![life, grid, overlay]
}
@@ -667,13 +668,13 @@ mod grid {
&self,
interaction: &Interaction,
bounds: Rectangle,
- cursor: Cursor,
+ cursor: mouse::Cursor,
) -> mouse::Interaction {
match interaction {
Interaction::Drawing => mouse::Interaction::Crosshair,
Interaction::Erasing => mouse::Interaction::Crosshair,
Interaction::Panning { .. } => mouse::Interaction::Grabbing,
- Interaction::None if cursor.is_over(&bounds) => {
+ Interaction::None if cursor.is_over(bounds) => {
mouse::Interaction::Crosshair
}
_ => mouse::Interaction::default(),
diff --git a/examples/geometry/Cargo.toml b/examples/geometry/Cargo.toml
index 22ede0e0..6068d651 100644
--- a/examples/geometry/Cargo.toml
+++ b/examples/geometry/Cargo.toml
@@ -6,6 +6,4 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../.." }
-iced_native = { path = "../../native" }
-iced_graphics = { path = "../../graphics" }
+iced = { path = "../..", features = ["advanced"] }
diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs
index 9bacce7f..3bc7f46b 100644
--- a/examples/geometry/src/main.rs
+++ b/examples/geometry/src/main.rs
@@ -1,24 +1,12 @@
//! This example showcases a simple native custom widget that renders using
//! arbitrary low-level geometry.
mod rainbow {
- // For now, to implement a custom native widget you will need to add
- // `iced_native` and `iced_wgpu` to your dependencies.
- //
- // Then, you simply need to define your widget type and implement the
- // `iced_native::Widget` trait with the `iced_wgpu::Renderer`.
- //
- // Of course, you can choose to make the implementation renderer-agnostic,
- // if you wish to, by creating your own `Renderer` trait, which could be
- // implemented by `iced_wgpu` and other renderers.
- use iced_graphics::renderer::{self, Renderer};
- use iced_graphics::triangle::ColoredVertex2D;
- use iced_graphics::{Backend, Primitive};
-
- use iced_native::layout;
- use iced_native::widget::{self, Widget};
- use iced_native::{
- Element, Layout, Length, Point, Rectangle, Size, Vector,
- };
+ use iced::advanced::graphics::color;
+ use iced::advanced::layout::{self, Layout};
+ use iced::advanced::renderer;
+ use iced::advanced::widget::{self, Widget};
+ use iced::mouse;
+ use iced::{Element, Length, Rectangle, Renderer, Size, Theme, Vector};
#[derive(Debug, Clone, Copy, Default)]
pub struct Rainbow;
@@ -27,10 +15,7 @@ mod rainbow {
Rainbow
}
- impl<Message, B, T> Widget<Message, Renderer<B, T>> for Rainbow
- where
- B: Backend,
- {
+ impl<Message> Widget<Message, Renderer> for Rainbow {
fn width(&self) -> Length {
Length::Fill
}
@@ -41,7 +26,7 @@ mod rainbow {
fn layout(
&self,
- _renderer: &Renderer<B, T>,
+ _renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let size = limits.width(Length::Fill).resolve(Size::ZERO);
@@ -52,17 +37,17 @@ mod rainbow {
fn draw(
&self,
_tree: &widget::Tree,
- renderer: &mut Renderer<B, T>,
- _theme: &T,
+ renderer: &mut Renderer,
+ _theme: &Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
- use iced_graphics::triangle::Mesh2D;
- use iced_native::Renderer as _;
+ use iced::advanced::graphics::mesh::{self, Mesh, SolidVertex2D};
+ use iced::advanced::Renderer as _;
- let b = layout.bounds();
+ let bounds = layout.bounds();
// R O Y G B I V
let color_r = [1.0, 0.0, 0.0, 1.0];
@@ -75,61 +60,61 @@ mod rainbow {
let color_v = [0.75, 0.0, 0.5, 1.0];
let posn_center = {
- if b.contains(cursor_position) {
- [cursor_position.x - b.x, cursor_position.y - b.y]
+ if let Some(cursor_position) = cursor.position_in(bounds) {
+ [cursor_position.x, cursor_position.y]
} else {
- [b.width / 2.0, b.height / 2.0]
+ [bounds.width / 2.0, bounds.height / 2.0]
}
};
let posn_tl = [0.0, 0.0];
- let posn_t = [b.width / 2.0, 0.0];
- let posn_tr = [b.width, 0.0];
- let posn_r = [b.width, b.height / 2.0];
- let posn_br = [b.width, b.height];
- let posn_b = [(b.width / 2.0), b.height];
- let posn_bl = [0.0, b.height];
- let posn_l = [0.0, b.height / 2.0];
-
- let mesh = Primitive::SolidMesh {
- size: b.size(),
- buffers: Mesh2D {
+ let posn_t = [bounds.width / 2.0, 0.0];
+ let posn_tr = [bounds.width, 0.0];
+ let posn_r = [bounds.width, bounds.height / 2.0];
+ let posn_br = [bounds.width, bounds.height];
+ let posn_b = [(bounds.width / 2.0), bounds.height];
+ let posn_bl = [0.0, bounds.height];
+ let posn_l = [0.0, bounds.height / 2.0];
+
+ let mesh = Mesh::Solid {
+ size: bounds.size(),
+ buffers: mesh::Indexed {
vertices: vec![
- ColoredVertex2D {
+ SolidVertex2D {
position: posn_center,
- color: [1.0, 1.0, 1.0, 1.0],
+ color: color::pack([1.0, 1.0, 1.0, 1.0]),
},
- ColoredVertex2D {
+ SolidVertex2D {
position: posn_tl,
- color: color_r,
+ color: color::pack(color_r),
},
- ColoredVertex2D {
+ SolidVertex2D {
position: posn_t,
- color: color_o,
+ color: color::pack(color_o),
},
- ColoredVertex2D {
+ SolidVertex2D {
position: posn_tr,
- color: color_y,
+ color: color::pack(color_y),
},
- ColoredVertex2D {
+ SolidVertex2D {
position: posn_r,
- color: color_g,
+ color: color::pack(color_g),
},
- ColoredVertex2D {
+ SolidVertex2D {
position: posn_br,
- color: color_gb,
+ color: color::pack(color_gb),
},
- ColoredVertex2D {
+ SolidVertex2D {
position: posn_b,
- color: color_b,
+ color: color::pack(color_b),
},
- ColoredVertex2D {
+ SolidVertex2D {
position: posn_bl,
- color: color_i,
+ color: color::pack(color_i),
},
- ColoredVertex2D {
+ SolidVertex2D {
position: posn_l,
- color: color_v,
+ color: color::pack(color_v),
},
],
indices: vec![
@@ -145,16 +130,16 @@ mod rainbow {
},
};
- renderer.with_translation(Vector::new(b.x, b.y), |renderer| {
- renderer.draw_primitive(mesh);
- });
+ renderer.with_translation(
+ Vector::new(bounds.x, bounds.y),
+ |renderer| {
+ renderer.draw_mesh(mesh);
+ },
+ );
}
}
- impl<'a, Message, B, T> From<Rainbow> for Element<'a, Message, Renderer<B, T>>
- where
- B: Backend,
- {
+ impl<'a, Message> From<Rainbow> for Element<'a, Message, Renderer> {
fn from(rainbow: Rainbow) -> Self {
Self::new(rainbow)
}
diff --git a/examples/integration_wgpu/.gitignore b/examples/integration/.gitignore
index e188dc28..e188dc28 100644
--- a/examples/integration_wgpu/.gitignore
+++ b/examples/integration/.gitignore
diff --git a/examples/integration_wgpu/Cargo.toml b/examples/integration/Cargo.toml
index eaa1df7e..22914742 100644
--- a/examples/integration_wgpu/Cargo.toml
+++ b/examples/integration/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "integration_wgpu"
+name = "integration"
version = "0.1.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
@@ -7,8 +7,10 @@ publish = false
[dependencies]
iced_winit = { path = "../../winit" }
-iced_wgpu = { path = "../../wgpu", features = ["webgl"] }
-env_logger = "0.8"
+iced_wgpu = { path = "../../wgpu" }
+iced_widget = { path = "../../widget" }
+iced_renderer = { path = "../../renderer", features = ["wgpu"] }
+env_logger = "0.10"
[target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1.7"
diff --git a/examples/integration_wgpu/README.md b/examples/integration/README.md
index ece9ba1e..ece9ba1e 100644
--- a/examples/integration_wgpu/README.md
+++ b/examples/integration/README.md
diff --git a/examples/integration_wgpu/index.html b/examples/integration/index.html
index 461e67a4..920bc4a0 100644
--- a/examples/integration_wgpu/index.html
+++ b/examples/integration/index.html
@@ -8,8 +8,8 @@
<h1>integration_wgpu</h1>
<canvas id="iced_canvas"></canvas>
<script type="module">
- import init from "./integration_wgpu.js";
- init('./integration_wgpu_bg.wasm');
+ import init from "./integration.js";
+ init('./integration_bg.wasm');
</script>
<style>
body {
diff --git a/examples/integration_wgpu/src/controls.rs b/examples/integration/src/controls.rs
index 533cb6e2..14e53ede 100644
--- a/examples/integration_wgpu/src/controls.rs
+++ b/examples/integration/src/controls.rs
@@ -1,6 +1,8 @@
use iced_wgpu::Renderer;
-use iced_winit::widget::{slider, text_input, Column, Row, Text};
-use iced_winit::{Alignment, Color, Command, Element, Length, Program};
+use iced_widget::{slider, text_input, Column, Row, Text};
+use iced_winit::core::{Alignment, Color, Element, Length};
+use iced_winit::runtime::{Command, Program};
+use iced_winit::style::Theme;
pub struct Controls {
background_color: Color,
@@ -27,7 +29,7 @@ impl Controls {
}
impl Program for Controls {
- type Renderer = Renderer;
+ type Renderer = Renderer<Theme>;
type Message = Message;
fn update(&mut self, message: Message) -> Command<Message> {
@@ -43,7 +45,7 @@ impl Program for Controls {
Command::none()
}
- fn view(&self) -> Element<Message, Renderer> {
+ fn view(&self) -> Element<Message, Renderer<Theme>> {
let background_color = self.background_color;
let text = &self.text;
@@ -100,11 +102,10 @@ impl Program for Controls {
.size(14)
.style(Color::WHITE),
)
- .push(text_input(
- "Placeholder",
- text,
- Message::TextChanged,
- )),
+ .push(
+ text_input("Placeholder", text)
+ .on_input(Message::TextChanged),
+ ),
),
)
.into()
diff --git a/examples/integration_wgpu/src/main.rs b/examples/integration/src/main.rs
index 1f42013b..a560959a 100644
--- a/examples/integration_wgpu/src/main.rs
+++ b/examples/integration/src/main.rs
@@ -4,14 +4,17 @@ mod scene;
use controls::Controls;
use scene::Scene;
-use iced_wgpu::{wgpu, Backend, Renderer, Settings, Viewport};
-use iced_winit::{
- conversion, futures, program, renderer, window, winit, Clipboard, Color,
- Debug, Size,
-};
+use iced_wgpu::graphics::Viewport;
+use iced_wgpu::{wgpu, Backend, Renderer, Settings};
+use iced_winit::core::mouse;
+use iced_winit::core::renderer;
+use iced_winit::core::{Color, Size};
+use iced_winit::runtime::program;
+use iced_winit::runtime::Debug;
+use iced_winit::style::Theme;
+use iced_winit::{conversion, futures, winit, Clipboard};
use winit::{
- dpi::PhysicalPosition,
event::{Event, ModifiersState, WindowEvent},
event_loop::{ControlFlow, EventLoop},
};
@@ -23,19 +26,20 @@ use web_sys::HtmlCanvasElement;
#[cfg(target_arch = "wasm32")]
use winit::platform::web::WindowBuilderExtWebSys;
-pub fn main() {
+pub fn main() -> Result<(), Box<dyn std::error::Error>> {
#[cfg(target_arch = "wasm32")]
let canvas_element = {
- console_log::init_with_level(log::Level::Debug)
- .expect("could not initialize logger");
+ console_log::init_with_level(log::Level::Debug)?;
+
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
web_sys::window()
.and_then(|win| win.document())
.and_then(|doc| doc.get_element_by_id("iced_canvas"))
.and_then(|element| element.dyn_into::<HtmlCanvasElement>().ok())
- .expect("Canvas with id `iced_canvas` is missing")
+ .expect("Get canvas element")
};
+
#[cfg(not(target_arch = "wasm32"))]
env_logger::init();
@@ -45,23 +49,21 @@ pub fn main() {
#[cfg(target_arch = "wasm32")]
let window = winit::window::WindowBuilder::new()
.with_canvas(Some(canvas_element))
- .build(&event_loop)
- .expect("Failed to build winit window");
+ .build(&event_loop)?;
#[cfg(not(target_arch = "wasm32"))]
- let window = winit::window::Window::new(&event_loop).unwrap();
+ let window = winit::window::Window::new(&event_loop)?;
let physical_size = window.inner_size();
let mut viewport = Viewport::with_physical_size(
Size::new(physical_size.width, physical_size.height),
window.scale_factor(),
);
- let mut cursor_position = PhysicalPosition::new(-1.0, -1.0);
+ let mut cursor_position = None;
let mut modifiers = ModifiersState::default();
let mut clipboard = Clipboard::connect(&window);
// Initialize wgpu
-
#[cfg(target_arch = "wasm32")]
let default_backend = wgpu::Backends::GL;
#[cfg(not(target_arch = "wasm32"))]
@@ -70,46 +72,55 @@ pub fn main() {
let backend =
wgpu::util::backend_bits_from_env().unwrap_or(default_backend);
- let instance = wgpu::Instance::new(backend);
- let surface = unsafe { instance.create_surface(&window) };
-
- let (format, (device, queue)) = futures::executor::block_on(async {
- let adapter = wgpu::util::initialize_adapter_from_env_or_default(
- &instance,
- backend,
- Some(&surface),
- )
- .await
- .expect("No suitable GPU adapters found on the system!");
-
- let adapter_features = adapter.features();
-
- #[cfg(target_arch = "wasm32")]
- let needed_limits = wgpu::Limits::downlevel_webgl2_defaults()
- .using_resolution(adapter.limits());
-
- #[cfg(not(target_arch = "wasm32"))]
- let needed_limits = wgpu::Limits::default();
-
- (
- surface
- .get_supported_formats(&adapter)
- .first()
- .copied()
- .expect("Get preferred format"),
- adapter
- .request_device(
- &wgpu::DeviceDescriptor {
- label: None,
- features: adapter_features & wgpu::Features::default(),
- limits: needed_limits,
- },
- None,
- )
- .await
- .expect("Request device"),
- )
+ let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
+ backends: backend,
+ ..Default::default()
});
+ let surface = unsafe { instance.create_surface(&window) }?;
+
+ let (format, (device, queue)) =
+ futures::futures::executor::block_on(async {
+ let adapter = wgpu::util::initialize_adapter_from_env_or_default(
+ &instance,
+ backend,
+ Some(&surface),
+ )
+ .await
+ .expect("Create adapter");
+
+ let adapter_features = adapter.features();
+
+ #[cfg(target_arch = "wasm32")]
+ let needed_limits = wgpu::Limits::downlevel_webgl2_defaults()
+ .using_resolution(adapter.limits());
+
+ #[cfg(not(target_arch = "wasm32"))]
+ let needed_limits = wgpu::Limits::default();
+
+ let capabilities = surface.get_capabilities(&adapter);
+
+ (
+ capabilities
+ .formats
+ .iter()
+ .copied()
+ .find(wgpu::TextureFormat::is_srgb)
+ .or_else(|| capabilities.formats.first().copied())
+ .expect("Get preferred format"),
+ adapter
+ .request_device(
+ &wgpu::DeviceDescriptor {
+ label: None,
+ features: adapter_features
+ & wgpu::Features::default(),
+ limits: needed_limits,
+ },
+ None,
+ )
+ .await
+ .expect("Request device"),
+ )
+ });
surface.configure(
&device,
@@ -120,22 +131,24 @@ pub fn main() {
height: physical_size.height,
present_mode: wgpu::PresentMode::AutoVsync,
alpha_mode: wgpu::CompositeAlphaMode::Auto,
+ view_formats: vec![],
},
);
let mut resized = false;
- // Initialize staging belt
- let mut staging_belt = wgpu::util::StagingBelt::new(5 * 1024);
-
// Initialize scene and GUI controls
let scene = Scene::new(&device, format);
let controls = Controls::new();
// Initialize iced
let mut debug = Debug::new();
- let mut renderer =
- Renderer::new(Backend::new(&device, Settings::default(), format));
+ let mut renderer = Renderer::new(Backend::new(
+ &device,
+ &queue,
+ Settings::default(),
+ format,
+ ));
let mut state = program::State::new(
controls,
@@ -153,7 +166,7 @@ pub fn main() {
Event::WindowEvent { event, .. } => {
match event {
WindowEvent::CursorMoved { position, .. } => {
- cursor_position = position;
+ cursor_position = Some(position);
}
WindowEvent::ModifiersChanged(new_modifiers) => {
modifiers = new_modifiers;
@@ -183,13 +196,20 @@ pub fn main() {
// We update iced
let _ = state.update(
viewport.logical_size(),
- conversion::cursor_position(
- cursor_position,
- viewport.scale_factor(),
- ),
+ cursor_position
+ .map(|p| {
+ conversion::cursor_position(
+ p,
+ viewport.scale_factor(),
+ )
+ })
+ .map(mouse::Cursor::Available)
+ .unwrap_or(mouse::Cursor::Unavailable),
&mut renderer,
- &iced_wgpu::Theme::Dark,
- &renderer::Style { text_color: Color::WHITE },
+ &Theme::Dark,
+ &renderer::Style {
+ text_color: Color::WHITE,
+ },
&mut clipboard,
&mut debug,
);
@@ -215,7 +235,8 @@ pub fn main() {
width: size.width,
height: size.height,
present_mode: wgpu::PresentMode::AutoVsync,
- alpha_mode: wgpu::CompositeAlphaMode::Auto
+ alpha_mode: wgpu::CompositeAlphaMode::Auto,
+ view_formats: vec![],
},
);
@@ -230,7 +251,9 @@ pub fn main() {
let program = state.program();
- let view = frame.texture.create_view(&wgpu::TextureViewDescriptor::default());
+ let view = frame.texture.create_view(
+ &wgpu::TextureViewDescriptor::default(),
+ );
{
// We clear the frame
@@ -248,8 +271,9 @@ pub fn main() {
renderer.with_primitives(|backend, primitive| {
backend.present(
&device,
- &mut staging_belt,
+ &queue,
&mut encoder,
+ None,
&view,
primitive,
&viewport,
@@ -258,24 +282,22 @@ pub fn main() {
});
// Then we submit the work
- staging_belt.finish();
queue.submit(Some(encoder.finish()));
frame.present();
// Update the mouse cursor
- window.set_cursor_icon(
- iced_winit::conversion::mouse_interaction(
- state.mouse_interaction(),
- ),
- );
-
- // And recall staging buffers
- staging_belt.recall();
-
+ window.set_cursor_icon(
+ iced_winit::conversion::mouse_interaction(
+ state.mouse_interaction(),
+ ),
+ );
}
Err(error) => match error {
wgpu::SurfaceError::OutOfMemory => {
- panic!("Swapchain error: {error}. Rendering cannot continue.")
+ panic!(
+ "Swapchain error: {error}. \
+ Rendering cannot continue."
+ )
}
_ => {
// Try rendering again next frame.
diff --git a/examples/integration_wgpu/src/scene.rs b/examples/integration/src/scene.rs
index 3e41fbda..90c7efbf 100644
--- a/examples/integration_wgpu/src/scene.rs
+++ b/examples/integration/src/scene.rs
@@ -1,5 +1,5 @@
use iced_wgpu::wgpu;
-use iced_winit::Color;
+use iced_winit::core::Color;
pub struct Scene {
pipeline: wgpu::RenderPipeline,
diff --git a/examples/integration_wgpu/src/shader/frag.wgsl b/examples/integration/src/shader/frag.wgsl
index cf27bb56..cf27bb56 100644
--- a/examples/integration_wgpu/src/shader/frag.wgsl
+++ b/examples/integration/src/shader/frag.wgsl
diff --git a/examples/integration_wgpu/src/shader/vert.wgsl b/examples/integration/src/shader/vert.wgsl
index e353e6ba..e353e6ba 100644
--- a/examples/integration_wgpu/src/shader/vert.wgsl
+++ b/examples/integration/src/shader/vert.wgsl
diff --git a/examples/integration_opengl/Cargo.toml b/examples/integration_opengl/Cargo.toml
deleted file mode 100644
index 6dac999c..00000000
--- a/examples/integration_opengl/Cargo.toml
+++ /dev/null
@@ -1,12 +0,0 @@
-[package]
-name = "integration_opengl"
-version = "0.1.0"
-authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
-edition = "2021"
-publish = false
-
-[dependencies]
-iced_glutin = { path = "../../glutin" }
-iced_glow = { path = "../../glow" }
-iced_winit = { path = "../../winit" }
-env_logger = "0.8"
diff --git a/examples/integration_opengl/README.md b/examples/integration_opengl/README.md
deleted file mode 100644
index b7c2c074..00000000
--- a/examples/integration_opengl/README.md
+++ /dev/null
@@ -1,16 +0,0 @@
-## OpenGL integration
-
-A demonstration of how to integrate Iced in an existing graphical OpenGL application.
-
-The __[`main`]__ file contains all the code of the example.
-
-<div align="center">
- <a href="https://imgbox.com/9P9ETcod" target="_blank"><img src="https://images2.imgbox.com/2a/51/9P9ETcod_o.gif" alt="image host"/></a>
-</div>
-
-You can run it with `cargo run`:
-```
-cargo run --package integration_opengl
-```
-
-[`main`]: src/main.rs
diff --git a/examples/integration_opengl/src/controls.rs b/examples/integration_opengl/src/controls.rs
deleted file mode 100644
index c3648f44..00000000
--- a/examples/integration_opengl/src/controls.rs
+++ /dev/null
@@ -1,101 +0,0 @@
-use iced_glow::Renderer;
-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,
-}
-
-#[derive(Debug, Clone)]
-pub enum Message {
- BackgroundColorChanged(Color),
-}
-
-impl Controls {
- pub fn new() -> Controls {
- Controls {
- background_color: Color::BLACK,
- }
- }
-
- pub fn background_color(&self) -> Color {
- self.background_color
- }
-}
-
-impl Program for Controls {
- type Renderer = Renderer;
- type Message = Message;
-
- fn update(&mut self, message: Message) -> Command<Message> {
- match message {
- Message::BackgroundColorChanged(color) => {
- self.background_color = color;
- }
- }
-
- Command::none()
- }
-
- fn view(&self) -> Element<Message, Renderer> {
- let background_color = self.background_color;
-
- let sliders = Row::new()
- .width(500)
- .spacing(20)
- .push(
- Slider::new(0.0..=1.0, background_color.r, move |r| {
- Message::BackgroundColorChanged(Color {
- r,
- ..background_color
- })
- })
- .step(0.01),
- )
- .push(
- Slider::new(0.0..=1.0, background_color.g, move |g| {
- Message::BackgroundColorChanged(Color {
- g,
- ..background_color
- })
- })
- .step(0.01),
- )
- .push(
- Slider::new(0.0..=1.0, background_color.b, move |b| {
- Message::BackgroundColorChanged(Color {
- b,
- ..background_color
- })
- })
- .step(0.01),
- );
-
- Row::new()
- .width(Length::Fill)
- .height(Length::Fill)
- .align_items(Alignment::End)
- .push(
- Column::new()
- .width(Length::Fill)
- .align_items(Alignment::End)
- .push(
- Column::new()
- .padding(10)
- .spacing(10)
- .push(
- Text::new("Background color")
- .style(Color::WHITE),
- )
- .push(sliders)
- .push(
- Text::new(format!("{background_color:?}"))
- .size(14)
- .style(Color::WHITE),
- ),
- ),
- )
- .into()
- }
-}
diff --git a/examples/integration_opengl/src/main.rs b/examples/integration_opengl/src/main.rs
index 4dd3a4a9..e69de29b 100644
--- a/examples/integration_opengl/src/main.rs
+++ b/examples/integration_opengl/src/main.rs
@@ -1,188 +0,0 @@
-mod controls;
-mod scene;
-
-use controls::Controls;
-use scene::Scene;
-
-use glow::*;
-use glutin::dpi::PhysicalPosition;
-use glutin::event::{Event, ModifiersState, WindowEvent};
-use glutin::event_loop::ControlFlow;
-use iced_glow::glow;
-use iced_glow::{Backend, Renderer, Settings, Viewport};
-use iced_glutin::conversion;
-use iced_glutin::glutin;
-use iced_glutin::renderer;
-use iced_glutin::{program, Clipboard, Color, Debug, Size};
-
-pub fn main() {
- env_logger::init();
- let (gl, event_loop, windowed_context, shader_version) = {
- let el = glutin::event_loop::EventLoop::new();
-
- let wb = glutin::window::WindowBuilder::new()
- .with_title("OpenGL integration example")
- .with_inner_size(glutin::dpi::LogicalSize::new(1024.0, 768.0));
-
- let windowed_context = glutin::ContextBuilder::new()
- .with_vsync(true)
- .build_windowed(wb, &el)
- .unwrap();
-
- unsafe {
- let windowed_context = windowed_context.make_current().unwrap();
-
- let gl = glow::Context::from_loader_function(|s| {
- windowed_context.get_proc_address(s) as *const _
- });
-
- // Enable auto-conversion from/to sRGB
- gl.enable(glow::FRAMEBUFFER_SRGB);
-
- // Enable alpha blending
- gl.enable(glow::BLEND);
- gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA);
-
- // Disable multisampling by default
- gl.disable(glow::MULTISAMPLE);
-
- (gl, el, windowed_context, "#version 410")
- }
- };
-
- let physical_size = windowed_context.window().inner_size();
- let mut viewport = Viewport::with_physical_size(
- Size::new(physical_size.width, physical_size.height),
- windowed_context.window().scale_factor(),
- );
-
- 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 renderer = Renderer::new(Backend::new(&gl, Settings::default()));
-
- let mut debug = Debug::new();
-
- let controls = Controls::new();
- let mut state = program::State::new(
- controls,
- viewport.logical_size(),
- &mut renderer,
- &mut debug,
- );
- let mut resized = false;
-
- let scene = Scene::new(&gl, shader_version);
-
- event_loop.run(move |event, _, control_flow| {
- *control_flow = ControlFlow::Wait;
-
- match event {
- Event::WindowEvent { event, .. } => {
- match event {
- WindowEvent::CursorMoved { position, .. } => {
- cursor_position = position;
- }
- WindowEvent::ModifiersChanged(new_modifiers) => {
- modifiers = new_modifiers;
- }
- WindowEvent::Resized(physical_size) => {
- viewport = Viewport::with_physical_size(
- Size::new(
- physical_size.width,
- physical_size.height,
- ),
- windowed_context.window().scale_factor(),
- );
-
- resized = true;
- }
- WindowEvent::CloseRequested => {
- scene.cleanup(&gl);
- *control_flow = ControlFlow::Exit
- }
- _ => (),
- }
-
- // Map window event to iced event
- if let Some(event) = iced_winit::conversion::window_event(
- iced_winit::window::Id::MAIN,
- &event,
- windowed_context.window().scale_factor(),
- modifiers,
- ) {
- state.queue_event(event);
- }
- }
- Event::MainEventsCleared => {
- // If there are events pending
- if !state.is_queue_empty() {
- // We update iced
- let _ = state.update(
- viewport.logical_size(),
- conversion::cursor_position(
- cursor_position,
- viewport.scale_factor(),
- ),
- &mut renderer,
- &iced_glow::Theme::Dark,
- &renderer::Style {
- text_color: Color::WHITE,
- },
- &mut clipboard,
- &mut debug,
- );
-
- // and request a redraw
- windowed_context.window().request_redraw();
- }
- }
- Event::RedrawRequested(_) => {
- if resized {
- let size = windowed_context.window().inner_size();
-
- unsafe {
- gl.viewport(
- 0,
- 0,
- size.width as i32,
- size.height as i32,
- );
- }
-
- resized = false;
- }
-
- let program = state.program();
- {
- // We clear the frame
- scene.clear(&gl, program.background_color());
-
- // Draw the scene
- scene.draw(&gl);
- }
-
- // And then iced on top
- renderer.with_primitives(|backend, primitive| {
- backend.present(
- &gl,
- primitive,
- &viewport,
- &debug.overlay(),
- );
- });
-
- // Update the mouse cursor
- windowed_context.window().set_cursor_icon(
- iced_winit::conversion::mouse_interaction(
- state.mouse_interaction(),
- ),
- );
-
- windowed_context.swap_buffers().unwrap();
- }
- _ => (),
- }
- });
-}
diff --git a/examples/integration_opengl/src/scene.rs b/examples/integration_opengl/src/scene.rs
deleted file mode 100644
index c1d05b65..00000000
--- a/examples/integration_opengl/src/scene.rs
+++ /dev/null
@@ -1,102 +0,0 @@
-use glow::*;
-use iced_glow::glow;
-use iced_glow::Color;
-
-pub struct Scene {
- program: glow::Program,
- vertex_array: glow::VertexArray,
-}
-
-impl Scene {
- pub fn new(gl: &glow::Context, shader_version: &str) -> Self {
- unsafe {
- let vertex_array = gl
- .create_vertex_array()
- .expect("Cannot create vertex array");
- gl.bind_vertex_array(Some(vertex_array));
-
- let program = gl.create_program().expect("Cannot create program");
-
- let (vertex_shader_source, fragment_shader_source) = (
- r#"const vec2 verts[3] = vec2[3](
- vec2(0.5f, 1.0f),
- vec2(0.0f, 0.0f),
- vec2(1.0f, 0.0f)
- );
- out vec2 vert;
- void main() {
- vert = verts[gl_VertexID];
- gl_Position = vec4(vert - 0.5, 0.0, 1.0);
- }"#,
- r#"precision highp float;
- in vec2 vert;
- out vec4 color;
- void main() {
- color = vec4(vert, 0.5, 1.0);
- }"#,
- );
-
- let shader_sources = [
- (glow::VERTEX_SHADER, vertex_shader_source),
- (glow::FRAGMENT_SHADER, fragment_shader_source),
- ];
-
- let mut shaders = Vec::with_capacity(shader_sources.len());
-
- for (shader_type, shader_source) in shader_sources.iter() {
- let shader = gl
- .create_shader(*shader_type)
- .expect("Cannot create shader");
- gl.shader_source(
- shader,
- &format!("{shader_version}\n{shader_source}"),
- );
- gl.compile_shader(shader);
- if !gl.get_shader_compile_status(shader) {
- panic!("{}", gl.get_shader_info_log(shader));
- }
- gl.attach_shader(program, shader);
- shaders.push(shader);
- }
-
- gl.link_program(program);
- if !gl.get_program_link_status(program) {
- panic!("{}", gl.get_program_info_log(program));
- }
-
- for shader in shaders {
- gl.detach_shader(program, shader);
- gl.delete_shader(shader);
- }
-
- gl.use_program(Some(program));
- Self {
- program,
- vertex_array,
- }
- }
- }
-
- pub fn clear(&self, gl: &glow::Context, background_color: Color) {
- let [r, g, b, a] = background_color.into_linear();
- unsafe {
- gl.clear_color(r, g, b, a);
- gl.clear(glow::COLOR_BUFFER_BIT);
- }
- }
-
- pub fn draw(&self, gl: &glow::Context) {
- unsafe {
- gl.bind_vertex_array(Some(self.vertex_array));
- gl.use_program(Some(self.program));
- gl.draw_arrays(glow::TRIANGLES, 0, 3);
- }
- }
-
- pub fn cleanup(&self, gl: &glow::Context) {
- unsafe {
- gl.delete_program(self.program);
- gl.delete_vertex_array(self.vertex_array);
- }
- }
-}
diff --git a/examples/lazy/Cargo.toml b/examples/lazy/Cargo.toml
index 79255c25..e03e89a9 100644
--- a/examples/lazy/Cargo.toml
+++ b/examples/lazy/Cargo.toml
@@ -6,5 +6,4 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["debug"] }
-iced_lazy = { path = "../../lazy" }
+iced = { path = "../..", features = ["debug", "lazy"] }
diff --git a/examples/lazy/src/main.rs b/examples/lazy/src/main.rs
index 6512106f..c6baa6a1 100644
--- a/examples/lazy/src/main.rs
+++ b/examples/lazy/src/main.rs
@@ -1,10 +1,9 @@
use iced::theme;
use iced::widget::{
- button, column, horizontal_space, pick_list, row, scrollable, text,
+ button, column, horizontal_space, lazy, pick_list, row, scrollable, text,
text_input,
};
use iced::{Element, Length, Sandbox, Settings};
-use iced_lazy::lazy;
use std::collections::HashSet;
use std::hash::Hash;
@@ -214,12 +213,9 @@ impl Sandbox for App {
column![
scrollable(options).height(Length::Fill),
row![
- text_input(
- "Add a new option",
- &self.input,
- Message::InputChanged,
- )
- .on_submit(Message::AddItem(self.input.clone())),
+ text_input("Add a new option", &self.input)
+ .on_input(Message::InputChanged)
+ .on_submit(Message::AddItem(self.input.clone())),
button(text(format!("Toggle Order ({})", self.order)))
.on_press(Message::ToggleOrder)
]
diff --git a/examples/loading_spinners/Cargo.toml b/examples/loading_spinners/Cargo.toml
new file mode 100644
index 00000000..ee9a48aa
--- /dev/null
+++ b/examples/loading_spinners/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "loading_spinners"
+version = "0.1.0"
+authors = ["Nick Senger <dev@nsenger.com>"]
+edition = "2021"
+publish = false
+
+[dependencies]
+iced = { path = "../..", features = ["advanced", "canvas"] }
+lyon_algorithms = "1"
+once_cell = "1"
diff --git a/examples/loading_spinners/README.md b/examples/loading_spinners/README.md
new file mode 100644
index 00000000..3573c6f6
--- /dev/null
+++ b/examples/loading_spinners/README.md
@@ -0,0 +1,14 @@
+## Loading Spinners
+
+Example implementation of animated indeterminate loading spinners.
+
+<div align="center">
+ <a href="https://gfycat.com/importantdevotedhammerheadbird">
+ <img src="https://thumbs.gfycat.com/ImportantDevotedHammerheadbird-small.gif">
+ </a>
+</div>
+
+You can run it with `cargo run`:
+```
+cargo run --package loading_spinners
+```
diff --git a/examples/loading_spinners/src/circular.rs b/examples/loading_spinners/src/circular.rs
new file mode 100644
index 00000000..3a35e029
--- /dev/null
+++ b/examples/loading_spinners/src/circular.rs
@@ -0,0 +1,421 @@
+//! Show a circular progress indicator.
+use iced::advanced::layout;
+use iced::advanced::renderer;
+use iced::advanced::widget::tree::{self, Tree};
+use iced::advanced::{Clipboard, Layout, Renderer, Shell, Widget};
+use iced::event;
+use iced::mouse;
+use iced::time::Instant;
+use iced::widget::canvas;
+use iced::window::{self, RedrawRequest};
+use iced::{
+ Background, Color, Element, Event, Length, Rectangle, Size, Vector,
+};
+
+use super::easing::{self, Easing};
+
+use std::f32::consts::PI;
+use std::time::Duration;
+
+const MIN_RADIANS: f32 = PI / 8.0;
+const WRAP_RADIANS: f32 = 2.0 * PI - PI / 4.0;
+const BASE_ROTATION_SPEED: u32 = u32::MAX / 80;
+
+#[allow(missing_debug_implementations)]
+pub struct Circular<'a, Theme>
+where
+ Theme: StyleSheet,
+{
+ size: f32,
+ bar_height: f32,
+ style: <Theme as StyleSheet>::Style,
+ easing: &'a Easing,
+ cycle_duration: Duration,
+ rotation_duration: Duration,
+}
+
+impl<'a, Theme> Circular<'a, Theme>
+where
+ Theme: StyleSheet,
+{
+ /// Creates a new [`Circular`] with the given content.
+ pub fn new() -> Self {
+ Circular {
+ size: 40.0,
+ bar_height: 4.0,
+ style: <Theme as StyleSheet>::Style::default(),
+ easing: &easing::STANDARD,
+ cycle_duration: Duration::from_millis(600),
+ rotation_duration: Duration::from_secs(2),
+ }
+ }
+
+ /// Sets the size of the [`Circular`].
+ pub fn size(mut self, size: f32) -> Self {
+ self.size = size;
+ self
+ }
+
+ /// Sets the bar height of the [`Circular`].
+ pub fn bar_height(mut self, bar_height: f32) -> Self {
+ self.bar_height = bar_height;
+ self
+ }
+
+ /// Sets the style variant of this [`Circular`].
+ pub fn style(mut self, style: <Theme as StyleSheet>::Style) -> Self {
+ self.style = style;
+ self
+ }
+
+ /// Sets the easing of this [`Circular`].
+ pub fn easing(mut self, easing: &'a Easing) -> Self {
+ self.easing = easing;
+ self
+ }
+
+ /// Sets the cycle duration of this [`Circular`].
+ pub fn cycle_duration(mut self, duration: Duration) -> Self {
+ self.cycle_duration = duration / 2;
+ self
+ }
+
+ /// Sets the base rotation duration of this [`Circular`]. This is the duration that a full
+ /// rotation would take if the cycle rotation were set to 0.0 (no expanding or contracting)
+ pub fn rotation_duration(mut self, duration: Duration) -> Self {
+ self.rotation_duration = duration;
+ self
+ }
+}
+
+impl<'a, Theme> Default for Circular<'a, Theme>
+where
+ Theme: StyleSheet,
+{
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+#[derive(Clone, Copy)]
+enum Animation {
+ Expanding {
+ start: Instant,
+ progress: f32,
+ rotation: u32,
+ last: Instant,
+ },
+ Contracting {
+ start: Instant,
+ progress: f32,
+ rotation: u32,
+ last: Instant,
+ },
+}
+
+impl Default for Animation {
+ fn default() -> Self {
+ Self::Expanding {
+ start: Instant::now(),
+ progress: 0.0,
+ rotation: 0,
+ last: Instant::now(),
+ }
+ }
+}
+
+impl Animation {
+ fn next(&self, additional_rotation: u32, now: Instant) -> Self {
+ match self {
+ Self::Expanding { rotation, .. } => Self::Contracting {
+ start: now,
+ progress: 0.0,
+ rotation: rotation.wrapping_add(additional_rotation),
+ last: now,
+ },
+ Self::Contracting { rotation, .. } => Self::Expanding {
+ start: now,
+ progress: 0.0,
+ rotation: rotation.wrapping_add(
+ BASE_ROTATION_SPEED.wrapping_add(
+ ((WRAP_RADIANS / (2.0 * PI)) * u32::MAX as f32) as u32,
+ ),
+ ),
+ last: now,
+ },
+ }
+ }
+
+ fn start(&self) -> Instant {
+ match self {
+ Self::Expanding { start, .. } | Self::Contracting { start, .. } => {
+ *start
+ }
+ }
+ }
+
+ fn last(&self) -> Instant {
+ match self {
+ Self::Expanding { last, .. } | Self::Contracting { last, .. } => {
+ *last
+ }
+ }
+ }
+
+ fn timed_transition(
+ &self,
+ cycle_duration: Duration,
+ rotation_duration: Duration,
+ now: Instant,
+ ) -> Self {
+ let elapsed = now.duration_since(self.start());
+ let additional_rotation = ((now - self.last()).as_secs_f32()
+ / rotation_duration.as_secs_f32()
+ * (u32::MAX) as f32) as u32;
+
+ match elapsed {
+ elapsed if elapsed > cycle_duration => {
+ self.next(additional_rotation, now)
+ }
+ _ => self.with_elapsed(
+ cycle_duration,
+ additional_rotation,
+ elapsed,
+ now,
+ ),
+ }
+ }
+
+ fn with_elapsed(
+ &self,
+ cycle_duration: Duration,
+ additional_rotation: u32,
+ elapsed: Duration,
+ now: Instant,
+ ) -> Self {
+ let progress = elapsed.as_secs_f32() / cycle_duration.as_secs_f32();
+ match self {
+ Self::Expanding {
+ start, rotation, ..
+ } => Self::Expanding {
+ start: *start,
+ progress,
+ rotation: rotation.wrapping_add(additional_rotation),
+ last: now,
+ },
+ Self::Contracting {
+ start, rotation, ..
+ } => Self::Contracting {
+ start: *start,
+ progress,
+ rotation: rotation.wrapping_add(additional_rotation),
+ last: now,
+ },
+ }
+ }
+
+ fn rotation(&self) -> f32 {
+ match self {
+ Self::Expanding { rotation, .. }
+ | Self::Contracting { rotation, .. } => {
+ *rotation as f32 / u32::MAX as f32
+ }
+ }
+ }
+}
+
+#[derive(Default)]
+struct State {
+ animation: Animation,
+ cache: canvas::Cache,
+}
+
+impl<'a, Message, Theme> Widget<Message, iced::Renderer<Theme>>
+ for Circular<'a, Theme>
+where
+ Message: 'a + Clone,
+ Theme: StyleSheet,
+{
+ fn tag(&self) -> tree::Tag {
+ tree::Tag::of::<State>()
+ }
+
+ fn state(&self) -> tree::State {
+ tree::State::new(State::default())
+ }
+
+ fn width(&self) -> Length {
+ Length::Fixed(self.size)
+ }
+
+ fn height(&self) -> Length {
+ Length::Fixed(self.size)
+ }
+
+ fn layout(
+ &self,
+ _renderer: &iced::Renderer<Theme>,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ let limits = limits.width(self.size).height(self.size);
+ let size = limits.resolve(Size::ZERO);
+
+ layout::Node::new(size)
+ }
+
+ fn on_event(
+ &mut self,
+ tree: &mut Tree,
+ event: Event,
+ _layout: Layout<'_>,
+ _cursor: mouse::Cursor,
+ _renderer: &iced::Renderer<Theme>,
+ _clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> event::Status {
+ const FRAME_RATE: u64 = 60;
+
+ let state = tree.state.downcast_mut::<State>();
+
+ if let Event::Window(window::Event::RedrawRequested(now)) = event {
+ state.animation = state.animation.timed_transition(
+ self.cycle_duration,
+ self.rotation_duration,
+ now,
+ );
+
+ state.cache.clear();
+ shell.request_redraw(RedrawRequest::At(
+ now + Duration::from_millis(1000 / FRAME_RATE),
+ ));
+ }
+
+ event::Status::Ignored
+ }
+
+ fn draw(
+ &self,
+ tree: &Tree,
+ renderer: &mut iced::Renderer<Theme>,
+ theme: &Theme,
+ _style: &renderer::Style,
+ layout: Layout<'_>,
+ _cursor: mouse::Cursor,
+ _viewport: &Rectangle,
+ ) {
+ let state = tree.state.downcast_ref::<State>();
+ let bounds = layout.bounds();
+ let custom_style =
+ <Theme as StyleSheet>::appearance(theme, &self.style);
+
+ let geometry = state.cache.draw(renderer, bounds.size(), |frame| {
+ let track_radius = frame.width() / 2.0 - self.bar_height;
+ let track_path = canvas::Path::circle(frame.center(), track_radius);
+
+ frame.stroke(
+ &track_path,
+ canvas::Stroke::default()
+ .with_color(custom_style.track_color)
+ .with_width(self.bar_height),
+ );
+
+ let mut builder = canvas::path::Builder::new();
+
+ let start = state.animation.rotation() * 2.0 * PI;
+
+ match state.animation {
+ Animation::Expanding { progress, .. } => {
+ builder.arc(canvas::path::Arc {
+ center: frame.center(),
+ radius: track_radius,
+ start_angle: start,
+ end_angle: start
+ + MIN_RADIANS
+ + WRAP_RADIANS * (self.easing.y_at_x(progress)),
+ });
+ }
+ Animation::Contracting { progress, .. } => {
+ builder.arc(canvas::path::Arc {
+ center: frame.center(),
+ radius: track_radius,
+ start_angle: start
+ + WRAP_RADIANS * (self.easing.y_at_x(progress)),
+ end_angle: start + MIN_RADIANS + WRAP_RADIANS,
+ });
+ }
+ }
+
+ let bar_path = builder.build();
+
+ frame.stroke(
+ &bar_path,
+ canvas::Stroke::default()
+ .with_color(custom_style.bar_color)
+ .with_width(self.bar_height),
+ );
+ });
+
+ renderer.with_translation(
+ Vector::new(bounds.x, bounds.y),
+ |renderer| {
+ use iced::advanced::graphics::geometry::Renderer as _;
+
+ renderer.draw(vec![geometry]);
+ },
+ );
+ }
+}
+
+impl<'a, Message, Theme> From<Circular<'a, Theme>>
+ for Element<'a, Message, iced::Renderer<Theme>>
+where
+ Message: Clone + 'a,
+ Theme: StyleSheet + 'a,
+{
+ fn from(circular: Circular<'a, Theme>) -> Self {
+ Self::new(circular)
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+pub struct Appearance {
+ /// The [`Background`] of the progress indicator.
+ pub background: Option<Background>,
+ /// The track [`Color`] of the progress indicator.
+ pub track_color: Color,
+ /// The bar [`Color`] of the progress indicator.
+ pub bar_color: Color,
+}
+
+impl std::default::Default for Appearance {
+ fn default() -> Self {
+ Self {
+ background: None,
+ track_color: Color::TRANSPARENT,
+ bar_color: Color::BLACK,
+ }
+ }
+}
+
+/// A set of rules that dictate the style of an indicator.
+pub trait StyleSheet {
+ /// The supported style of the [`StyleSheet`].
+ type Style: Default;
+
+ /// Produces the active [`Appearance`] of a indicator.
+ fn appearance(&self, style: &Self::Style) -> Appearance;
+}
+
+impl StyleSheet for iced::Theme {
+ type Style = ();
+
+ fn appearance(&self, _style: &Self::Style) -> Appearance {
+ let palette = self.extended_palette();
+
+ Appearance {
+ background: None,
+ track_color: palette.background.weak.color,
+ bar_color: palette.primary.base.color,
+ }
+ }
+}
diff --git a/examples/loading_spinners/src/easing.rs b/examples/loading_spinners/src/easing.rs
new file mode 100644
index 00000000..665b3329
--- /dev/null
+++ b/examples/loading_spinners/src/easing.rs
@@ -0,0 +1,133 @@
+use iced::Point;
+
+use lyon_algorithms::measure::PathMeasurements;
+use lyon_algorithms::path::{builder::NoAttributes, path::BuilderImpl, Path};
+use once_cell::sync::Lazy;
+
+pub static EMPHASIZED: Lazy<Easing> = Lazy::new(|| {
+ Easing::builder()
+ .cubic_bezier_to([0.05, 0.0], [0.133333, 0.06], [0.166666, 0.4])
+ .cubic_bezier_to([0.208333, 0.82], [0.25, 1.0], [1.0, 1.0])
+ .build()
+});
+
+pub static EMPHASIZED_DECELERATE: Lazy<Easing> = Lazy::new(|| {
+ Easing::builder()
+ .cubic_bezier_to([0.05, 0.7], [0.1, 1.0], [1.0, 1.0])
+ .build()
+});
+
+pub static EMPHASIZED_ACCELERATE: Lazy<Easing> = Lazy::new(|| {
+ Easing::builder()
+ .cubic_bezier_to([0.3, 0.0], [0.8, 0.15], [1.0, 1.0])
+ .build()
+});
+
+pub static STANDARD: Lazy<Easing> = Lazy::new(|| {
+ Easing::builder()
+ .cubic_bezier_to([0.2, 0.0], [0.0, 1.0], [1.0, 1.0])
+ .build()
+});
+
+pub static STANDARD_DECELERATE: Lazy<Easing> = Lazy::new(|| {
+ Easing::builder()
+ .cubic_bezier_to([0.0, 0.0], [0.0, 1.0], [1.0, 1.0])
+ .build()
+});
+
+pub static STANDARD_ACCELERATE: Lazy<Easing> = Lazy::new(|| {
+ Easing::builder()
+ .cubic_bezier_to([0.3, 0.0], [1.0, 1.0], [1.0, 1.0])
+ .build()
+});
+
+pub struct Easing {
+ path: Path,
+ measurements: PathMeasurements,
+}
+
+impl Easing {
+ pub fn builder() -> Builder {
+ Builder::new()
+ }
+
+ pub fn y_at_x(&self, x: f32) -> f32 {
+ let mut sampler = self.measurements.create_sampler(
+ &self.path,
+ lyon_algorithms::measure::SampleType::Normalized,
+ );
+ let sample = sampler.sample(x);
+
+ sample.position().y
+ }
+}
+
+pub struct Builder(NoAttributes<BuilderImpl>);
+
+impl Builder {
+ pub fn new() -> Self {
+ let mut builder = Path::builder();
+ builder.begin(lyon_algorithms::geom::point(0.0, 0.0));
+
+ Self(builder)
+ }
+
+ /// Adds a line segment. Points must be between 0,0 and 1,1
+ pub fn line_to(mut self, to: impl Into<Point>) -> Self {
+ self.0.line_to(Self::point(to));
+
+ self
+ }
+
+ /// Adds a quadratic bézier curve. Points must be between 0,0 and 1,1
+ pub fn quadratic_bezier_to(
+ mut self,
+ ctrl: impl Into<Point>,
+ to: impl Into<Point>,
+ ) -> Self {
+ self.0
+ .quadratic_bezier_to(Self::point(ctrl), Self::point(to));
+
+ self
+ }
+
+ /// Adds a cubic bézier curve. Points must be between 0,0 and 1,1
+ pub fn cubic_bezier_to(
+ mut self,
+ ctrl1: impl Into<Point>,
+ ctrl2: impl Into<Point>,
+ to: impl Into<Point>,
+ ) -> Self {
+ self.0.cubic_bezier_to(
+ Self::point(ctrl1),
+ Self::point(ctrl2),
+ Self::point(to),
+ );
+
+ self
+ }
+
+ pub fn build(mut self) -> Easing {
+ self.0.line_to(lyon_algorithms::geom::point(1.0, 1.0));
+ self.0.end(false);
+
+ let path = self.0.build();
+ let measurements = PathMeasurements::from_path(&path, 0.0);
+
+ Easing { path, measurements }
+ }
+
+ fn point(p: impl Into<Point>) -> lyon_algorithms::geom::Point<f32> {
+ let p: Point = p.into();
+ lyon_algorithms::geom::point(
+ p.x.min(1.0).max(0.0),
+ p.y.min(1.0).max(0.0),
+ )
+ }
+}
+
+impl Default for Builder {
+ fn default() -> Self {
+ Self::new()
+ }
+}
diff --git a/examples/loading_spinners/src/linear.rs b/examples/loading_spinners/src/linear.rs
new file mode 100644
index 00000000..3d95729b
--- /dev/null
+++ b/examples/loading_spinners/src/linear.rs
@@ -0,0 +1,326 @@
+//! Show a linear progress indicator.
+use iced::advanced::layout;
+use iced::advanced::renderer::{self, Quad};
+use iced::advanced::widget::tree::{self, Tree};
+use iced::advanced::{Clipboard, Layout, Shell, Widget};
+use iced::event;
+use iced::mouse;
+use iced::time::Instant;
+use iced::window::{self, RedrawRequest};
+use iced::{Background, Color, Element, Event, Length, Rectangle, Size};
+
+use super::easing::{self, Easing};
+
+use std::time::Duration;
+
+#[allow(missing_debug_implementations)]
+pub struct Linear<'a, Renderer>
+where
+ Renderer: iced::advanced::Renderer,
+ Renderer::Theme: StyleSheet,
+{
+ width: Length,
+ height: Length,
+ style: <Renderer::Theme as StyleSheet>::Style,
+ easing: &'a Easing,
+ cycle_duration: Duration,
+}
+
+impl<'a, Renderer> Linear<'a, Renderer>
+where
+ Renderer: iced::advanced::Renderer,
+ Renderer::Theme: StyleSheet,
+{
+ /// Creates a new [`Linear`] with the given content.
+ pub fn new() -> Self {
+ Linear {
+ width: Length::Fixed(100.0),
+ height: Length::Fixed(4.0),
+ style: <Renderer::Theme as StyleSheet>::Style::default(),
+ easing: &easing::STANDARD,
+ cycle_duration: Duration::from_millis(600),
+ }
+ }
+
+ /// Sets the width of the [`Linear`].
+ pub fn width(mut self, width: impl Into<Length>) -> Self {
+ self.width = width.into();
+ self
+ }
+
+ /// Sets the height of the [`Linear`].
+ pub fn height(mut self, height: impl Into<Length>) -> Self {
+ self.height = height.into();
+ self
+ }
+
+ /// Sets the style variant of this [`Linear`].
+ pub fn style(
+ mut self,
+ style: <Renderer::Theme as StyleSheet>::Style,
+ ) -> Self {
+ self.style = style;
+ self
+ }
+
+ /// Sets the motion easing of this [`Linear`].
+ pub fn easing(mut self, easing: &'a Easing) -> Self {
+ self.easing = easing;
+ self
+ }
+
+ /// Sets the cycle duration of this [`Linear`].
+ pub fn cycle_duration(mut self, duration: Duration) -> Self {
+ self.cycle_duration = duration / 2;
+ self
+ }
+}
+
+impl<'a, Renderer> Default for Linear<'a, Renderer>
+where
+ Renderer: iced::advanced::Renderer,
+ Renderer::Theme: StyleSheet,
+{
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+#[derive(Clone, Copy)]
+enum State {
+ Expanding { start: Instant, progress: f32 },
+ Contracting { start: Instant, progress: f32 },
+}
+
+impl Default for State {
+ fn default() -> Self {
+ Self::Expanding {
+ start: Instant::now(),
+ progress: 0.0,
+ }
+ }
+}
+
+impl State {
+ fn next(&self, now: Instant) -> Self {
+ match self {
+ Self::Expanding { .. } => Self::Contracting {
+ start: now,
+ progress: 0.0,
+ },
+ Self::Contracting { .. } => Self::Expanding {
+ start: now,
+ progress: 0.0,
+ },
+ }
+ }
+
+ fn start(&self) -> Instant {
+ match self {
+ Self::Expanding { start, .. } | Self::Contracting { start, .. } => {
+ *start
+ }
+ }
+ }
+
+ fn timed_transition(&self, cycle_duration: Duration, now: Instant) -> Self {
+ let elapsed = now.duration_since(self.start());
+
+ match elapsed {
+ elapsed if elapsed > cycle_duration => self.next(now),
+ _ => self.with_elapsed(cycle_duration, elapsed),
+ }
+ }
+
+ fn with_elapsed(
+ &self,
+ cycle_duration: Duration,
+ elapsed: Duration,
+ ) -> Self {
+ let progress = elapsed.as_secs_f32() / cycle_duration.as_secs_f32();
+ match self {
+ Self::Expanding { start, .. } => Self::Expanding {
+ start: *start,
+ progress,
+ },
+ Self::Contracting { start, .. } => Self::Contracting {
+ start: *start,
+ progress,
+ },
+ }
+ }
+}
+
+impl<'a, Message, Renderer> Widget<Message, Renderer> for Linear<'a, Renderer>
+where
+ Message: 'a + Clone,
+ Renderer: 'a + iced::advanced::Renderer,
+ Renderer::Theme: StyleSheet,
+{
+ fn tag(&self) -> tree::Tag {
+ tree::Tag::of::<State>()
+ }
+
+ fn state(&self) -> tree::State {
+ tree::State::new(State::default())
+ }
+
+ fn width(&self) -> Length {
+ self.width
+ }
+
+ fn height(&self) -> Length {
+ self.height
+ }
+
+ fn layout(
+ &self,
+ _renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ let limits = limits.width(self.width).height(self.height);
+ let size = limits.resolve(Size::ZERO);
+
+ layout::Node::new(size)
+ }
+
+ fn on_event(
+ &mut self,
+ tree: &mut Tree,
+ event: Event,
+ _layout: Layout<'_>,
+ _cursor: mouse::Cursor,
+ _renderer: &Renderer,
+ _clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> event::Status {
+ const FRAME_RATE: u64 = 60;
+
+ let state = tree.state.downcast_mut::<State>();
+
+ if let Event::Window(window::Event::RedrawRequested(now)) = event {
+ *state = state.timed_transition(self.cycle_duration, now);
+
+ shell.request_redraw(RedrawRequest::At(
+ now + Duration::from_millis(1000 / FRAME_RATE),
+ ));
+ }
+
+ event::Status::Ignored
+ }
+
+ fn draw(
+ &self,
+ tree: &Tree,
+ renderer: &mut Renderer,
+ theme: &Renderer::Theme,
+ _style: &renderer::Style,
+ layout: Layout<'_>,
+ _cursor: mouse::Cursor,
+ _viewport: &Rectangle,
+ ) {
+ let bounds = layout.bounds();
+ let custom_style = theme.appearance(&self.style);
+ let state = tree.state.downcast_ref::<State>();
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: Rectangle {
+ x: bounds.x,
+ y: bounds.y,
+ width: bounds.width,
+ height: bounds.height,
+ },
+ border_radius: 0.0.into(),
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ Background::Color(custom_style.track_color),
+ );
+
+ match state {
+ State::Expanding { progress, .. } => renderer.fill_quad(
+ renderer::Quad {
+ bounds: Rectangle {
+ x: bounds.x,
+ y: bounds.y,
+ width: self.easing.y_at_x(*progress) * bounds.width,
+ height: bounds.height,
+ },
+ border_radius: 0.0.into(),
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ Background::Color(custom_style.bar_color),
+ ),
+
+ State::Contracting { progress, .. } => renderer.fill_quad(
+ Quad {
+ bounds: Rectangle {
+ x: bounds.x
+ + self.easing.y_at_x(*progress) * bounds.width,
+ y: bounds.y,
+ width: (1.0 - self.easing.y_at_x(*progress))
+ * bounds.width,
+ height: bounds.height,
+ },
+ border_radius: 0.0.into(),
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ Background::Color(custom_style.bar_color),
+ ),
+ }
+ }
+}
+
+impl<'a, Message, Renderer> From<Linear<'a, Renderer>>
+ for Element<'a, Message, Renderer>
+where
+ Message: Clone + 'a,
+ Renderer: iced::advanced::Renderer + 'a,
+ Renderer::Theme: StyleSheet,
+{
+ fn from(linear: Linear<'a, Renderer>) -> Self {
+ Self::new(linear)
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+pub struct Appearance {
+ /// The track [`Color`] of the progress indicator.
+ pub track_color: Color,
+ /// The bar [`Color`] of the progress indicator.
+ pub bar_color: Color,
+}
+
+impl std::default::Default for Appearance {
+ fn default() -> Self {
+ Self {
+ track_color: Color::TRANSPARENT,
+ bar_color: Color::BLACK,
+ }
+ }
+}
+
+/// A set of rules that dictate the style of an indicator.
+pub trait StyleSheet {
+ /// The supported style of the [`StyleSheet`].
+ type Style: Default;
+
+ /// Produces the active [`Appearance`] of a indicator.
+ fn appearance(&self, style: &Self::Style) -> Appearance;
+}
+
+impl StyleSheet for iced::Theme {
+ type Style = ();
+
+ fn appearance(&self, _style: &Self::Style) -> Appearance {
+ let palette = self.extended_palette();
+
+ Appearance {
+ track_color: palette.background.weak.color,
+ bar_color: palette.primary.base.color,
+ }
+ }
+}
diff --git a/examples/loading_spinners/src/main.rs b/examples/loading_spinners/src/main.rs
new file mode 100644
index 00000000..a78e9590
--- /dev/null
+++ b/examples/loading_spinners/src/main.rs
@@ -0,0 +1,118 @@
+use iced::executor;
+use iced::widget::{column, container, row, slider, text};
+use iced::{Application, Command, Element, Length, Settings, Theme};
+
+use std::time::Duration;
+
+mod circular;
+mod easing;
+mod linear;
+
+use circular::Circular;
+use linear::Linear;
+
+pub fn main() -> iced::Result {
+ LoadingSpinners::run(Settings {
+ antialiasing: true,
+ ..Default::default()
+ })
+}
+
+struct LoadingSpinners {
+ cycle_duration: f32,
+}
+
+impl Default for LoadingSpinners {
+ fn default() -> Self {
+ Self {
+ cycle_duration: 2.0,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+enum Message {
+ CycleDurationChanged(f32),
+}
+
+impl Application for LoadingSpinners {
+ type Message = Message;
+ type Flags = ();
+ type Executor = executor::Default;
+ type Theme = Theme;
+
+ fn new(_flags: Self::Flags) -> (Self, Command<Message>) {
+ (Self::default(), Command::none())
+ }
+
+ fn title(&self) -> String {
+ String::from("Loading Spinners - Iced")
+ }
+
+ fn update(&mut self, message: Message) -> Command<Message> {
+ match message {
+ Message::CycleDurationChanged(duration) => {
+ self.cycle_duration = duration;
+ }
+ }
+
+ Command::none()
+ }
+
+ fn view(&self) -> Element<Message> {
+ let column = [
+ &easing::EMPHASIZED,
+ &easing::EMPHASIZED_DECELERATE,
+ &easing::EMPHASIZED_ACCELERATE,
+ &easing::STANDARD,
+ &easing::STANDARD_DECELERATE,
+ &easing::STANDARD_ACCELERATE,
+ ]
+ .iter()
+ .zip([
+ "Emphasized:",
+ "Emphasized Decelerate:",
+ "Emphasized Accelerate:",
+ "Standard:",
+ "Standard Decelerate:",
+ "Standard Accelerate:",
+ ])
+ .fold(column![], |column, (easing, label)| {
+ column.push(
+ row![
+ text(label).width(250),
+ Linear::new().easing(easing).cycle_duration(
+ Duration::from_secs_f32(self.cycle_duration)
+ ),
+ Circular::new().easing(easing).cycle_duration(
+ Duration::from_secs_f32(self.cycle_duration)
+ )
+ ]
+ .align_items(iced::Alignment::Center)
+ .spacing(20.0),
+ )
+ })
+ .spacing(20);
+
+ container(
+ column.push(
+ row(vec![
+ text("Cycle duration:").into(),
+ slider(1.0..=1000.0, self.cycle_duration * 100.0, |x| {
+ Message::CycleDurationChanged(x / 100.0)
+ })
+ .width(200.0)
+ .into(),
+ text(format!("{:.2}s", self.cycle_duration)).into(),
+ ])
+ .align_items(iced::Alignment::Center)
+ .spacing(20.0),
+ ),
+ )
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .center_x()
+ .center_y()
+ .into()
+ }
+}
diff --git a/examples/modal/Cargo.toml b/examples/modal/Cargo.toml
index 8770acac..3ac61e6a 100644
--- a/examples/modal/Cargo.toml
+++ b/examples/modal/Cargo.toml
@@ -6,5 +6,4 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = [] }
-iced_native = { path = "../../native" }
+iced = { path = "../..", features = ["advanced"] }
diff --git a/examples/modal/src/main.rs b/examples/modal/src/main.rs
index 54555684..7fcbbfe4 100644
--- a/examples/modal/src/main.rs
+++ b/examples/modal/src/main.rs
@@ -1,12 +1,15 @@
+use iced::executor;
+use iced::keyboard;
+use iced::subscription::{self, Subscription};
+use iced::theme;
use iced::widget::{
- self, button, column, container, horizontal_space, row, text, text_input,
-};
-use iced::{
- executor, keyboard, subscription, theme, Alignment, Application, Command,
- Element, Event, Length, Settings, Subscription,
+ self, button, column, container, horizontal_space, pick_list, row, text,
+ text_input,
};
+use iced::{Alignment, Application, Command, Element, Event, Length, Settings};
-use self::modal::Modal;
+use modal::Modal;
+use std::fmt;
pub fn main() -> iced::Result {
App::run(Settings::default())
@@ -17,6 +20,7 @@ struct App {
show_modal: bool,
email: String,
password: String,
+ plan: Plan,
}
#[derive(Debug, Clone)]
@@ -25,6 +29,7 @@ enum Message {
HideModal,
Email(String),
Password(String),
+ Plan(Plan),
Submit,
Event(Event),
}
@@ -65,6 +70,10 @@ impl Application for App {
self.password = password;
Command::none()
}
+ Message::Plan(plan) => {
+ self.plan = plan;
+ Command::none()
+ }
Message::Submit => {
if !self.email.is_empty() && !self.password.is_empty() {
self.hide_modal();
@@ -133,23 +142,31 @@ impl Application for App {
column![
column![
text("Email").size(12),
- text_input(
- "abc@123.com",
- &self.email,
- Message::Email
- )
- .on_submit(Message::Submit)
- .padding(5),
+ text_input("abc@123.com", &self.email,)
+ .on_input(Message::Email)
+ .on_submit(Message::Submit)
+ .padding(5),
]
.spacing(5),
column![
text("Password").size(12),
- text_input("", &self.password, Message::Password)
+ text_input("", &self.password)
+ .on_input(Message::Password)
.on_submit(Message::Submit)
.password()
.padding(5),
]
.spacing(5),
+ column![
+ text("Plan").size(12),
+ pick_list(
+ Plan::ALL,
+ Some(self.plan),
+ Message::Plan
+ )
+ .padding(5),
+ ]
+ .spacing(5),
button(text("Submit")).on_press(Message::HideModal),
]
.spacing(10)
@@ -177,13 +194,39 @@ impl App {
}
}
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+enum Plan {
+ #[default]
+ Basic,
+ Pro,
+ Enterprise,
+}
+
+impl Plan {
+ pub const ALL: &[Self] = &[Self::Basic, Self::Pro, Self::Enterprise];
+}
+
+impl fmt::Display for Plan {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Plan::Basic => "Basic",
+ Plan::Pro => "Pro",
+ Plan::Enterprise => "Enterprise",
+ }
+ .fmt(f)
+ }
+}
+
mod modal {
- use iced_native::alignment::Alignment;
- use iced_native::widget::{self, Tree};
- use iced_native::{
- event, layout, mouse, overlay, renderer, Clipboard, Color, Element,
- Event, Layout, Length, Point, Rectangle, Shell, Size, Widget,
- };
+ use iced::advanced::layout::{self, Layout};
+ use iced::advanced::overlay;
+ use iced::advanced::renderer;
+ use iced::advanced::widget::{self, Widget};
+ use iced::advanced::{self, Clipboard, Shell};
+ use iced::alignment::Alignment;
+ use iced::event;
+ use iced::mouse;
+ use iced::{Color, Element, Event, Length, Point, Rectangle, Size};
/// A widget that centers a modal element over some base element
pub struct Modal<'a, Message, Renderer> {
@@ -218,14 +261,17 @@ mod modal {
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Modal<'a, Message, Renderer>
where
- Renderer: iced_native::Renderer,
+ Renderer: advanced::Renderer,
Message: Clone,
{
- fn children(&self) -> Vec<Tree> {
- vec![Tree::new(&self.base), Tree::new(&self.modal)]
+ fn children(&self) -> Vec<widget::Tree> {
+ vec![
+ widget::Tree::new(&self.base),
+ widget::Tree::new(&self.modal),
+ ]
}
- fn diff(&self, tree: &mut Tree) {
+ fn diff(&self, tree: &mut widget::Tree) {
tree.diff_children(&[&self.base, &self.modal]);
}
@@ -247,10 +293,10 @@ mod modal {
fn on_event(
&mut self,
- state: &mut Tree,
+ state: &mut widget::Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -259,7 +305,7 @@ mod modal {
&mut state.children[0],
event,
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -268,12 +314,12 @@ mod modal {
fn draw(
&self,
- state: &Tree,
+ state: &widget::Tree,
renderer: &mut Renderer,
- theme: &<Renderer as iced_native::Renderer>::Theme,
+ theme: &<Renderer as advanced::Renderer>::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
) {
self.base.as_widget().draw(
@@ -282,14 +328,14 @@ mod modal {
theme,
style,
layout,
- cursor_position,
+ cursor,
viewport,
);
}
fn overlay<'b>(
&'b mut self,
- state: &'b mut Tree,
+ state: &'b mut widget::Tree,
layout: Layout<'_>,
_renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
@@ -306,16 +352,16 @@ mod modal {
fn mouse_interaction(
&self,
- state: &Tree,
+ state: &widget::Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.base.as_widget().mouse_interaction(
&state.children[0],
layout,
- cursor_position,
+ cursor,
viewport,
renderer,
)
@@ -323,7 +369,7 @@ mod modal {
fn operate(
&self,
- state: &mut Tree,
+ state: &mut widget::Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn widget::Operation<Message>,
@@ -339,7 +385,7 @@ mod modal {
struct Overlay<'a, 'b, Message, Renderer> {
content: &'b mut Element<'a, Message, Renderer>,
- tree: &'b mut Tree,
+ tree: &'b mut widget::Tree,
size: Size,
on_blur: Option<Message>,
}
@@ -347,7 +393,7 @@ mod modal {
impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, Renderer>
for Overlay<'a, 'b, Message, Renderer>
where
- Renderer: iced_native::Renderer,
+ Renderer: advanced::Renderer,
Message: Clone,
{
fn layout(
@@ -373,7 +419,7 @@ mod modal {
&mut self,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -385,7 +431,7 @@ mod modal {
mouse::Button::Left,
)) = &event
{
- if !content_bounds.contains(cursor_position) {
+ if !cursor.is_over(content_bounds) {
shell.publish(message.clone());
return event::Status::Captured;
}
@@ -396,7 +442,7 @@ mod modal {
self.tree,
event,
layout.children().next().unwrap(),
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -409,12 +455,12 @@ mod modal {
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
) {
renderer.fill_quad(
renderer::Quad {
bounds: layout.bounds(),
- border_radius: renderer::BorderRadius::from(0.0),
+ border_radius: Default::default(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
@@ -430,7 +476,7 @@ mod modal {
theme,
style,
layout.children().next().unwrap(),
- cursor_position,
+ cursor,
&layout.bounds(),
);
}
@@ -452,24 +498,36 @@ mod modal {
fn mouse_interaction(
&self,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.content.as_widget().mouse_interaction(
self.tree,
layout.children().next().unwrap(),
- cursor_position,
+ cursor,
viewport,
renderer,
)
}
+
+ fn overlay<'c>(
+ &'c mut self,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ ) -> Option<overlay::Element<'c, Message, Renderer>> {
+ self.content.as_widget_mut().overlay(
+ self.tree,
+ layout.children().next().unwrap(),
+ renderer,
+ )
+ }
}
impl<'a, Message, Renderer> From<Modal<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + iced_native::Renderer,
+ Renderer: 'a + advanced::Renderer,
Message: 'a + Clone,
{
fn from(modal: Modal<'a, Message, Renderer>) -> Self {
diff --git a/examples/modern_art/Cargo.toml b/examples/modern_art/Cargo.toml
deleted file mode 100644
index 4242d209..00000000
--- a/examples/modern_art/Cargo.toml
+++ /dev/null
@@ -1,11 +0,0 @@
-[package]
-name = "modern_art"
-version = "0.1.0"
-authors = ["Bingus <shankern@protonmail.com>"]
-edition = "2021"
-publish = false
-
-[dependencies]
-iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
-rand = "0.8.5"
-env_logger = "0.9"
diff --git a/examples/modern_art/src/main.rs b/examples/modern_art/src/main.rs
deleted file mode 100644
index 28ed3e21..00000000
--- a/examples/modern_art/src/main.rs
+++ /dev/null
@@ -1,142 +0,0 @@
-use iced::widget::canvas::{
- self, gradient::Location, gradient::Position, Cache, Canvas, Cursor, Frame,
- Geometry, Gradient,
-};
-use iced::{
- executor, Application, Color, Command, Element, Length, Point, Rectangle,
- Renderer, Settings, Size, Theme,
-};
-use rand::{thread_rng, Rng};
-
-fn main() -> iced::Result {
- env_logger::builder().format_timestamp(None).init();
-
- ModernArt::run(Settings {
- antialiasing: true,
- ..Settings::default()
- })
-}
-
-#[derive(Debug, Clone, Copy)]
-enum Message {}
-
-struct ModernArt {
- cache: Cache,
-}
-
-impl Application for ModernArt {
- type Executor = executor::Default;
- type Message = Message;
- type Theme = Theme;
- type Flags = ();
-
- fn new(_flags: Self::Flags) -> (Self, Command<Self::Message>) {
- (
- ModernArt {
- cache: Default::default(),
- },
- Command::none(),
- )
- }
-
- fn title(&self) -> String {
- String::from("Modern Art")
- }
-
- fn update(&mut self, _message: Message) -> Command<Message> {
- Command::none()
- }
-
- fn view(&self) -> Element<'_, Self::Message, Renderer<Self::Theme>> {
- Canvas::new(self)
- .width(Length::Fill)
- .height(Length::Fill)
- .into()
- }
-}
-
-impl<Message> canvas::Program<Message> for ModernArt {
- 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 num_squares = thread_rng().gen_range(0..1200);
-
- let mut i = 0;
- while i <= num_squares {
- generate_box(frame, bounds.size());
- i += 1;
- }
- });
-
- vec![geometry]
- }
-}
-
-fn random_direction() -> Location {
- match thread_rng().gen_range(0..8) {
- 0 => Location::TopLeft,
- 1 => Location::Top,
- 2 => Location::TopRight,
- 3 => Location::Right,
- 4 => Location::BottomRight,
- 5 => Location::Bottom,
- 6 => Location::BottomLeft,
- 7 => Location::Left,
- _ => Location::TopLeft,
- }
-}
-
-fn generate_box(frame: &mut Frame, bounds: Size) -> bool {
- let solid = rand::random::<bool>();
-
- let random_color = || -> Color {
- Color::from_rgb(
- thread_rng().gen_range(0.0..1.0),
- thread_rng().gen_range(0.0..1.0),
- thread_rng().gen_range(0.0..1.0),
- )
- };
-
- let gradient = |top_left: Point, size: Size| -> Gradient {
- let mut builder = Gradient::linear(Position::Relative {
- top_left,
- size,
- start: random_direction(),
- end: random_direction(),
- });
- let stops = thread_rng().gen_range(1..15u32);
-
- let mut i = 0;
- while i <= stops {
- builder = builder.add_stop(i as f32 / stops as f32, random_color());
- i += 1;
- }
-
- builder.build().unwrap()
- };
-
- let top_left = Point::new(
- thread_rng().gen_range(0.0..bounds.width),
- thread_rng().gen_range(0.0..bounds.height),
- );
-
- let size = Size::new(
- thread_rng().gen_range(50.0..200.0),
- thread_rng().gen_range(50.0..200.0),
- );
-
- if solid {
- frame.fill_rectangle(top_left, size, random_color());
- } else {
- frame.fill_rectangle(top_left, size, gradient(top_left, size));
- };
-
- solid
-}
diff --git a/examples/multitouch/src/main.rs b/examples/multitouch/src/main.rs
index f5faae0f..2830b78d 100644
--- a/examples/multitouch/src/main.rs
+++ b/examples/multitouch/src/main.rs
@@ -1,12 +1,13 @@
//! This example shows how to use touch events in `Canvas` to draw
//! a circle around each fingertip. This only works on touch-enabled
//! computers like Microsoft Surface.
+use iced::mouse;
use iced::widget::canvas::event;
use iced::widget::canvas::stroke::{self, Stroke};
-use iced::widget::canvas::{self, Canvas, Cursor, Geometry};
+use iced::widget::canvas::{self, Canvas, Geometry};
use iced::{
executor, touch, window, Application, Color, Command, Element, Length,
- Point, Rectangle, Settings, Subscription, Theme,
+ Point, Rectangle, Renderer, Settings, Subscription, Theme,
};
use std::collections::HashMap;
@@ -95,7 +96,7 @@ impl Application for Multitouch {
}
}
-impl canvas::Program<Message> for State {
+impl canvas::Program<Message, Renderer> for State {
type State = ();
fn update(
@@ -103,7 +104,7 @@ impl canvas::Program<Message> for State {
_state: &mut Self::State,
event: event::Event,
_bounds: Rectangle,
- _cursor: Cursor,
+ _cursor: mouse::Cursor,
) -> (event::Status, Option<Message>) {
match event {
event::Event::Touch(touch_event) => match touch_event {
@@ -125,11 +126,12 @@ impl canvas::Program<Message> for State {
fn draw(
&self,
_state: &Self::State,
+ renderer: &Renderer,
_theme: &Theme,
bounds: Rectangle,
- _cursor: Cursor,
+ _cursor: mouse::Cursor,
) -> Vec<Geometry> {
- let fingerweb = self.cache.draw(bounds.size(), |frame| {
+ let fingerweb = self.cache.draw(renderer, bounds.size(), |frame| {
if self.fingers.len() < 2 {
return;
}
diff --git a/examples/pane_grid/Cargo.toml b/examples/pane_grid/Cargo.toml
index dfd6dfa9..4c0bf072 100644
--- a/examples/pane_grid/Cargo.toml
+++ b/examples/pane_grid/Cargo.toml
@@ -6,6 +6,4 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["debug"] }
-iced_native = { path = "../../native" }
-iced_lazy = { path = "../../lazy" }
+iced = { path = "../..", features = ["debug", "lazy"] }
diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs
index c9f1376c..04896e20 100644
--- a/examples/pane_grid/src/main.rs
+++ b/examples/pane_grid/src/main.rs
@@ -1,14 +1,16 @@
use iced::alignment::{self, Alignment};
+use iced::event::{self, Event};
use iced::executor;
use iced::keyboard;
+use iced::subscription;
use iced::theme::{self, Theme};
use iced::widget::pane_grid::{self, PaneGrid};
-use iced::widget::{button, column, container, row, scrollable, text};
+use iced::widget::{
+ button, column, container, responsive, row, scrollable, text,
+};
use iced::{
Application, Color, Command, Element, Length, Settings, Size, Subscription,
};
-use iced_lazy::responsive;
-use iced_native::{event, subscription, Event};
pub fn main() -> iced::Result {
Example::run(Settings::default())
@@ -107,7 +109,7 @@ impl Application for Example {
pane,
target,
}) => {
- self.panes.swap(&pane, &target);
+ self.panes.drop(&pane, target);
}
Message::Dragged(_) => {}
Message::TogglePin(pane) => {
@@ -253,6 +255,7 @@ fn handle_hotkey(key_code: keyboard::KeyCode) -> Option<Message> {
}
}
+#[derive(Clone, Copy)]
struct Pane {
id: usize,
pub is_pinned: bool,
@@ -296,7 +299,7 @@ fn view_content<'a>(
)
]
.spacing(5)
- .max_width(150);
+ .max_width(160);
if total_panes > 1 && !is_pinned {
controls = controls.push(
diff --git a/examples/pick_list/src/main.rs b/examples/pick_list/src/main.rs
index 62a4ef88..21200621 100644
--- a/examples/pick_list/src/main.rs
+++ b/examples/pick_list/src/main.rs
@@ -61,8 +61,9 @@ impl Sandbox for Example {
}
}
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Language {
+ #[default]
Rust,
Elm,
Ruby,
@@ -84,12 +85,6 @@ impl Language {
];
}
-impl Default for Language {
- fn default() -> Language {
- Language::Rust
- }
-}
-
impl std::fmt::Display for Language {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
diff --git a/examples/qr_code/src/main.rs b/examples/qr_code/src/main.rs
index d8041745..867ebfa4 100644
--- a/examples/qr_code/src/main.rs
+++ b/examples/qr_code/src/main.rs
@@ -49,13 +49,11 @@ impl Sandbox for QRGenerator {
.size(70)
.style(Color::from([0.5, 0.5, 0.5]));
- let input = text_input(
- "Type the data of your QR code here...",
- &self.data,
- Message::DataChanged,
- )
- .size(30)
- .padding(15);
+ let input =
+ text_input("Type the data of your QR code here...", &self.data)
+ .on_input(Message::DataChanged)
+ .size(30)
+ .padding(15);
let mut content = column![title, input]
.width(700)
diff --git a/examples/screenshot/Cargo.toml b/examples/screenshot/Cargo.toml
new file mode 100644
index 00000000..b79300b7
--- /dev/null
+++ b/examples/screenshot/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "screenshot"
+version = "0.1.0"
+authors = ["Bingus <shankern@protonmail.com>"]
+edition = "2021"
+publish = false
+
+[dependencies]
+iced = { path = "../..", features = ["debug", "image", "advanced"] }
+image = { version = "0.24.6", features = ["png"]}
+env_logger = "0.10.0"
diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs
new file mode 100644
index 00000000..83824535
--- /dev/null
+++ b/examples/screenshot/src/main.rs
@@ -0,0 +1,320 @@
+use iced::alignment;
+use iced::keyboard::KeyCode;
+use iced::theme::{Button, Container};
+use iced::widget::{button, column, container, image, row, text, text_input};
+use iced::window::screenshot::{self, Screenshot};
+use iced::{
+ event, executor, keyboard, subscription, Alignment, Application, Command,
+ ContentFit, Element, Event, Length, Rectangle, Renderer, Subscription,
+ Theme,
+};
+
+use ::image as img;
+use ::image::ColorType;
+
+fn main() -> iced::Result {
+ env_logger::builder().format_timestamp(None).init();
+
+ Example::run(iced::Settings::default())
+}
+
+struct Example {
+ screenshot: Option<Screenshot>,
+ saved_png_path: Option<Result<String, PngError>>,
+ png_saving: bool,
+ crop_error: Option<screenshot::CropError>,
+ x_input_value: Option<u32>,
+ y_input_value: Option<u32>,
+ width_input_value: Option<u32>,
+ height_input_value: Option<u32>,
+}
+
+#[derive(Clone, Debug)]
+enum Message {
+ Crop,
+ Screenshot,
+ ScreenshotData(Screenshot),
+ Png,
+ PngSaved(Result<String, PngError>),
+ XInputChanged(Option<u32>),
+ YInputChanged(Option<u32>),
+ WidthInputChanged(Option<u32>),
+ HeightInputChanged(Option<u32>),
+}
+
+impl Application for Example {
+ type Executor = executor::Default;
+ type Message = Message;
+ type Theme = Theme;
+ type Flags = ();
+
+ fn new(_flags: Self::Flags) -> (Self, Command<Self::Message>) {
+ (
+ Example {
+ screenshot: None,
+ saved_png_path: None,
+ png_saving: false,
+ crop_error: None,
+ x_input_value: None,
+ y_input_value: None,
+ width_input_value: None,
+ height_input_value: None,
+ },
+ Command::none(),
+ )
+ }
+
+ fn title(&self) -> String {
+ "Screenshot".to_string()
+ }
+
+ fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
+ match message {
+ Message::Screenshot => {
+ return iced::window::screenshot(Message::ScreenshotData);
+ }
+ Message::ScreenshotData(screenshot) => {
+ self.screenshot = Some(screenshot);
+ }
+ Message::Png => {
+ if let Some(screenshot) = &self.screenshot {
+ self.png_saving = true;
+
+ return Command::perform(
+ save_to_png(screenshot.clone()),
+ Message::PngSaved,
+ );
+ }
+ }
+ Message::PngSaved(res) => {
+ self.png_saving = false;
+ self.saved_png_path = Some(res);
+ }
+ Message::XInputChanged(new_value) => {
+ self.x_input_value = new_value;
+ }
+ Message::YInputChanged(new_value) => {
+ self.y_input_value = new_value;
+ }
+ Message::WidthInputChanged(new_value) => {
+ self.width_input_value = new_value;
+ }
+ Message::HeightInputChanged(new_value) => {
+ self.height_input_value = new_value;
+ }
+ Message::Crop => {
+ if let Some(screenshot) = &self.screenshot {
+ let cropped = screenshot.crop(Rectangle::<u32> {
+ x: self.x_input_value.unwrap_or(0),
+ y: self.y_input_value.unwrap_or(0),
+ width: self.width_input_value.unwrap_or(0),
+ height: self.height_input_value.unwrap_or(0),
+ });
+
+ match cropped {
+ Ok(screenshot) => {
+ self.screenshot = Some(screenshot);
+ self.crop_error = None;
+ }
+ Err(crop_error) => {
+ self.crop_error = Some(crop_error);
+ }
+ }
+ }
+ }
+ }
+
+ Command::none()
+ }
+
+ fn view(&self) -> Element<'_, Self::Message, Renderer<Self::Theme>> {
+ let image: Element<Message> = if let Some(screenshot) = &self.screenshot
+ {
+ image(image::Handle::from_pixels(
+ screenshot.size.width,
+ screenshot.size.height,
+ screenshot.clone(),
+ ))
+ .content_fit(ContentFit::Contain)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .into()
+ } else {
+ text("Press the button to take a screenshot!").into()
+ };
+
+ let image = container(image)
+ .padding(10)
+ .style(Container::Box)
+ .width(Length::FillPortion(2))
+ .height(Length::Fill)
+ .center_x()
+ .center_y();
+
+ let crop_origin_controls = row![
+ text("X:")
+ .vertical_alignment(alignment::Vertical::Center)
+ .width(30),
+ numeric_input("0", self.x_input_value).map(Message::XInputChanged),
+ text("Y:")
+ .vertical_alignment(alignment::Vertical::Center)
+ .width(30),
+ numeric_input("0", self.y_input_value).map(Message::YInputChanged)
+ ]
+ .spacing(10)
+ .align_items(Alignment::Center);
+
+ let crop_dimension_controls = row![
+ text("W:")
+ .vertical_alignment(alignment::Vertical::Center)
+ .width(30),
+ numeric_input("0", self.width_input_value)
+ .map(Message::WidthInputChanged),
+ text("H:")
+ .vertical_alignment(alignment::Vertical::Center)
+ .width(30),
+ numeric_input("0", self.height_input_value)
+ .map(Message::HeightInputChanged)
+ ]
+ .spacing(10)
+ .align_items(Alignment::Center);
+
+ let mut crop_controls =
+ column![crop_origin_controls, crop_dimension_controls]
+ .spacing(10)
+ .align_items(Alignment::Center);
+
+ if let Some(crop_error) = &self.crop_error {
+ crop_controls = crop_controls
+ .push(text(format!("Crop error! \n{}", crop_error)));
+ }
+
+ let mut controls = column![
+ column![
+ button(centered_text("Screenshot!"))
+ .padding([10, 20, 10, 20])
+ .width(Length::Fill)
+ .on_press(Message::Screenshot),
+ if !self.png_saving {
+ button(centered_text("Save as png")).on_press_maybe(
+ self.screenshot.is_some().then(|| Message::Png),
+ )
+ } else {
+ button(centered_text("Saving...")).style(Button::Secondary)
+ }
+ .style(Button::Secondary)
+ .padding([10, 20, 10, 20])
+ .width(Length::Fill)
+ ]
+ .spacing(10),
+ column![
+ crop_controls,
+ button(centered_text("Crop"))
+ .on_press(Message::Crop)
+ .style(Button::Destructive)
+ .padding([10, 20, 10, 20])
+ .width(Length::Fill),
+ ]
+ .spacing(10)
+ .align_items(Alignment::Center),
+ ]
+ .spacing(40);
+
+ if let Some(png_result) = &self.saved_png_path {
+ let msg = match png_result {
+ Ok(path) => format!("Png saved as: {:?}!", path),
+ Err(msg) => {
+ format!("Png could not be saved due to:\n{:?}", msg)
+ }
+ };
+
+ controls = controls.push(text(msg));
+ }
+
+ let side_content = container(controls)
+ .align_x(alignment::Horizontal::Center)
+ .width(Length::FillPortion(1))
+ .height(Length::Fill)
+ .center_y()
+ .center_x();
+
+ let content = row![side_content, image]
+ .spacing(10)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .align_items(Alignment::Center);
+
+ container(content)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .padding(10)
+ .center_x()
+ .center_y()
+ .into()
+ }
+
+ fn subscription(&self) -> Subscription<Self::Message> {
+ subscription::events_with(|event, status| {
+ if let event::Status::Captured = status {
+ return None;
+ }
+
+ if let Event::Keyboard(keyboard::Event::KeyPressed {
+ key_code: KeyCode::F5,
+ ..
+ }) = event
+ {
+ Some(Message::Screenshot)
+ } else {
+ None
+ }
+ })
+ }
+}
+
+async fn save_to_png(screenshot: Screenshot) -> Result<String, PngError> {
+ let path = "screenshot.png".to_string();
+ img::save_buffer(
+ &path,
+ &screenshot.bytes,
+ screenshot.size.width,
+ screenshot.size.height,
+ ColorType::Rgba8,
+ )
+ .map(|_| path)
+ .map_err(|err| PngError(format!("{:?}", err)))
+}
+
+#[derive(Clone, Debug)]
+struct PngError(String);
+
+fn numeric_input(
+ placeholder: &str,
+ value: Option<u32>,
+) -> Element<'_, Option<u32>> {
+ text_input(
+ placeholder,
+ &value
+ .as_ref()
+ .map(ToString::to_string)
+ .unwrap_or_else(String::new),
+ )
+ .on_input(move |text| {
+ if text.is_empty() {
+ None
+ } else if let Ok(new_value) = text.parse() {
+ Some(new_value)
+ } else {
+ value
+ }
+ })
+ .width(40)
+ .into()
+}
+
+fn centered_text(content: &str) -> Element<'_, Message> {
+ text(content)
+ .width(Length::Fill)
+ .horizontal_alignment(alignment::Horizontal::Center)
+ .into()
+}
diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs
index a3ade54f..8c08d993 100644
--- a/examples/scrollable/src/main.rs
+++ b/examples/scrollable/src/main.rs
@@ -5,6 +5,7 @@ use iced::widget::{
};
use iced::{executor, theme, Alignment, Color};
use iced::{Application, Command, Element, Length, Settings, Theme};
+
use once_cell::sync::Lazy;
static SCROLLABLE_ID: Lazy<scrollable::Id> = Lazy::new(scrollable::Id::unique);
@@ -19,6 +20,7 @@ struct ScrollableDemo {
scrollbar_margin: u16,
scroller_width: u16,
current_scroll_offset: scrollable::RelativeOffset,
+ alignment: scrollable::Alignment,
}
#[derive(Debug, Clone, Eq, PartialEq, Copy)]
@@ -31,12 +33,13 @@ enum Direction {
#[derive(Debug, Clone)]
enum Message {
SwitchDirection(Direction),
+ AlignmentChanged(scrollable::Alignment),
ScrollbarWidthChanged(u16),
ScrollbarMarginChanged(u16),
ScrollerWidthChanged(u16),
ScrollToBeginning,
ScrollToEnd,
- Scrolled(scrollable::RelativeOffset),
+ Scrolled(scrollable::Viewport),
}
impl Application for ScrollableDemo {
@@ -53,6 +56,7 @@ impl Application for ScrollableDemo {
scrollbar_margin: 0,
scroller_width: 10,
current_scroll_offset: scrollable::RelativeOffset::START,
+ alignment: scrollable::Alignment::Start,
},
Command::none(),
)
@@ -73,6 +77,15 @@ impl Application for ScrollableDemo {
self.current_scroll_offset,
)
}
+ Message::AlignmentChanged(alignment) => {
+ self.current_scroll_offset = scrollable::RelativeOffset::START;
+ self.alignment = alignment;
+
+ scrollable::snap_to(
+ SCROLLABLE_ID.clone(),
+ self.current_scroll_offset,
+ )
+ }
Message::ScrollbarWidthChanged(width) => {
self.scrollbar_width = width;
@@ -104,8 +117,8 @@ impl Application for ScrollableDemo {
self.current_scroll_offset,
)
}
- Message::Scrolled(offset) => {
- self.current_scroll_offset = offset;
+ Message::Scrolled(viewport) => {
+ self.current_scroll_offset = viewport.relative_offset();
Command::none()
}
@@ -164,10 +177,33 @@ impl Application for ScrollableDemo {
.spacing(10)
.width(Length::Fill);
- let scroll_controls =
- row![scroll_slider_controls, scroll_orientation_controls]
- .spacing(20)
- .width(Length::Fill);
+ let scroll_alignment_controls = column(vec![
+ text("Scrollable alignment:").into(),
+ radio(
+ "Start",
+ scrollable::Alignment::Start,
+ Some(self.alignment),
+ Message::AlignmentChanged,
+ )
+ .into(),
+ radio(
+ "End",
+ scrollable::Alignment::End,
+ Some(self.alignment),
+ Message::AlignmentChanged,
+ )
+ .into(),
+ ])
+ .spacing(10)
+ .width(Length::Fill);
+
+ let scroll_controls = row![
+ scroll_slider_controls,
+ scroll_orientation_controls,
+ scroll_alignment_controls
+ ]
+ .spacing(20)
+ .width(Length::Fill);
let scroll_to_end_button = || {
button("Scroll to end")
@@ -199,12 +235,13 @@ impl Application for ScrollableDemo {
.spacing(40),
)
.height(Length::Fill)
- .vertical_scroll(
+ .direction(scrollable::Direction::Vertical(
Properties::new()
.width(self.scrollbar_width)
.margin(self.scrollbar_margin)
- .scroller_width(self.scroller_width),
- )
+ .scroller_width(self.scroller_width)
+ .alignment(self.alignment),
+ ))
.id(SCROLLABLE_ID.clone())
.on_scroll(Message::Scrolled),
Direction::Horizontal => scrollable(
@@ -223,12 +260,13 @@ impl Application for ScrollableDemo {
.spacing(40),
)
.height(Length::Fill)
- .horizontal_scroll(
+ .direction(scrollable::Direction::Horizontal(
Properties::new()
.width(self.scrollbar_width)
.margin(self.scrollbar_margin)
- .scroller_width(self.scroller_width),
- )
+ .scroller_width(self.scroller_width)
+ .alignment(self.alignment),
+ ))
.style(theme::Scrollable::custom(ScrollbarCustomStyle))
.id(SCROLLABLE_ID.clone())
.on_scroll(Message::Scrolled),
@@ -264,18 +302,18 @@ impl Application for ScrollableDemo {
.spacing(40),
)
.height(Length::Fill)
- .vertical_scroll(
- Properties::new()
- .width(self.scrollbar_width)
- .margin(self.scrollbar_margin)
- .scroller_width(self.scroller_width),
- )
- .horizontal_scroll(
- Properties::new()
+ .direction({
+ let properties = Properties::new()
.width(self.scrollbar_width)
.margin(self.scrollbar_margin)
- .scroller_width(self.scroller_width),
- )
+ .scroller_width(self.scroller_width)
+ .alignment(self.alignment);
+
+ scrollable::Direction::Both {
+ horizontal: properties,
+ vertical: properties,
+ }
+ })
.style(theme::Scrollable::Custom(Box::new(
ScrollbarCustomStyle,
)))
@@ -289,18 +327,13 @@ impl Application for ScrollableDemo {
}
Direction::Horizontal => {
progress_bar(0.0..=1.0, self.current_scroll_offset.x)
- .style(theme::ProgressBar::Custom(Box::new(
- ProgressBarCustomStyle,
- )))
+ .style(progress_bar_custom_style)
.into()
}
Direction::Multi => column![
progress_bar(0.0..=1.0, self.current_scroll_offset.y),
- progress_bar(0.0..=1.0, self.current_scroll_offset.x).style(
- theme::ProgressBar::Custom(Box::new(
- ProgressBarCustomStyle,
- ))
- )
+ progress_bar(0.0..=1.0, self.current_scroll_offset.x)
+ .style(progress_bar_custom_style)
]
.spacing(10)
.into(),
@@ -338,36 +371,44 @@ impl scrollable::StyleSheet for ScrollbarCustomStyle {
style.active(&theme::Scrollable::Default)
}
- fn hovered(&self, style: &Self::Style) -> Scrollbar {
- style.hovered(&theme::Scrollable::Default)
+ fn hovered(
+ &self,
+ style: &Self::Style,
+ is_mouse_over_scrollbar: bool,
+ ) -> Scrollbar {
+ style.hovered(&theme::Scrollable::Default, is_mouse_over_scrollbar)
}
- fn hovered_horizontal(&self, style: &Self::Style) -> Scrollbar {
- Scrollbar {
- background: style.active(&theme::Scrollable::default()).background,
- border_radius: 0.0,
- border_width: 0.0,
- border_color: Default::default(),
- scroller: Scroller {
- color: Color::from_rgb8(250, 85, 134),
- border_radius: 0.0,
+ fn hovered_horizontal(
+ &self,
+ style: &Self::Style,
+ is_mouse_over_scrollbar: bool,
+ ) -> Scrollbar {
+ if is_mouse_over_scrollbar {
+ Scrollbar {
+ background: style
+ .active(&theme::Scrollable::default())
+ .background,
+ border_radius: 0.0.into(),
border_width: 0.0,
border_color: Default::default(),
- },
+ scroller: Scroller {
+ color: Color::from_rgb8(250, 85, 134),
+ border_radius: 0.0.into(),
+ border_width: 0.0,
+ border_color: Default::default(),
+ },
+ }
+ } else {
+ self.active(style)
}
}
}
-struct ProgressBarCustomStyle;
-
-impl progress_bar::StyleSheet for ProgressBarCustomStyle {
- type Style = Theme;
-
- fn appearance(&self, style: &Self::Style) -> progress_bar::Appearance {
- progress_bar::Appearance {
- background: style.extended_palette().background.strong.color.into(),
- bar: Color::from_rgb8(250, 85, 134).into(),
- border_radius: 0.0,
- }
+fn progress_bar_custom_style(theme: &Theme) -> progress_bar::Appearance {
+ progress_bar::Appearance {
+ background: theme.extended_palette().background.strong.color.into(),
+ bar: Color::from_rgb8(250, 85, 134).into(),
+ border_radius: 0.0.into(),
}
}
diff --git a/examples/sierpinski_triangle/src/main.rs b/examples/sierpinski_triangle/src/main.rs
index 1d25d171..885d3c63 100644
--- a/examples/sierpinski_triangle/src/main.rs
+++ b/examples/sierpinski_triangle/src/main.rs
@@ -1,12 +1,13 @@
use std::fmt::Debug;
use iced::executor;
+use iced::mouse;
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,
+ Application, Color, Command, Length, Point, Rectangle, Renderer, Settings,
+ Size, Theme,
};
use rand::Rng;
@@ -105,14 +106,14 @@ impl canvas::Program<Message> for SierpinskiGraph {
_state: &mut Self::State,
event: Event,
bounds: Rectangle,
- cursor: canvas::Cursor,
+ cursor: mouse::Cursor,
) -> (event::Status, Option<Message>) {
- let cursor_position =
- if let Some(position) = cursor.position_in(&bounds) {
- position
- } else {
- return (event::Status::Ignored, None);
- };
+ let cursor_position = if let Some(position) = cursor.position_in(bounds)
+ {
+ position
+ } else {
+ return (event::Status::Ignored, None);
+ };
match event {
Event::Mouse(mouse_event) => {
@@ -134,11 +135,12 @@ impl canvas::Program<Message> for SierpinskiGraph {
fn draw(
&self,
_state: &Self::State,
+ renderer: &Renderer,
_theme: &Theme,
bounds: Rectangle,
- _cursor: canvas::Cursor,
+ _cursor: mouse::Cursor,
) -> Vec<canvas::Geometry> {
- let geom = self.cache.draw(bounds.size(), |frame| {
+ let geom = self.cache.draw(renderer, bounds.size(), |frame| {
frame.stroke(
&canvas::Path::rectangle(Point::ORIGIN, frame.size()),
canvas::Stroke::default(),
diff --git a/examples/solar_system/Cargo.toml b/examples/solar_system/Cargo.toml
index 835396b0..1a98a87e 100644
--- a/examples/solar_system/Cargo.toml
+++ b/examples/solar_system/Cargo.toml
@@ -7,4 +7,5 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
+env_logger = "0.10.0"
rand = "0.8.3"
diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs
index 9a4ee754..58d06206 100644
--- a/examples/solar_system/src/main.rs
+++ b/examples/solar_system/src/main.rs
@@ -8,20 +8,23 @@
//! [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::mouse;
use iced::theme::{self, Theme};
use iced::widget::canvas;
-use iced::widget::canvas::gradient::{self, Gradient};
+use iced::widget::canvas::gradient;
use iced::widget::canvas::stroke::{self, Stroke};
-use iced::widget::canvas::{Cursor, Path};
+use iced::widget::canvas::Path;
use iced::window;
use iced::{
- Application, Color, Command, Element, Length, Point, Rectangle, Settings,
- Size, Subscription, Vector,
+ Application, Color, Command, Element, Length, Point, Rectangle, Renderer,
+ Settings, Size, Subscription, Vector,
};
use std::time::Instant;
pub fn main() -> iced::Result {
+ env_logger::builder().format_timestamp(None).init();
+
SolarSystem::run(Settings {
antialiasing: true,
..Settings::default()
@@ -156,24 +159,26 @@ impl<Message> canvas::Program<Message> for State {
fn draw(
&self,
_state: &Self::State,
+ renderer: &Renderer,
_theme: &Theme,
bounds: Rectangle,
- _cursor: Cursor,
+ _cursor: mouse::Cursor,
) -> Vec<canvas::Geometry> {
use std::f32::consts::PI;
- let background = self.space_cache.draw(bounds.size(), |frame| {
- let stars = Path::new(|path| {
- for (p, size) in &self.stars {
- path.rectangle(*p, Size::new(*size, *size));
- }
- });
+ let background =
+ self.space_cache.draw(renderer, bounds.size(), |frame| {
+ let stars = Path::new(|path| {
+ for (p, size) in &self.stars {
+ path.rectangle(*p, Size::new(*size, *size));
+ }
+ });
- frame.translate(frame.center() - Point::ORIGIN);
- frame.fill(&stars, Color::WHITE);
- });
+ frame.translate(frame.center() - Point::ORIGIN);
+ frame.fill(&stars, Color::WHITE);
+ });
- let system = self.system_cache.draw(bounds.size(), |frame| {
+ let system = self.system_cache.draw(renderer, bounds.size(), |frame| {
let center = frame.center();
let sun = Path::circle(center, Self::SUN_RADIUS);
@@ -206,15 +211,12 @@ impl<Message> canvas::Program<Message> for State {
let earth = Path::circle(Point::ORIGIN, Self::EARTH_RADIUS);
- let earth_fill =
- Gradient::linear(gradient::Position::Absolute {
- start: Point::new(-Self::EARTH_RADIUS, 0.0),
- end: Point::new(Self::EARTH_RADIUS, 0.0),
- })
- .add_stop(0.2, Color::from_rgb(0.15, 0.50, 1.0))
- .add_stop(0.8, Color::from_rgb(0.0, 0.20, 0.47))
- .build()
- .expect("Build Earth fill gradient");
+ let earth_fill = gradient::Linear::new(
+ Point::new(-Self::EARTH_RADIUS, 0.0),
+ Point::new(Self::EARTH_RADIUS, 0.0),
+ )
+ .add_stop(0.2, Color::from_rgb(0.15, 0.50, 1.0))
+ .add_stop(0.8, Color::from_rgb(0.0, 0.20, 0.47));
frame.fill(&earth, earth_fill);
diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs
index 448c9792..f8a4c80a 100644
--- a/examples/styling/src/main.rs
+++ b/examples/styling/src/main.rs
@@ -90,13 +90,10 @@ impl Sandbox for Styling {
},
);
- let text_input = text_input(
- "Type something...",
- &self.input_value,
- Message::InputChanged,
- )
- .padding(10)
- .size(20);
+ let text_input = text_input("Type something...", &self.input_value)
+ .on_input(Message::InputChanged)
+ .padding(10)
+ .size(20);
let button = button("Submit")
.padding(10)
@@ -130,7 +127,9 @@ impl Sandbox for Styling {
let content = column![
choose_theme,
horizontal_rule(38),
- row![text_input, button].spacing(10),
+ row![text_input, button]
+ .spacing(10)
+ .align_items(Alignment::Center),
slider,
progress_bar,
row![
diff --git a/examples/toast/Cargo.toml b/examples/toast/Cargo.toml
index f1f986aa..f703572c 100644
--- a/examples/toast/Cargo.toml
+++ b/examples/toast/Cargo.toml
@@ -6,5 +6,4 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = [] }
-iced_native = { path = "../../native" }
+iced = { path = "../..", features = ["advanced"] }
diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs
index e74b3ee6..4282ddcf 100644
--- a/examples/toast/src/main.rs
+++ b/examples/toast/src/main.rs
@@ -1,10 +1,10 @@
+use iced::executor;
+use iced::keyboard;
+use iced::subscription::{self, Subscription};
use iced::widget::{
self, button, column, container, pick_list, row, slider, text, text_input,
};
-use iced::{
- executor, keyboard, subscription, Alignment, Application, Command, Element,
- Event, Length, Settings, Subscription,
-};
+use iced::{Alignment, Application, Command, Element, Event, Length, Settings};
use toast::{Status, Toast};
@@ -119,13 +119,15 @@ impl Application for App {
column![
subtitle(
"Title",
- text_input("", &self.editing.title, Message::Title)
+ text_input("", &self.editing.title)
+ .on_input(Message::Title)
.on_submit(Message::Add)
.into()
),
subtitle(
"Message",
- text_input("", &self.editing.body, Message::Body)
+ text_input("", &self.editing.body)
+ .on_input(Message::Body)
.on_submit(Message::Add)
.into()
),
@@ -176,17 +178,23 @@ mod toast {
use std::fmt;
use std::time::{Duration, Instant};
+ use iced::advanced;
+ use iced::advanced::layout::{self, Layout};
+ use iced::advanced::overlay;
+ use iced::advanced::renderer;
+ use iced::advanced::widget::{self, Operation, Tree};
+ use iced::advanced::{Clipboard, Shell, Widget};
+ use iced::event::{self, Event};
+ use iced::mouse;
use iced::theme;
use iced::widget::{
button, column, container, horizontal_rule, horizontal_space, row, text,
};
+ use iced::window;
use iced::{
Alignment, Element, Length, Point, Rectangle, Renderer, Size, Theme,
Vector,
};
- use iced_native::widget::{tree, Operation, Tree};
- use iced_native::{event, layout, mouse, overlay, renderer, window};
- use iced_native::{Clipboard, Event, Layout, Shell, Widget};
pub const DEFAULT_TIMEOUT: u64 = 5;
@@ -218,7 +226,7 @@ mod toast {
};
container::Appearance {
- background: pair.color.into(),
+ background: Some(pair.color.into()),
text_color: pair.text.into(),
..Default::default()
}
@@ -324,13 +332,13 @@ mod toast {
self.content.as_widget().layout(renderer, limits)
}
- fn tag(&self) -> tree::Tag {
+ fn tag(&self) -> widget::tree::Tag {
struct Marker(Vec<Instant>);
- iced_native::widget::tree::Tag::of::<Marker>()
+ widget::tree::Tag::of::<Marker>()
}
- fn state(&self) -> tree::State {
- iced_native::widget::tree::State::new(Vec::<Option<Instant>>::new())
+ fn state(&self) -> widget::tree::State {
+ widget::tree::State::new(Vec::<Option<Instant>>::new())
}
fn children(&self) -> Vec<Tree> {
@@ -388,7 +396,7 @@ mod toast {
state: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -397,7 +405,7 @@ mod toast {
&mut state.children[0],
event,
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -411,7 +419,7 @@ mod toast {
theme: &Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
) {
self.content.as_widget().draw(
@@ -420,7 +428,7 @@ mod toast {
theme,
style,
layout,
- cursor_position,
+ cursor,
viewport,
);
}
@@ -429,14 +437,14 @@ mod toast {
&self,
state: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.content.as_widget().mouse_interaction(
&state.children[0],
layout,
- cursor_position,
+ cursor,
viewport,
renderer,
)
@@ -515,7 +523,7 @@ mod toast {
&mut self,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -564,7 +572,7 @@ mod toast {
state,
event.clone(),
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
&mut local_shell,
@@ -584,10 +592,10 @@ mod toast {
fn draw(
&self,
renderer: &mut Renderer,
- theme: &<Renderer as iced_native::Renderer>::Theme,
+ theme: &<Renderer as advanced::Renderer>::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
) {
let viewport = layout.bounds();
@@ -598,13 +606,7 @@ mod toast {
.zip(layout.children())
{
child.as_widget().draw(
- state,
- renderer,
- theme,
- style,
- layout,
- cursor_position,
- &viewport,
+ state, renderer, theme, style, layout, cursor, &viewport,
);
}
}
@@ -613,7 +615,7 @@ mod toast {
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn iced_native::widget::Operation<Message>,
+ operation: &mut dyn widget::Operation<Message>,
) {
operation.container(None, &mut |operation| {
self.toasts
@@ -631,7 +633,7 @@ mod toast {
fn mouse_interaction(
&self,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
@@ -641,18 +643,19 @@ mod toast {
.zip(layout.children())
.map(|((child, state), layout)| {
child.as_widget().mouse_interaction(
- state,
- layout,
- cursor_position,
- viewport,
- renderer,
+ state, layout, cursor, viewport, renderer,
)
})
.max()
.unwrap_or_default()
}
- fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
+ fn is_over(
+ &self,
+ layout: Layout<'_>,
+ _renderer: &Renderer,
+ cursor_position: Point,
+ ) -> bool {
layout
.children()
.any(|layout| layout.bounds().contains(cursor_position))
diff --git a/examples/todos/fonts/icons.ttf b/examples/todos/fonts/icons.ttf
index 4498299d..7b65fd36 100644
--- a/examples/todos/fonts/icons.ttf
+++ b/examples/todos/fonts/icons.ttf
Binary files differ
diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs
index 6a87f58c..6ad7b4fb 100644
--- a/examples/todos/src/main.rs
+++ b/examples/todos/src/main.rs
@@ -1,5 +1,6 @@
use iced::alignment::{self, Alignment};
use iced::event::{self, Event};
+use iced::font::{self, Font};
use iced::keyboard::{self, KeyCode, Modifiers};
use iced::subscription;
use iced::theme::{self, Theme};
@@ -9,7 +10,7 @@ use iced::widget::{
};
use iced::window;
use iced::{Application, Element};
-use iced::{Color, Command, Font, Length, Settings, Subscription};
+use iced::{Color, Command, Length, Settings, Subscription};
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
@@ -44,6 +45,7 @@ struct State {
#[derive(Debug, Clone)]
enum Message {
Loaded(Result<SavedState, LoadError>),
+ FontLoaded(Result<(), font::Error>),
Saved(Result<(), SaveError>),
InputChanged(String),
CreateTask,
@@ -62,7 +64,11 @@ impl Application for Todos {
fn new(_flags: ()) -> (Todos, Command<Message>) {
(
Todos::Loading,
- Command::perform(SavedState::load(), Message::Loaded),
+ Command::batch(vec![
+ font::load(include_bytes!("../fonts/icons.ttf").as_slice())
+ .map(Message::FontLoaded),
+ Command::perform(SavedState::load(), Message::Loaded),
+ ]),
)
}
@@ -204,15 +210,12 @@ impl Application for Todos {
.style(Color::from([0.5, 0.5, 0.5]))
.horizontal_alignment(alignment::Horizontal::Center);
- 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 input = text_input("What needs to be done?", input_value)
+ .id(INPUT_ID.clone())
+ .on_input(Message::InputChanged)
+ .on_submit(Message::CreateTask)
+ .padding(15)
+ .size(30);
let controls = view_controls(tasks, *filter);
let filtered_tasks =
@@ -361,7 +364,8 @@ impl Task {
self.completed,
TaskMessage::Completed,
)
- .width(Length::Fill);
+ .width(Length::Fill)
+ .text_shaping(text::Shaping::Advanced);
row![
checkbox,
@@ -375,21 +379,23 @@ impl Task {
.into()
}
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);
+ let text_input =
+ text_input("Describe your task...", &self.description)
+ .id(Self::text_input_id(i))
+ .on_input(TaskMessage::DescriptionEdited)
+ .on_submit(TaskMessage::FinishEdition)
+ .padding(10);
row![
text_input,
- button(row![delete_icon(), "Delete"].spacing(10))
- .on_press(TaskMessage::Delete)
- .padding(10)
- .style(theme::Button::Destructive)
+ button(
+ row![delete_icon(), "Delete"]
+ .spacing(10)
+ .align_items(Alignment::Center)
+ )
+ .on_press(TaskMessage::Delete)
+ .padding(10)
+ .style(theme::Button::Destructive)
]
.spacing(20)
.align_items(Alignment::Center)
@@ -403,7 +409,7 @@ fn view_controls(tasks: &[Task], current_filter: Filter) -> Element<Message> {
let tasks_left = tasks.iter().filter(|task| !task.completed).count();
let filter_button = |label, filter, current_filter| {
- let label = text(label).size(16);
+ let label = text(label);
let button = button(label).style(if filter == current_filter {
theme::Button::Primary
@@ -420,8 +426,7 @@ fn view_controls(tasks: &[Task], current_filter: Filter) -> Element<Message> {
tasks_left,
if tasks_left == 1 { "task" } else { "tasks" }
))
- .width(Length::Fill)
- .size(16),
+ .width(Length::Fill),
row![
filter_button("All", Filter::All, current_filter),
filter_button("Active", Filter::Active, current_filter),
@@ -435,19 +440,16 @@ fn view_controls(tasks: &[Task], current_filter: Filter) -> Element<Message> {
.into()
}
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
+#[derive(
+ Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
+)]
pub enum Filter {
+ #[default]
All,
Active,
Completed,
}
-impl Default for Filter {
- fn default() -> Self {
- Filter::All
- }
-}
-
impl Filter {
fn matches(&self, task: &Task) -> bool {
match self {
@@ -485,17 +487,13 @@ fn empty_message(message: &str) -> Element<'_, Message> {
}
// Fonts
-const ICONS: Font = Font::External {
- name: "Icons",
- bytes: include_bytes!("../../todos/fonts/icons.ttf"),
-};
+const ICONS: Font = Font::with_name("Iced-Todos-Icons");
fn icon(unicode: char) -> Text<'static> {
text(unicode.to_string())
.font(ICONS)
.width(20)
.horizontal_alignment(alignment::Horizontal::Center)
- .size(20)
}
fn edit_icon() -> Text<'static> {
diff --git a/examples/tour/Cargo.toml b/examples/tour/Cargo.toml
index 39e83671..48471f2d 100644
--- a/examples/tour/Cargo.toml
+++ b/examples/tour/Cargo.toml
@@ -7,4 +7,4 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["image", "debug"] }
-env_logger = "0.8"
+env_logger = "0.10.0"
diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs
index de063d00..13bcd5ff 100644
--- a/examples/tour/src/main.rs
+++ b/examples/tour/src/main.rs
@@ -5,7 +5,7 @@ use iced::widget::{
scrollable, slider, text, text_input, toggler, vertical_space,
};
use iced::widget::{Button, Column, Container, Slider};
-use iced::{Color, Element, Length, Renderer, Sandbox, Settings};
+use iced::{Color, Element, Font, Length, Renderer, Sandbox, Settings};
pub fn main() -> iced::Result {
env_logger::init();
@@ -62,11 +62,8 @@ impl Sandbox for Tour {
controls = controls.push(horizontal_space(Length::Fill));
if steps.can_continue() {
- controls = controls.push(
- button("Next")
- .on_press(Message::NextPressed)
- .style(theme::Button::Primary),
- );
+ controls =
+ controls.push(button("Next").on_press(Message::NextPressed));
}
let content: Element<_> = column![
@@ -127,6 +124,7 @@ impl Steps {
Step::TextInput {
value: String::new(),
is_secure: false,
+ is_showing_icon: false,
},
Step::Debugger,
Step::End,
@@ -171,14 +169,32 @@ impl Steps {
enum Step {
Welcome,
- Slider { value: u8 },
- RowsAndColumns { layout: Layout, spacing: u16 },
- Text { size: u16, color: Color },
- Radio { selection: Option<Language> },
- Toggler { can_continue: bool },
- Image { width: u16 },
+ 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 },
+ TextInput {
+ value: String,
+ is_secure: bool,
+ is_showing_icon: bool,
+ },
Debugger,
End,
}
@@ -194,6 +210,7 @@ pub enum StepMessage {
ImageWidthChanged(u16),
InputChanged(String),
ToggleSecureInput(bool),
+ ToggleTextInputIcon(bool),
DebugToggled(bool),
TogglerChanged(bool),
}
@@ -256,6 +273,14 @@ impl<'a> Step {
*can_continue = value;
}
}
+ StepMessage::ToggleTextInputIcon(toggle) => {
+ if let Step::TextInput {
+ is_showing_icon, ..
+ } = self
+ {
+ *is_showing_icon = toggle
+ }
+ }
};
}
@@ -303,9 +328,11 @@ impl<'a> Step {
Self::rows_and_columns(*layout, *spacing)
}
Step::Scrollable => Self::scrollable(),
- Step::TextInput { value, is_secure } => {
- Self::text_input(value, *is_secure)
- }
+ Step::TextInput {
+ value,
+ is_secure,
+ is_showing_icon,
+ } => Self::text_input(value, *is_secure, *is_showing_icon),
Step::Debugger => Self::debugger(debug),
Step::End => Self::end(),
}
@@ -530,14 +557,25 @@ impl<'a> Step {
)
}
- 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);
+ fn text_input(
+ value: &str,
+ is_secure: bool,
+ is_showing_icon: bool,
+ ) -> Column<'a, StepMessage> {
+ let mut text_input = text_input("Type something to continue...", value)
+ .on_input(StepMessage::InputChanged)
+ .padding(10)
+ .size(30);
+
+ if is_showing_icon {
+ text_input = text_input.icon(text_input::Icon {
+ font: Font::default(),
+ code_point: '🚀',
+ size: Some(28.0),
+ spacing: 10.0,
+ side: text_input::Side::Right,
+ });
+ }
Self::container("Text input")
.push("Use a text input to ask for different kinds of information.")
@@ -551,6 +589,11 @@ impl<'a> Step {
is_secure,
StepMessage::ToggleSecureInput,
))
+ .push(checkbox(
+ "Show icon",
+ is_showing_icon,
+ StepMessage::ToggleTextInputIcon,
+ ))
.push(
"A text input produces a message every time it changes. It is \
very easy to keep track of its contents:",
diff --git a/examples/url_handler/Cargo.toml b/examples/url_handler/Cargo.toml
index 63c7ec27..4dcff92d 100644
--- a/examples/url_handler/Cargo.toml
+++ b/examples/url_handler/Cargo.toml
@@ -7,4 +7,3 @@ publish = false
[dependencies]
iced = { path = "../.." }
-iced_native = { path = "../../native" }
diff --git a/examples/url_handler/src/main.rs b/examples/url_handler/src/main.rs
index 3257b519..f63fa06a 100644
--- a/examples/url_handler/src/main.rs
+++ b/examples/url_handler/src/main.rs
@@ -1,12 +1,10 @@
+use iced::event::{Event, MacOS, PlatformSpecific};
use iced::executor;
+use iced::subscription;
use iced::widget::{container, text};
use iced::{
Application, Command, Element, Length, Settings, Subscription, Theme,
};
-use iced_native::{
- event::{MacOS, PlatformSpecific},
- Event,
-};
pub fn main() -> iced::Result {
App::run(Settings::default())
@@ -19,7 +17,7 @@ struct App {
#[derive(Debug, Clone)]
enum Message {
- EventOccurred(iced_native::Event),
+ EventOccurred(Event),
}
impl Application for App {
@@ -52,7 +50,7 @@ impl Application for App {
}
fn subscription(&self) -> Subscription<Message> {
- iced_native::subscription::events().map(Message::EventOccurred)
+ subscription::events().map(Message::EventOccurred)
}
fn view(&self) -> Element<Message> {
diff --git a/examples/websocket/Cargo.toml b/examples/websocket/Cargo.toml
index c25f067b..03b240c6 100644
--- a/examples/websocket/Cargo.toml
+++ b/examples/websocket/Cargo.toml
@@ -7,8 +7,6 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["tokio", "debug"] }
-iced_native = { path = "../../native" }
-iced_futures = { path = "../../futures" }
once_cell = "1.15"
[dependencies.async-tungstenite]
diff --git a/examples/websocket/src/echo.rs b/examples/websocket/src/echo.rs
index e74768a6..281ed4bd 100644
--- a/examples/websocket/src/echo.rs
+++ b/examples/websocket/src/echo.rs
@@ -1,7 +1,7 @@
pub mod server;
-use iced_futures::futures;
-use iced_native::subscription::{self, Subscription};
+use iced::futures;
+use iced::subscription::{self, Subscription};
use futures::channel::mpsc;
use futures::sink::SinkExt;
@@ -13,63 +13,67 @@ use std::fmt;
pub fn connect() -> Subscription<Event> {
struct Connect;
- subscription::unfold(
+ subscription::channel(
std::any::TypeId::of::<Connect>(),
- State::Disconnected,
- |state| async move {
- match state {
- State::Disconnected => {
- const ECHO_SERVER: &str = "ws://localhost:3030";
-
- match async_tungstenite::tokio::connect_async(ECHO_SERVER)
+ 100,
+ |mut output| async move {
+ let mut state = State::Disconnected;
+
+ loop {
+ match &mut state {
+ State::Disconnected => {
+ const ECHO_SERVER: &str = "ws://127.0.0.1:3030";
+
+ match async_tungstenite::tokio::connect_async(
+ ECHO_SERVER,
+ )
.await
- {
- Ok((websocket, _)) => {
- let (sender, receiver) = mpsc::channel(100);
-
- (
- Some(Event::Connected(Connection(sender))),
- State::Connected(websocket, receiver),
- )
- }
- Err(_) => {
- tokio::time::sleep(
- tokio::time::Duration::from_secs(1),
- )
- .await;
+ {
+ Ok((websocket, _)) => {
+ let (sender, receiver) = mpsc::channel(100);
+
+ let _ = output
+ .send(Event::Connected(Connection(sender)))
+ .await;
- (Some(Event::Disconnected), State::Disconnected)
+ state = State::Connected(websocket, receiver);
+ }
+ Err(_) => {
+ tokio::time::sleep(
+ tokio::time::Duration::from_secs(1),
+ )
+ .await;
+
+ let _ = output.send(Event::Disconnected).await;
+ }
}
}
- }
- State::Connected(mut websocket, mut input) => {
- let mut fused_websocket = websocket.by_ref().fuse();
-
- futures::select! {
- received = fused_websocket.select_next_some() => {
- match received {
- Ok(tungstenite::Message::Text(message)) => {
- (
- Some(Event::MessageReceived(Message::User(message))),
- State::Connected(websocket, input)
- )
- }
- Ok(_) => {
- (None, State::Connected(websocket, input))
- }
- Err(_) => {
- (Some(Event::Disconnected), State::Disconnected)
+ State::Connected(websocket, input) => {
+ let mut fused_websocket = websocket.by_ref().fuse();
+
+ futures::select! {
+ received = fused_websocket.select_next_some() => {
+ match received {
+ Ok(tungstenite::Message::Text(message)) => {
+ let _ = output.send(Event::MessageReceived(Message::User(message))).await;
+ }
+ Err(_) => {
+ let _ = output.send(Event::Disconnected).await;
+
+ state = State::Disconnected;
+ }
+ Ok(_) => continue,
}
}
- }
- message = input.select_next_some() => {
- let result = websocket.send(tungstenite::Message::Text(message.to_string())).await;
+ message = input.select_next_some() => {
+ let result = websocket.send(tungstenite::Message::Text(message.to_string())).await;
+
+ if result.is_err() {
+ let _ = output.send(Event::Disconnected).await;
- if result.is_ok() {
- (None, State::Connected(websocket, input))
- } else {
- (Some(Event::Disconnected), State::Disconnected)
+ state = State::Disconnected;
+ }
}
}
}
diff --git a/examples/websocket/src/echo/server.rs b/examples/websocket/src/echo/server.rs
index dd234984..168a635e 100644
--- a/examples/websocket/src/echo/server.rs
+++ b/examples/websocket/src/echo/server.rs
@@ -1,4 +1,4 @@
-use iced_futures::futures;
+use iced::futures;
use futures::channel::mpsc;
use futures::{SinkExt, StreamExt};
diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs
index e617b8ce..920189f5 100644
--- a/examples/websocket/src/main.rs
+++ b/examples/websocket/src/main.rs
@@ -125,12 +125,9 @@ impl Application for WebSocket {
};
let new_message_input = {
- let mut input = text_input(
- "Type a message...",
- &self.new_message,
- Message::NewMessageChanged,
- )
- .padding(10);
+ let mut input = text_input("Type a message...", &self.new_message)
+ .on_input(Message::NewMessageChanged)
+ .padding(10);
let mut button = button(
text("Send")