summaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to 'examples')
-rw-r--r--examples/README.md5
-rw-r--r--examples/bezier_tool/src/main.rs75
-rw-r--r--examples/clock/src/main.rs10
-rw-r--r--examples/color_palette/src/main.rs2
-rw-r--r--examples/custom_widget/src/main.rs39
-rw-r--r--examples/download_progress/Cargo.toml4
-rw-r--r--examples/download_progress/README.md2
-rw-r--r--examples/download_progress/src/download.rs46
-rw-r--r--examples/download_progress/src/main.rs205
-rw-r--r--examples/events/src/main.rs53
-rw-r--r--examples/game_of_life/Cargo.toml2
-rw-r--r--examples/game_of_life/README.md4
-rw-r--r--examples/game_of_life/src/main.rs132
-rw-r--r--examples/game_of_life/src/preset.rs13
-rw-r--r--examples/game_of_life/src/style.rs19
-rw-r--r--examples/geometry/src/main.rs5
-rw-r--r--examples/integration/Cargo.toml2
-rw-r--r--examples/integration/src/controls.rs11
-rw-r--r--examples/integration/src/main.rs145
-rw-r--r--examples/integration/src/scene.rs47
-rw-r--r--examples/menu/Cargo.toml10
-rw-r--r--examples/menu/src/main.rs117
-rw-r--r--examples/pane_grid/Cargo.toml3
-rw-r--r--examples/pane_grid/src/main.rs246
-rw-r--r--examples/pick_list/src/main.rs9
-rw-r--r--examples/pokedex/Cargo.toml2
-rw-r--r--examples/pokedex/src/main.rs26
-rw-r--r--examples/qr_code/Cargo.toml9
-rw-r--r--examples/qr_code/README.md18
-rw-r--r--examples/qr_code/src/main.rs81
-rw-r--r--examples/scrollable/Cargo.toml9
-rw-r--r--examples/scrollable/README.md15
-rw-r--r--examples/scrollable/screenshot.pngbin0 -> 148253 bytes
-rw-r--r--examples/scrollable/src/main.rs255
-rw-r--r--examples/scrollable/src/style.rs190
-rw-r--r--examples/solar_system/Cargo.toml2
-rw-r--r--examples/solar_system/src/main.rs18
-rw-r--r--examples/stopwatch/Cargo.toml2
-rw-r--r--examples/stopwatch/src/main.rs12
-rw-r--r--examples/styling/src/main.rs111
-rw-r--r--examples/todos/Cargo.toml2
-rw-r--r--examples/todos/src/main.rs24
-rw-r--r--examples/tooltip/Cargo.toml9
-rw-r--r--examples/tooltip/README.md14
-rw-r--r--examples/tooltip/src/main.rs138
-rw-r--r--examples/tour/Cargo.toml2
-rw-r--r--examples/tour/src/main.rs36
-rw-r--r--examples/url_handler/Cargo.toml10
-rw-r--r--examples/url_handler/src/main.rs73
49 files changed, 1843 insertions, 421 deletions
diff --git a/examples/README.md b/examples/README.md
index 34a916a1..10c28cf5 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -103,6 +103,7 @@ A bunch of simpler examples exist:
- [`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].
- [`progress_bar`](progress_bar), a simple progress bar that can be filled by using a slider.
+- [`scrollable`](scrollable), a showcase of the various scrollbar width options.
- [`solar_system`](solar_system), an animated solar system drawn using the `Canvas` widget and showcasing how to compose different transforms.
- [`stopwatch`](stopwatch), a watch with start/stop and reset buttons showcasing how to listen to time.
- [`svg`](svg), an application that renders the [Ghostscript Tiger] by leveraging the `Svg` widget.
@@ -117,7 +118,7 @@ cargo run --package <example>
[Ghostscript Tiger]: https://commons.wikimedia.org/wiki/File:Ghostscript_Tiger.svg
## [Coffee]
-Since [Iced was born in May], it has been powering the user interfaces in
+Since [Iced was born in May 2019], it has been powering the user interfaces in
[Coffee], an experimental 2D game engine.
@@ -127,6 +128,6 @@ Since [Iced was born in May], it has been powering the user interfaces in
</a>
</div>
-[Iced was born in May]: https://github.com/hecrj/coffee/pull/35
+[Iced was born in May 2019]: https://github.com/hecrj/coffee/pull/35
[`ui` module]: https://docs.rs/coffee/0.3.2/coffee/ui/index.html
[Coffee]: https://github.com/hecrj/coffee
diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs
index cb5247aa..97832e01 100644
--- a/examples/bezier_tool/src/main.rs
+++ b/examples/bezier_tool/src/main.rs
@@ -69,7 +69,8 @@ impl Sandbox for Example {
mod bezier {
use iced::{
- canvas::{self, Canvas, Cursor, Event, Frame, Geometry, Path, Stroke},
+ canvas::event::{self, Event},
+ canvas::{self, Canvas, Cursor, Frame, Geometry, Path, Stroke},
mouse, Element, Length, Point, Rectangle,
};
@@ -109,41 +110,51 @@ mod bezier {
event: Event,
bounds: Rectangle,
cursor: Cursor,
- ) -> Option<Curve> {
- let cursor_position = cursor.position_in(&bounds)?;
+ ) -> (event::Status, Option<Curve>) {
+ let cursor_position =
+ if let Some(position) = cursor.position_in(&bounds) {
+ position
+ } else {
+ return (event::Status::Ignored, None);
+ };
match event {
- Event::Mouse(mouse_event) => match mouse_event {
- mouse::Event::ButtonPressed(mouse::Button::Left) => {
- match self.state.pending {
- None => {
- self.state.pending = Some(Pending::One {
- from: cursor_position,
- });
- None
- }
- Some(Pending::One { from }) => {
- self.state.pending = Some(Pending::Two {
- from,
- to: cursor_position,
- });
-
- None
- }
- Some(Pending::Two { from, to }) => {
- self.state.pending = None;
-
- Some(Curve {
- from,
- to,
- control: cursor_position,
- })
+ Event::Mouse(mouse_event) => {
+ let message = match mouse_event {
+ mouse::Event::ButtonPressed(mouse::Button::Left) => {
+ match self.state.pending {
+ None => {
+ self.state.pending = Some(Pending::One {
+ from: cursor_position,
+ });
+
+ None
+ }
+ Some(Pending::One { from }) => {
+ self.state.pending = Some(Pending::Two {
+ from,
+ to: cursor_position,
+ });
+
+ None
+ }
+ Some(Pending::Two { from, to }) => {
+ self.state.pending = None;
+
+ Some(Curve {
+ from,
+ to,
+ control: cursor_position,
+ })
+ }
}
}
- }
- _ => None,
- },
- _ => None,
+ _ => None,
+ };
+
+ (event::Status::Captured, message)
+ }
+ _ => (event::Status::Ignored, None),
}
}
diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs
index b317ac00..9bcc827b 100644
--- a/examples/clock/src/main.rs
+++ b/examples/clock/src/main.rs
@@ -1,7 +1,7 @@
use iced::{
canvas::{self, Cache, Canvas, Cursor, Geometry, LineCap, Path, Stroke},
- executor, time, Application, Color, Command, Container, Element, Length,
- Point, Rectangle, Settings, Subscription, Vector,
+ executor, time, Application, Clipboard, Color, Command, Container, Element,
+ Length, Point, Rectangle, Settings, Subscription, Vector,
};
pub fn main() -> iced::Result {
@@ -40,7 +40,11 @@ impl Application for Clock {
String::from("Clock - Iced")
}
- fn update(&mut self, message: Message) -> Command<Message> {
+ fn update(
+ &mut self,
+ message: Message,
+ _clipboard: &mut Clipboard,
+ ) -> Command<Message> {
match message {
Message::Tick(local_time) => {
let now = local_time;
diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs
index fec5f48c..bb2c61cb 100644
--- a/examples/color_palette/src/main.rs
+++ b/examples/color_palette/src/main.rs
@@ -284,7 +284,7 @@ impl<C: 'static + ColorSpace + Copy> ColorPicker<C> {
let [s1, s2, s3] = &mut self.sliders;
let [cr1, cr2, cr3] = C::COMPONENT_RANGES;
- fn slider<C>(
+ fn slider<C: Clone>(
state: &mut slider::State,
range: RangeInclusive<f64>,
component: f32,
diff --git a/examples/custom_widget/src/main.rs b/examples/custom_widget/src/main.rs
index 0eba1cd0..36f468c7 100644
--- a/examples/custom_widget/src/main.rs
+++ b/examples/custom_widget/src/main.rs
@@ -12,15 +12,15 @@ mod circle {
use iced_graphics::{Backend, Defaults, Primitive, Renderer};
use iced_native::{
layout, mouse, Background, Color, Element, Hasher, Layout, Length,
- Point, Size, Widget,
+ Point, Rectangle, Size, Widget,
};
pub struct Circle {
- radius: u16,
+ radius: f32,
}
impl Circle {
- pub fn new(radius: u16) -> Self {
+ pub fn new(radius: f32) -> Self {
Self { radius }
}
}
@@ -42,16 +42,13 @@ mod circle {
_renderer: &Renderer<B>,
_limits: &layout::Limits,
) -> layout::Node {
- layout::Node::new(Size::new(
- f32::from(self.radius) * 2.0,
- f32::from(self.radius) * 2.0,
- ))
+ layout::Node::new(Size::new(self.radius * 2.0, self.radius * 2.0))
}
fn hash_layout(&self, state: &mut Hasher) {
use std::hash::Hash;
- self.radius.hash(state);
+ self.radius.to_bits().hash(state);
}
fn draw(
@@ -60,13 +57,14 @@ mod circle {
_defaults: &Defaults,
layout: Layout<'_>,
_cursor_position: Point,
+ _viewport: &Rectangle,
) -> (Primitive, mouse::Interaction) {
(
Primitive::Quad {
bounds: layout.bounds(),
background: Background::Color(Color::BLACK),
border_radius: self.radius,
- border_width: 0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
},
mouse::Interaction::default(),
@@ -95,7 +93,7 @@ pub fn main() -> iced::Result {
}
struct Example {
- radius: u16,
+ radius: f32,
slider: slider::State,
}
@@ -109,7 +107,7 @@ impl Sandbox for Example {
fn new() -> Self {
Example {
- radius: 50,
+ radius: 50.0,
slider: slider::State::new(),
}
}
@@ -121,7 +119,7 @@ impl Sandbox for Example {
fn update(&mut self, message: Message) {
match message {
Message::RadiusChanged(radius) => {
- self.radius = radius.round() as u16;
+ self.radius = radius;
}
}
}
@@ -133,13 +131,16 @@ impl Sandbox for Example {
.max_width(500)
.align_items(Align::Center)
.push(Circle::new(self.radius))
- .push(Text::new(format!("Radius: {}", self.radius.to_string())))
- .push(Slider::new(
- &mut self.slider,
- 1.0..=100.0,
- f32::from(self.radius),
- Message::RadiusChanged,
- ));
+ .push(Text::new(format!("Radius: {:.2}", self.radius)))
+ .push(
+ Slider::new(
+ &mut self.slider,
+ 1.0..=100.0,
+ self.radius,
+ Message::RadiusChanged,
+ )
+ .step(0.01),
+ );
Container::new(content)
.width(Length::Fill)
diff --git a/examples/download_progress/Cargo.toml b/examples/download_progress/Cargo.toml
index 34e6a132..d3c578b1 100644
--- a/examples/download_progress/Cargo.toml
+++ b/examples/download_progress/Cargo.toml
@@ -1,7 +1,7 @@
[package]
name = "download_progress"
version = "0.1.0"
-authors = ["Songtronix <contact@songtronix.com>"]
+authors = ["Songtronix <contact@songtronix.com>", "Folyd <lyshuhow@gmail.com>"]
edition = "2018"
publish = false
@@ -9,4 +9,4 @@ publish = false
iced = { path = "../..", features = ["tokio"] }
iced_native = { path = "../../native" }
iced_futures = { path = "../../futures" }
-reqwest = "0.10"
+reqwest = "0.11"
diff --git a/examples/download_progress/README.md b/examples/download_progress/README.md
index c606c5f9..7999ce94 100644
--- a/examples/download_progress/README.md
+++ b/examples/download_progress/README.md
@@ -1,6 +1,6 @@
## Download progress
-A basic application that asynchronously downloads a dummy file of 100 MB and tracks the download progress.
+A basic application that asynchronously downloads multiple dummy files of 100 MB and tracks the download progress.
The example implements a custom `Subscription` in the __[`download`](src/download.rs)__ module. This subscription downloads and produces messages that can be used to keep track of its progress.
diff --git a/examples/download_progress/src/download.rs b/examples/download_progress/src/download.rs
index f46a01f7..08805f13 100644
--- a/examples/download_progress/src/download.rs
+++ b/examples/download_progress/src/download.rs
@@ -1,37 +1,46 @@
use iced_futures::futures;
+use std::hash::{Hash, Hasher};
// Just a little utility function
-pub fn file<T: ToString>(url: T) -> iced::Subscription<Progress> {
+pub fn file<I: 'static + Hash + Copy + Send, T: ToString>(
+ id: I,
+ url: T,
+) -> iced::Subscription<(I, Progress)> {
iced::Subscription::from_recipe(Download {
+ id,
url: url.to_string(),
})
}
-pub struct Download {
+pub struct Download<I> {
+ id: I,
url: String,
}
// Make sure iced can use our download stream
-impl<H, I> iced_native::subscription::Recipe<H, I> for Download
+impl<H, I, T> iced_native::subscription::Recipe<H, I> for Download<T>
where
- H: std::hash::Hasher,
+ T: 'static + Hash + Copy + Send,
+ H: Hasher,
{
- type Output = Progress;
+ type Output = (T, Progress);
fn hash(&self, state: &mut H) {
- use std::hash::Hash;
+ struct Marker;
+ std::any::TypeId::of::<Marker>().hash(state);
- std::any::TypeId::of::<Self>().hash(state);
- self.url.hash(state);
+ self.id.hash(state);
}
fn stream(
self: Box<Self>,
_input: futures::stream::BoxStream<'static, I>,
) -> futures::stream::BoxStream<'static, Self::Output> {
+ let id = self.id;
+
Box::pin(futures::stream::unfold(
State::Ready(self.url),
- |state| async move {
+ move |state| async move {
match state {
State::Ready(url) => {
let response = reqwest::get(&url).await;
@@ -40,7 +49,7 @@ where
Ok(response) => {
if let Some(total) = response.content_length() {
Some((
- Progress::Started,
+ (id, Progress::Started),
State::Downloading {
response,
total,
@@ -48,11 +57,14 @@ where
},
))
} else {
- Some((Progress::Errored, State::Finished))
+ Some((
+ (id, Progress::Errored),
+ State::Finished,
+ ))
}
}
Err(_) => {
- Some((Progress::Errored, State::Finished))
+ Some(((id, Progress::Errored), State::Finished))
}
}
}
@@ -68,7 +80,7 @@ where
(downloaded as f32 / total as f32) * 100.0;
Some((
- Progress::Advanced(percentage),
+ (id, Progress::Advanced(percentage)),
State::Downloading {
response,
total,
@@ -76,8 +88,12 @@ where
},
))
}
- Ok(None) => Some((Progress::Finished, State::Finished)),
- Err(_) => Some((Progress::Errored, State::Finished)),
+ Ok(None) => {
+ Some(((id, Progress::Finished), State::Finished))
+ }
+ Err(_) => {
+ Some(((id, Progress::Errored), State::Finished))
+ }
},
State::Finished => {
// We do not let the stream die, as it would start a
diff --git a/examples/download_progress/src/main.rs b/examples/download_progress/src/main.rs
index 77b01354..6f844e66 100644
--- a/examples/download_progress/src/main.rs
+++ b/examples/download_progress/src/main.rs
@@ -1,6 +1,6 @@
use iced::{
- button, executor, Align, Application, Button, Column, Command, Container,
- Element, Length, ProgressBar, Settings, Subscription, Text,
+ button, executor, Align, Application, Button, Clipboard, Column, Command,
+ Container, Element, Length, ProgressBar, Settings, Subscription, Text,
};
mod download;
@@ -10,17 +10,17 @@ pub fn main() -> iced::Result {
}
#[derive(Debug)]
-enum Example {
- Idle { button: button::State },
- Downloading { progress: f32 },
- Finished { button: button::State },
- Errored { button: button::State },
+struct Example {
+ downloads: Vec<Download>,
+ last_id: usize,
+ add: button::State,
}
#[derive(Debug, Clone)]
pub enum Message {
- Download,
- DownloadProgressed(download::Progress),
+ Add,
+ Download(usize),
+ DownloadProgressed((usize, download::Progress)),
}
impl Application for Example {
@@ -30,8 +30,10 @@ impl Application for Example {
fn new(_flags: ()) -> (Example, Command<Message>) {
(
- Example::Idle {
- button: button::State::new(),
+ Example {
+ downloads: vec![Download::new(0)],
+ last_id: 0,
+ add: button::State::new(),
},
Command::none(),
)
@@ -41,104 +43,177 @@ impl Application for Example {
String::from("Download progress - Iced")
}
- fn update(&mut self, message: Message) -> Command<Message> {
+ fn update(
+ &mut self,
+ message: Message,
+ _clipboard: &mut Clipboard,
+ ) -> Command<Message> {
match message {
- Message::Download => match self {
- Example::Idle { .. }
- | Example::Finished { .. }
- | Example::Errored { .. } => {
- *self = Example::Downloading { progress: 0.0 };
+ Message::Add => {
+ self.last_id = self.last_id + 1;
+
+ self.downloads.push(Download::new(self.last_id));
+ }
+ Message::Download(index) => {
+ if let Some(download) = self.downloads.get_mut(index) {
+ download.start();
}
- _ => {}
- },
- Message::DownloadProgressed(message) => match self {
- Example::Downloading { progress } => match message {
- download::Progress::Started => {
- *progress = 0.0;
- }
- download::Progress::Advanced(percentage) => {
- *progress = percentage;
- }
- download::Progress::Finished => {
- *self = Example::Finished {
- button: button::State::new(),
- }
- }
- download::Progress::Errored => {
- *self = Example::Errored {
- button: button::State::new(),
- };
- }
- },
- _ => {}
- },
+ }
+ Message::DownloadProgressed((id, progress)) => {
+ if let Some(download) =
+ self.downloads.iter_mut().find(|download| download.id == id)
+ {
+ download.progress(progress);
+ }
+ }
};
Command::none()
}
fn subscription(&self) -> Subscription<Message> {
- match self {
- Example::Downloading { .. } => {
- download::file("https://speed.hetzner.de/100MB.bin")
+ Subscription::batch(self.downloads.iter().map(Download::subscription))
+ }
+
+ fn view(&mut self) -> Element<Message> {
+ let downloads = self
+ .downloads
+ .iter_mut()
+ .fold(Column::new().spacing(20), |column, download| {
+ column.push(download.view())
+ })
+ .push(
+ Button::new(&mut self.add, Text::new("Add another download"))
+ .on_press(Message::Add)
+ .padding(10),
+ )
+ .align_items(Align::End);
+
+ Container::new(downloads)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .center_x()
+ .center_y()
+ .padding(20)
+ .into()
+ }
+}
+
+#[derive(Debug)]
+struct Download {
+ id: usize,
+ state: State,
+}
+
+#[derive(Debug)]
+enum State {
+ Idle { button: button::State },
+ Downloading { progress: f32 },
+ Finished { button: button::State },
+ Errored { button: button::State },
+}
+
+impl Download {
+ pub fn new(id: usize) -> Self {
+ Download {
+ id,
+ state: State::Idle {
+ button: button::State::new(),
+ },
+ }
+ }
+
+ pub fn start(&mut self) {
+ match self.state {
+ State::Idle { .. }
+ | State::Finished { .. }
+ | State::Errored { .. } => {
+ self.state = State::Downloading { progress: 0.0 };
+ }
+ _ => {}
+ }
+ }
+
+ pub fn progress(&mut self, new_progress: download::Progress) {
+ match &mut self.state {
+ State::Downloading { progress } => match new_progress {
+ download::Progress::Started => {
+ *progress = 0.0;
+ }
+ download::Progress::Advanced(percentage) => {
+ *progress = percentage;
+ }
+ download::Progress::Finished => {
+ self.state = State::Finished {
+ button: button::State::new(),
+ }
+ }
+ download::Progress::Errored => {
+ self.state = State::Errored {
+ button: button::State::new(),
+ };
+ }
+ },
+ _ => {}
+ }
+ }
+
+ pub fn subscription(&self) -> Subscription<Message> {
+ match self.state {
+ State::Downloading { .. } => {
+ download::file(self.id, "https://speed.hetzner.de/100MB.bin?")
.map(Message::DownloadProgressed)
}
_ => Subscription::none(),
}
}
- fn view(&mut self) -> Element<Message> {
- let current_progress = match self {
- Example::Idle { .. } => 0.0,
- Example::Downloading { progress } => *progress,
- Example::Finished { .. } => 100.0,
- Example::Errored { .. } => 0.0,
+ pub fn view(&mut self) -> Element<Message> {
+ let current_progress = match &self.state {
+ State::Idle { .. } => 0.0,
+ State::Downloading { progress } => *progress,
+ State::Finished { .. } => 100.0,
+ State::Errored { .. } => 0.0,
};
let progress_bar = ProgressBar::new(0.0..=100.0, current_progress);
- let control: Element<_> = match self {
- Example::Idle { button } => {
+ let control: Element<_> = match &mut self.state {
+ State::Idle { button } => {
Button::new(button, Text::new("Start the download!"))
- .on_press(Message::Download)
+ .on_press(Message::Download(self.id))
.into()
}
- Example::Finished { button } => Column::new()
+ State::Finished { button } => Column::new()
.spacing(10)
.align_items(Align::Center)
.push(Text::new("Download finished!"))
.push(
Button::new(button, Text::new("Start again"))
- .on_press(Message::Download),
+ .on_press(Message::Download(self.id)),
)
.into(),
- Example::Downloading { .. } => {
+ State::Downloading { .. } => {
Text::new(format!("Downloading... {:.2}%", current_progress))
.into()
}
- Example::Errored { button } => Column::new()
+ State::Errored { button } => Column::new()
.spacing(10)
.align_items(Align::Center)
.push(Text::new("Something went wrong :("))
.push(
Button::new(button, Text::new("Try again"))
- .on_press(Message::Download),
+ .on_press(Message::Download(self.id)),
)
.into(),
};
- let content = Column::new()
+ Column::new()
.spacing(10)
.padding(10)
.align_items(Align::Center)
.push(progress_bar)
- .push(control);
-
- Container::new(content)
- .width(Length::Fill)
- .height(Length::Fill)
- .center_x()
- .center_y()
+ .push(control)
.into()
}
}
diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs
index 6eba6aad..446c190b 100644
--- a/examples/events/src/main.rs
+++ b/examples/events/src/main.rs
@@ -1,22 +1,30 @@
use iced::{
- executor, Align, Application, Checkbox, Column, Command, Container,
- Element, Length, Settings, Subscription, Text,
+ button, executor, Align, Application, Button, Checkbox, Clipboard, Column,
+ Command, Container, Element, HorizontalAlignment, Length, Settings,
+ Subscription, Text,
};
+use iced_native::{window, Event};
pub fn main() -> iced::Result {
- Events::run(Settings::default())
+ Events::run(Settings {
+ exit_on_close_request: false,
+ ..Settings::default()
+ })
}
#[derive(Debug, Default)]
struct Events {
last: Vec<iced_native::Event>,
enabled: bool,
+ exit: button::State,
+ should_exit: bool,
}
#[derive(Debug, Clone)]
enum Message {
EventOccurred(iced_native::Event),
Toggled(bool),
+ Exit,
}
impl Application for Events {
@@ -32,29 +40,41 @@ impl Application for Events {
String::from("Events - Iced")
}
- fn update(&mut self, message: Message) -> Command<Message> {
+ fn update(
+ &mut self,
+ message: Message,
+ _clipboard: &mut Clipboard,
+ ) -> Command<Message> {
match message {
- Message::EventOccurred(event) => {
+ Message::EventOccurred(event) if self.enabled => {
self.last.push(event);
if self.last.len() > 5 {
let _ = self.last.remove(0);
}
}
+ Message::EventOccurred(event) => {
+ if let Event::Window(window::Event::CloseRequested) = event {
+ self.should_exit = true;
+ }
+ }
Message::Toggled(enabled) => {
self.enabled = enabled;
}
+ Message::Exit => {
+ self.should_exit = true;
+ }
};
Command::none()
}
fn subscription(&self) -> Subscription<Message> {
- if self.enabled {
- iced_native::subscription::events().map(Message::EventOccurred)
- } else {
- Subscription::none()
- }
+ iced_native::subscription::events().map(Message::EventOccurred)
+ }
+
+ fn should_exit(&self) -> bool {
+ self.should_exit
}
fn view(&mut self) -> Element<Message> {
@@ -71,11 +91,22 @@ impl Application for Events {
Message::Toggled,
);
+ let exit = Button::new(
+ &mut self.exit,
+ Text::new("Exit")
+ .width(Length::Fill)
+ .horizontal_alignment(HorizontalAlignment::Center),
+ )
+ .width(Length::Units(100))
+ .padding(10)
+ .on_press(Message::Exit);
+
let content = Column::new()
.align_items(Align::Center)
.spacing(20)
.push(events)
- .push(toggle);
+ .push(toggle)
+ .push(exit);
Container::new(content)
.width(Length::Fill)
diff --git a/examples/game_of_life/Cargo.toml b/examples/game_of_life/Cargo.toml
index b9bb7f2a..ffd2f19e 100644
--- a/examples/game_of_life/Cargo.toml
+++ b/examples/game_of_life/Cargo.toml
@@ -7,6 +7,6 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
-tokio = { version = "0.2", features = ["blocking"] }
+tokio = { version = "1.0", features = ["sync"] }
itertools = "0.9"
rustc-hash = "1.1"
diff --git a/examples/game_of_life/README.md b/examples/game_of_life/README.md
index 1aeb1455..aa39201c 100644
--- a/examples/game_of_life/README.md
+++ b/examples/game_of_life/README.md
@@ -7,8 +7,8 @@ It runs a simulation in a background thread while allowing interaction with a `C
The __[`main`]__ file contains the relevant code of the example.
<div align="center">
- <a href="https://gfycat.com/briefaccurateaardvark">
- <img src="https://thumbs.gfycat.com/BriefAccurateAardvark-size_restricted.gif">
+ <a href="https://gfycat.com/WhichPaltryChick">
+ <img src="https://thumbs.gfycat.com/WhichPaltryChick-size_restricted.gif">
</a>
</div>
diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs
index 332d2d95..c3e16e8b 100644
--- a/examples/game_of_life/src/main.rs
+++ b/examples/game_of_life/src/main.rs
@@ -6,12 +6,14 @@ mod style;
use grid::Grid;
use iced::button::{self, Button};
use iced::executor;
+use iced::menu::{self, Menu};
use iced::pick_list::{self, PickList};
use iced::slider::{self, Slider};
use iced::time;
+use iced::window;
use iced::{
- Align, Application, Checkbox, Column, Command, Container, Element, Length,
- Row, Settings, Subscription, Text,
+ Align, Application, Checkbox, Clipboard, Column, Command, Container,
+ Element, Length, Row, Settings, Subscription, Text,
};
use preset::Preset;
use std::time::{Duration, Instant};
@@ -19,6 +21,10 @@ use std::time::{Duration, Instant};
pub fn main() -> iced::Result {
GameOfLife::run(Settings {
antialiasing: true,
+ window: window::Settings {
+ position: window::Position::Centered,
+ ..window::Settings::default()
+ },
..Settings::default()
})
}
@@ -31,11 +37,12 @@ struct GameOfLife {
queued_ticks: usize,
speed: usize,
next_speed: Option<usize>,
+ version: usize,
}
#[derive(Debug, Clone)]
enum Message {
- Grid(grid::Message),
+ Grid(grid::Message, usize),
Tick(Instant),
TogglePlayback,
ToggleGrid(bool),
@@ -64,10 +71,16 @@ impl Application for GameOfLife {
String::from("Game of Life - Iced")
}
- fn update(&mut self, message: Message) -> Command<Message> {
+ fn update(
+ &mut self,
+ message: Message,
+ _clipboard: &mut Clipboard,
+ ) -> Command<Message> {
match message {
- Message::Grid(message) => {
- self.grid.update(message);
+ Message::Grid(message, version) => {
+ if version == self.version {
+ self.grid.update(message);
+ }
}
Message::Tick(_) | Message::Next => {
self.queued_ticks = (self.queued_ticks + 1).min(self.speed);
@@ -79,7 +92,11 @@ impl Application for GameOfLife {
self.queued_ticks = 0;
- return Command::perform(task, Message::Grid);
+ let version = self.version;
+
+ return Command::perform(task, move |message| {
+ Message::Grid(message, version)
+ });
}
}
Message::TogglePlayback => {
@@ -90,6 +107,7 @@ impl Application for GameOfLife {
}
Message::Clear => {
self.grid.clear();
+ self.version += 1;
}
Message::SpeedChanged(speed) => {
if self.is_playing {
@@ -100,6 +118,7 @@ impl Application for GameOfLife {
}
Message::PresetPicked(new_preset) => {
self.grid = Grid::from_preset(new_preset);
+ self.version += 1;
}
}
@@ -115,7 +134,15 @@ impl Application for GameOfLife {
}
}
+ fn menu(&self) -> Menu<Message> {
+ Menu::with_entries(vec![menu::Entry::dropdown(
+ "Presets",
+ Preset::menu().map(Message::PresetPicked),
+ )])
+ }
+
fn view(&mut self) -> Element<Message> {
+ let version = self.version;
let selected_speed = self.next_speed.unwrap_or(self.speed);
let controls = self.controls.view(
self.is_playing,
@@ -125,7 +152,11 @@ impl Application for GameOfLife {
);
let content = Column::new()
- .push(self.grid.view().map(Message::Grid))
+ .push(
+ self.grid
+ .view()
+ .map(move |message| Message::Grid(message, version)),
+ )
.push(controls);
Container::new(content)
@@ -139,9 +170,8 @@ impl Application for GameOfLife {
mod grid {
use crate::Preset;
use iced::{
- canvas::{
- self, Cache, Canvas, Cursor, Event, Frame, Geometry, Path, Text,
- },
+ canvas::event::{self, Event},
+ canvas::{self, Cache, Canvas, Cursor, Frame, Geometry, Path, Text},
mouse, Color, Element, HorizontalAlignment, Length, Point, Rectangle,
Size, Vector, VerticalAlignment,
};
@@ -161,7 +191,6 @@ mod grid {
show_lines: bool,
last_tick_duration: Duration,
last_queued_ticks: usize,
- version: usize,
}
#[derive(Debug, Clone)]
@@ -171,7 +200,6 @@ mod grid {
Ticked {
result: Result<Life, TickError>,
tick_duration: Duration,
- version: usize,
},
}
@@ -208,7 +236,6 @@ mod grid {
show_lines: true,
last_tick_duration: Duration::default(),
last_queued_ticks: 0,
- version: 0,
}
}
@@ -216,7 +243,6 @@ mod grid {
&mut self,
amount: usize,
) -> Option<impl Future<Output = Message>> {
- let version = self.version;
let tick = self.state.tick(amount)?;
self.last_queued_ticks = amount;
@@ -228,7 +254,6 @@ mod grid {
Message::Ticked {
result,
- version,
tick_duration,
}
})
@@ -250,13 +275,11 @@ mod grid {
}
Message::Ticked {
result: Ok(life),
- version,
tick_duration,
- } if version == self.version => {
+ } => {
self.state.update(life);
self.life_cache.clear();
- self.version += 1;
self.last_tick_duration = tick_duration;
}
Message::Ticked {
@@ -264,7 +287,6 @@ mod grid {
} => {
dbg!(error);
}
- Message::Ticked { .. } => {}
}
}
@@ -278,7 +300,6 @@ mod grid {
pub fn clear(&mut self) {
self.state = State::default();
self.preset = Preset::Custom;
- self.version += 1;
self.life_cache.clear();
}
@@ -323,12 +344,18 @@ mod grid {
event: Event,
bounds: Rectangle,
cursor: Cursor,
- ) -> Option<Message> {
+ ) -> (event::Status, Option<Message>) {
if let Event::Mouse(mouse::Event::ButtonReleased(_)) = event {
self.interaction = Interaction::None;
}
- let cursor_position = cursor.position_in(&bounds)?;
+ let cursor_position =
+ if let Some(position) = cursor.position_in(&bounds) {
+ position
+ } else {
+ return (event::Status::Ignored, None);
+ };
+
let cell = Cell::at(self.project(cursor_position, bounds.size()));
let is_populated = self.state.contains(&cell);
@@ -340,28 +367,32 @@ mod grid {
match event {
Event::Mouse(mouse_event) => match mouse_event {
- mouse::Event::ButtonPressed(button) => match button {
- mouse::Button::Left => {
- self.interaction = if is_populated {
- Interaction::Erasing
- } else {
- Interaction::Drawing
- };
-
- populate.or(unpopulate)
- }
- mouse::Button::Right => {
- self.interaction = Interaction::Panning {
- translation: self.translation,
- start: cursor_position,
- };
+ mouse::Event::ButtonPressed(button) => {
+ let message = match button {
+ mouse::Button::Left => {
+ self.interaction = if is_populated {
+ Interaction::Erasing
+ } else {
+ Interaction::Drawing
+ };
+
+ populate.or(unpopulate)
+ }
+ mouse::Button::Right => {
+ self.interaction = Interaction::Panning {
+ translation: self.translation,
+ start: cursor_position,
+ };
- None
- }
- _ => None,
- },
+ None
+ }
+ _ => None,
+ };
+
+ (event::Status::Captured, message)
+ }
mouse::Event::CursorMoved { .. } => {
- match self.interaction {
+ let message = match self.interaction {
Interaction::Drawing => populate,
Interaction::Erasing => unpopulate,
Interaction::Panning { translation, start } => {
@@ -375,7 +406,14 @@ mod grid {
None
}
_ => None,
- }
+ };
+
+ let event_status = match self.interaction {
+ Interaction::None => event::Status::Ignored,
+ _ => event::Status::Captured,
+ };
+
+ (event_status, message)
}
mouse::Event::WheelScrolled { delta } => match delta {
mouse::ScrollDelta::Lines { y, .. }
@@ -408,12 +446,12 @@ mod grid {
self.grid_cache.clear();
}
- None
+ (event::Status::Captured, None)
}
},
- _ => None,
+ _ => (event::Status::Ignored, None),
},
- _ => None,
+ _ => (event::Status::Ignored, None),
}
}
diff --git a/examples/game_of_life/src/preset.rs b/examples/game_of_life/src/preset.rs
index 05157b6a..1c199a72 100644
--- a/examples/game_of_life/src/preset.rs
+++ b/examples/game_of_life/src/preset.rs
@@ -1,3 +1,5 @@
+use iced::menu::{self, Menu};
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Preset {
Custom,
@@ -26,6 +28,17 @@ pub static ALL: &[Preset] = &[
];
impl Preset {
+ pub fn menu() -> Menu<Self> {
+ Menu::with_entries(
+ ALL.iter()
+ .copied()
+ .map(|preset| {
+ menu::Entry::item(preset.to_string(), None, preset)
+ })
+ .collect(),
+ )
+ }
+
pub fn life(self) -> Vec<(isize, isize)> {
#[rustfmt::skip]
let cells = match self {
diff --git a/examples/game_of_life/src/style.rs b/examples/game_of_life/src/style.rs
index 308ce43c..be9a0e96 100644
--- a/examples/game_of_life/src/style.rs
+++ b/examples/game_of_life/src/style.rs
@@ -44,7 +44,7 @@ impl button::StyleSheet for Button {
fn active(&self) -> button::Style {
button::Style {
background: Some(Background::Color(ACTIVE)),
- border_radius: 3,
+ border_radius: 3.0,
text_color: Color::WHITE,
..button::Style::default()
}
@@ -60,7 +60,7 @@ impl button::StyleSheet for Button {
fn pressed(&self) -> button::Style {
button::Style {
- border_width: 1,
+ border_width: 1.0,
border_color: Color::WHITE,
..self.hovered()
}
@@ -73,7 +73,7 @@ impl button::StyleSheet for Clear {
fn active(&self) -> button::Style {
button::Style {
background: Some(Background::Color(DESTRUCTIVE)),
- border_radius: 3,
+ border_radius: 3.0,
text_color: Color::WHITE,
..button::Style::default()
}
@@ -92,7 +92,7 @@ impl button::StyleSheet for Clear {
fn pressed(&self) -> button::Style {
button::Style {
- border_width: 1,
+ border_width: 1.0,
border_color: Color::WHITE,
..self.hovered()
}
@@ -106,9 +106,9 @@ impl slider::StyleSheet for Slider {
slider::Style {
rail_colors: (ACTIVE, Color { a: 0.1, ..ACTIVE }),
handle: slider::Handle {
- shape: slider::HandleShape::Circle { radius: 9 },
+ shape: slider::HandleShape::Circle { radius: 9.0 },
color: ACTIVE,
- border_width: 0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
},
}
@@ -146,7 +146,7 @@ impl pick_list::StyleSheet for PickList {
pick_list::Menu {
text_color: Color::WHITE,
background: BACKGROUND.into(),
- border_width: 1,
+ border_width: 1.0,
border_color: Color {
a: 0.7,
..Color::BLACK
@@ -164,13 +164,14 @@ impl pick_list::StyleSheet for PickList {
pick_list::Style {
text_color: Color::WHITE,
background: BACKGROUND.into(),
- border_width: 1,
+ border_width: 1.0,
border_color: Color {
a: 0.6,
..Color::BLACK
},
- border_radius: 2,
+ border_radius: 2.0,
icon_size: 0.5,
+ ..pick_list::Style::default()
}
}
diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs
index 1c13c8d5..f650b2c1 100644
--- a/examples/geometry/src/main.rs
+++ b/examples/geometry/src/main.rs
@@ -15,8 +15,8 @@ mod rainbow {
Backend, Defaults, Primitive, Renderer,
};
use iced_native::{
- layout, mouse, Element, Hasher, Layout, Length, Point, Size, Vector,
- Widget,
+ layout, mouse, Element, Hasher, Layout, Length, Point, Rectangle, Size,
+ Vector, Widget,
};
pub struct Rainbow;
@@ -57,6 +57,7 @@ mod rainbow {
_defaults: &Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ _viewport: &Rectangle,
) -> (Primitive, mouse::Interaction) {
let b = layout.bounds();
diff --git a/examples/integration/Cargo.toml b/examples/integration/Cargo.toml
index afc2c791..4515502f 100644
--- a/examples/integration/Cargo.toml
+++ b/examples/integration/Cargo.toml
@@ -8,4 +8,4 @@ publish = false
[dependencies]
iced_winit = { path = "../../winit" }
iced_wgpu = { path = "../../wgpu" }
-env_logger = "0.7"
+env_logger = "0.8"
diff --git a/examples/integration/src/controls.rs b/examples/integration/src/controls.rs
index 824f9f53..36ee9b7e 100644
--- a/examples/integration/src/controls.rs
+++ b/examples/integration/src/controls.rs
@@ -1,7 +1,7 @@
use iced_wgpu::Renderer;
use iced_winit::{
- slider, Align, Color, Column, Command, Element, Length, Program, Row,
- Slider, Text,
+ slider, Align, Clipboard, Color, Column, Command, Element, Length, Program,
+ Row, Slider, Text,
};
pub struct Controls {
@@ -30,8 +30,13 @@ impl Controls {
impl Program for Controls {
type Renderer = Renderer;
type Message = Message;
+ type Clipboard = Clipboard;
- fn update(&mut self, message: Message) -> Command<Message> {
+ fn update(
+ &mut self,
+ message: Message,
+ _clipboard: &mut Clipboard,
+ ) -> Command<Message> {
match message {
Message::BackgroundColorChanged(color) => {
self.background_color = color;
diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs
index 9b52f3a5..6f319466 100644
--- a/examples/integration/src/main.rs
+++ b/examples/integration/src/main.rs
@@ -5,7 +5,7 @@ use controls::Controls;
use scene::Scene;
use iced_wgpu::{wgpu, Backend, Renderer, Settings, Viewport};
-use iced_winit::{conversion, futures, program, winit, Debug, Size};
+use iced_winit::{conversion, futures, program, winit, Clipboard, Debug, Size};
use futures::task::SpawnExt;
use winit::{
@@ -28,43 +28,47 @@ pub fn main() {
);
let mut cursor_position = PhysicalPosition::new(-1.0, -1.0);
let mut modifiers = ModifiersState::default();
+ let mut clipboard = Clipboard::connect(&window);
// Initialize wgpu
let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY);
let surface = unsafe { instance.create_surface(&window) };
- let (mut device, queue) = futures::executor::block_on(async {
+ let (format, (mut device, queue)) = futures::executor::block_on(async {
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
- power_preference: wgpu::PowerPreference::Default,
+ power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: Some(&surface),
})
.await
.expect("Request adapter");
- adapter
- .request_device(
- &wgpu::DeviceDescriptor {
- features: wgpu::Features::empty(),
- limits: wgpu::Limits::default(),
- shader_validation: false,
- },
- None,
- )
- .await
- .expect("Request device")
+ (
+ adapter
+ .get_swap_chain_preferred_format(&surface)
+ .expect("Get preferred format"),
+ adapter
+ .request_device(
+ &wgpu::DeviceDescriptor {
+ label: None,
+ features: wgpu::Features::empty(),
+ limits: wgpu::Limits::default(),
+ },
+ None,
+ )
+ .await
+ .expect("Request device"),
+ )
});
- let format = wgpu::TextureFormat::Bgra8UnormSrgb;
-
let mut swap_chain = {
let size = window.inner_size();
device.create_swap_chain(
&surface,
&wgpu::SwapChainDescriptor {
- usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
- format: format,
+ usage: wgpu::TextureUsage::RENDER_ATTACHMENT,
+ format,
width: size.width,
height: size.height,
present_mode: wgpu::PresentMode::Mailbox,
@@ -84,7 +88,7 @@ pub fn main() {
// Initialize iced
let mut debug = Debug::new();
let mut renderer =
- Renderer::new(Backend::new(&mut device, Settings::default()));
+ Renderer::new(Backend::new(&mut device, Settings::default(), format));
let mut state = program::State::new(
controls,
@@ -141,8 +145,8 @@ pub fn main() {
cursor_position,
viewport.scale_factor(),
),
- None,
&mut renderer,
+ &mut clipboard,
&mut debug,
);
@@ -157,7 +161,7 @@ pub fn main() {
swap_chain = device.create_swap_chain(
&surface,
&wgpu::SwapChainDescriptor {
- usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
+ usage: wgpu::TextureUsage::RENDER_ATTACHMENT,
format: format,
width: size.width,
height: size.height,
@@ -168,55 +172,66 @@ pub fn main() {
resized = false;
}
- let frame = swap_chain.get_current_frame().expect("Next frame");
+ match swap_chain.get_current_frame() {
+ Ok(frame) => {
+ let mut encoder = device.create_command_encoder(
+ &wgpu::CommandEncoderDescriptor { label: None },
+ );
+
+ let program = state.program();
+
+ {
+ // We clear the frame
+ let mut render_pass = scene.clear(
+ &frame.output.view,
+ &mut encoder,
+ program.background_color(),
+ );
+
+ // Draw the scene
+ scene.draw(&mut render_pass);
+ }
+
+ // And then iced on top
+ let mouse_interaction = renderer.backend_mut().draw(
+ &mut device,
+ &mut staging_belt,
+ &mut encoder,
+ &frame.output.view,
+ &viewport,
+ state.primitive(),
+ &debug.overlay(),
+ );
- let mut encoder = device.create_command_encoder(
- &wgpu::CommandEncoderDescriptor { label: None },
- );
+ // Then we submit the work
+ staging_belt.finish();
+ queue.submit(Some(encoder.finish()));
- let program = state.program();
+ // Update the mouse cursor
+ window.set_cursor_icon(
+ iced_winit::conversion::mouse_interaction(
+ mouse_interaction,
+ ),
+ );
- {
- // We clear the frame
- let mut render_pass = scene.clear(
- &frame.output.view,
- &mut encoder,
- program.background_color(),
- );
+ // And recall staging buffers
+ local_pool
+ .spawner()
+ .spawn(staging_belt.recall())
+ .expect("Recall staging buffers");
- // Draw the scene
- scene.draw(&mut render_pass);
+ local_pool.run_until_stalled();
+ }
+ Err(error) => match error {
+ wgpu::SwapChainError::OutOfMemory => {
+ panic!("Swapchain error: {}. Rendering cannot continue.", error)
+ }
+ _ => {
+ // Try rendering again next frame.
+ window.request_redraw();
+ }
+ },
}
-
- // And then iced on top
- let mouse_interaction = renderer.backend_mut().draw(
- &mut device,
- &mut staging_belt,
- &mut encoder,
- &frame.output.view,
- &viewport,
- state.primitive(),
- &debug.overlay(),
- );
-
- // Then we submit the work
- staging_belt.finish();
- queue.submit(Some(encoder.finish()));
-
- // Update the mouse cursor
- window.set_cursor_icon(
- iced_winit::conversion::mouse_interaction(
- mouse_interaction,
- ),
- );
-
- // And recall staging buffers
- local_pool
- .spawner()
- .spawn(staging_belt.recall())
- .expect("Recall staging buffers");
-
- local_pool.run_until_stalled();
}
_ => {}
}
diff --git a/examples/integration/src/scene.rs b/examples/integration/src/scene.rs
index 03a338c6..3e8277c8 100644
--- a/examples/integration/src/scene.rs
+++ b/examples/integration/src/scene.rs
@@ -19,8 +19,9 @@ impl Scene {
background_color: Color,
) -> wgpu::RenderPass<'a> {
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
- color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
- attachment: target,
+ label: None,
+ color_attachments: &[wgpu::RenderPassColorAttachment {
+ view: target,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear({
@@ -48,10 +49,10 @@ impl Scene {
fn build_pipeline(device: &wgpu::Device) -> wgpu::RenderPipeline {
let vs_module =
- device.create_shader_module(wgpu::include_spirv!("shader/vert.spv"));
+ device.create_shader_module(&wgpu::include_spirv!("shader/vert.spv"));
let fs_module =
- device.create_shader_module(wgpu::include_spirv!("shader/frag.spv"));
+ device.create_shader_module(&wgpu::include_spirv!("shader/frag.spv"));
let pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
@@ -64,34 +65,34 @@ fn build_pipeline(device: &wgpu::Device) -> wgpu::RenderPipeline {
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: None,
layout: Some(&pipeline_layout),
- vertex_stage: wgpu::ProgrammableStageDescriptor {
+ vertex: wgpu::VertexState {
module: &vs_module,
entry_point: "main",
+ buffers: &[],
},
- fragment_stage: Some(wgpu::ProgrammableStageDescriptor {
+ fragment: Some(wgpu::FragmentState {
module: &fs_module,
entry_point: "main",
+ targets: &[wgpu::ColorTargetState {
+ format: wgpu::TextureFormat::Bgra8UnormSrgb,
+ blend: Some(wgpu::BlendState {
+ color: wgpu::BlendComponent::REPLACE,
+ alpha: wgpu::BlendComponent::REPLACE,
+ }),
+ write_mask: wgpu::ColorWrite::ALL,
+ }],
}),
- rasterization_state: Some(wgpu::RasterizationStateDescriptor {
+ primitive: wgpu::PrimitiveState {
+ topology: wgpu::PrimitiveTopology::TriangleList,
front_face: wgpu::FrontFace::Ccw,
- cull_mode: wgpu::CullMode::None,
..Default::default()
- }),
- primitive_topology: wgpu::PrimitiveTopology::TriangleList,
- color_states: &[wgpu::ColorStateDescriptor {
- format: wgpu::TextureFormat::Bgra8UnormSrgb,
- color_blend: wgpu::BlendDescriptor::REPLACE,
- alpha_blend: wgpu::BlendDescriptor::REPLACE,
- write_mask: wgpu::ColorWrite::ALL,
- }],
- depth_stencil_state: None,
- vertex_state: wgpu::VertexStateDescriptor {
- index_format: wgpu::IndexFormat::Uint16,
- vertex_buffers: &[],
},
- sample_count: 1,
- sample_mask: !0,
- alpha_to_coverage_enabled: false,
+ depth_stencil: None,
+ multisample: wgpu::MultisampleState {
+ count: 1,
+ mask: !0,
+ alpha_to_coverage_enabled: false,
+ },
});
pipeline
diff --git a/examples/menu/Cargo.toml b/examples/menu/Cargo.toml
new file mode 100644
index 00000000..44597734
--- /dev/null
+++ b/examples/menu/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "menu"
+version = "0.1.0"
+authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
+edition = "2018"
+publish = false
+
+[dependencies]
+iced = { path = "../.." }
+iced_native = { path = "../../native" } \ No newline at end of file
diff --git a/examples/menu/src/main.rs b/examples/menu/src/main.rs
new file mode 100644
index 00000000..7403713c
--- /dev/null
+++ b/examples/menu/src/main.rs
@@ -0,0 +1,117 @@
+use iced::menu::{self, Menu};
+use iced::{
+ executor, Application, Clipboard, Command, Container, Element, Length,
+ Settings, Text,
+};
+use iced_native::keyboard::{Hotkey, KeyCode, Modifiers};
+
+pub fn main() -> iced::Result {
+ App::run(Settings::default())
+}
+
+#[derive(Debug, Default)]
+struct App {
+ selected: Option<Entry>,
+}
+
+#[derive(Debug, Clone)]
+enum Entry {
+ One,
+ Two,
+ Three,
+ A,
+ B,
+ C,
+}
+
+#[derive(Debug, Clone)]
+enum Message {
+ MenuActivated(Entry),
+}
+
+impl Application for App {
+ type Executor = executor::Default;
+ type Message = Message;
+ type Flags = ();
+
+ fn new(_flags: ()) -> (App, Command<Message>) {
+ (App::default(), Command::none())
+ }
+
+ fn title(&self) -> String {
+ String::from("Menu - Iced")
+ }
+
+ fn menu(&self) -> Menu<Message> {
+ let alt = Modifiers::ALT;
+ let ctrl_shift = Modifiers::CTRL | Modifiers::SHIFT;
+
+ Menu::with_entries(vec![
+ menu::Entry::dropdown(
+ "First",
+ Menu::with_entries(vec![
+ menu::Entry::item(
+ "One",
+ Hotkey::new(alt, KeyCode::F1),
+ Message::MenuActivated(Entry::One),
+ ),
+ menu::Entry::item(
+ "Two",
+ Hotkey::new(alt, KeyCode::F2),
+ Message::MenuActivated(Entry::Two),
+ ),
+ menu::Entry::Separator,
+ menu::Entry::item(
+ "Three",
+ Hotkey::new(alt, KeyCode::F3),
+ Message::MenuActivated(Entry::Three),
+ ),
+ ]),
+ ),
+ menu::Entry::dropdown(
+ "Second",
+ Menu::with_entries(vec![
+ menu::Entry::item(
+ "A",
+ Hotkey::new(ctrl_shift, KeyCode::A),
+ Message::MenuActivated(Entry::A),
+ ),
+ menu::Entry::item(
+ "B",
+ Hotkey::new(ctrl_shift, KeyCode::B),
+ Message::MenuActivated(Entry::B),
+ ),
+ menu::Entry::Separator,
+ menu::Entry::item(
+ "C",
+ Hotkey::new(ctrl_shift, KeyCode::C),
+ Message::MenuActivated(Entry::C),
+ ),
+ ]),
+ ),
+ ])
+ }
+
+ fn update(
+ &mut self,
+ message: Message,
+ _clipboard: &mut Clipboard,
+ ) -> Command<Message> {
+ match message {
+ Message::MenuActivated(entry) => self.selected = Some(entry),
+ }
+
+ Command::none()
+ }
+
+ fn view(&mut self) -> Element<Message> {
+ Container::new(
+ Text::new(format!("Selected {:?}", self.selected)).size(48),
+ )
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .center_x()
+ .center_y()
+ .into()
+ }
+}
diff --git a/examples/pane_grid/Cargo.toml b/examples/pane_grid/Cargo.toml
index 3ed912ac..e489f210 100644
--- a/examples/pane_grid/Cargo.toml
+++ b/examples/pane_grid/Cargo.toml
@@ -6,4 +6,5 @@ edition = "2018"
publish = false
[dependencies]
-iced = { path = "../.." }
+iced = { path = "../..", features = ["debug"] }
+iced_native = { path = "../../native" }
diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs
index c4946645..3bd8aa25 100644
--- a/examples/pane_grid/src/main.rs
+++ b/examples/pane_grid/src/main.rs
@@ -1,16 +1,19 @@
use iced::{
- button, keyboard, pane_grid, scrollable, Align, Button, Column, Container,
- Element, HorizontalAlignment, Length, PaneGrid, Sandbox, Scrollable,
- Settings, Text,
+ button, executor, keyboard, pane_grid, scrollable, Align, Application,
+ Button, Clipboard, Color, Column, Command, Container, Element,
+ HorizontalAlignment, Length, PaneGrid, Row, Scrollable, Settings,
+ Subscription, Text,
};
+use iced_native::{event, subscription, Event};
pub fn main() -> iced::Result {
Example::run(Settings::default())
}
struct Example {
- panes: pane_grid::State<Content>,
+ panes: pane_grid::State<Pane>,
panes_created: usize,
+ focus: Option<pane_grid::Pane>,
}
#[derive(Debug, Clone, Copy)]
@@ -18,59 +21,82 @@ enum Message {
Split(pane_grid::Axis, pane_grid::Pane),
SplitFocused(pane_grid::Axis),
FocusAdjacent(pane_grid::Direction),
+ Clicked(pane_grid::Pane),
Dragged(pane_grid::DragEvent),
Resized(pane_grid::ResizeEvent),
+ TogglePin(pane_grid::Pane),
Close(pane_grid::Pane),
CloseFocused,
}
-impl Sandbox for Example {
+impl Application for Example {
type Message = Message;
-
- fn new() -> Self {
- let (panes, _) = pane_grid::State::new(Content::new(0));
-
- Example {
- panes,
- panes_created: 1,
- }
+ type Executor = executor::Default;
+ type Flags = ();
+
+ fn new(_flags: ()) -> (Self, Command<Message>) {
+ let (panes, _) = pane_grid::State::new(Pane::new(0));
+
+ (
+ Example {
+ panes,
+ panes_created: 1,
+ focus: None,
+ },
+ Command::none(),
+ )
}
fn title(&self) -> String {
String::from("Pane grid - Iced")
}
- fn update(&mut self, message: Message) {
+ fn update(
+ &mut self,
+ message: Message,
+ _clipboard: &mut Clipboard,
+ ) -> Command<Message> {
match message {
Message::Split(axis, pane) => {
- let _ = self.panes.split(
+ let result = self.panes.split(
axis,
&pane,
- Content::new(self.panes_created),
+ Pane::new(self.panes_created),
);
+ if let Some((pane, _)) = result {
+ self.focus = Some(pane);
+ }
+
self.panes_created += 1;
}
Message::SplitFocused(axis) => {
- if let Some(pane) = self.panes.active() {
- let _ = self.panes.split(
+ if let Some(pane) = self.focus {
+ let result = self.panes.split(
axis,
&pane,
- Content::new(self.panes_created),
+ Pane::new(self.panes_created),
);
+ if let Some((pane, _)) = result {
+ self.focus = Some(pane);
+ }
+
self.panes_created += 1;
}
}
Message::FocusAdjacent(direction) => {
- if let Some(pane) = self.panes.active() {
+ if let Some(pane) = self.focus {
if let Some(adjacent) =
self.panes.adjacent(&pane, direction)
{
- self.panes.focus(&adjacent);
+ self.focus = Some(adjacent);
}
}
}
+ Message::Clicked(pane) => {
+ self.focus = Some(pane);
+ }
Message::Resized(pane_grid::ResizeEvent { split, ratio }) => {
self.panes.resize(&split, ratio);
}
@@ -81,38 +107,97 @@ impl Sandbox for Example {
self.panes.swap(&pane, &target);
}
Message::Dragged(_) => {}
+ Message::TogglePin(pane) => {
+ if let Some(Pane { is_pinned, .. }) = self.panes.get_mut(&pane)
+ {
+ *is_pinned = !*is_pinned;
+ }
+ }
Message::Close(pane) => {
- let _ = self.panes.close(&pane);
+ if let Some((_, sibling)) = self.panes.close(&pane) {
+ self.focus = Some(sibling);
+ }
}
Message::CloseFocused => {
- if let Some(pane) = self.panes.active() {
- let _ = self.panes.close(&pane);
+ if let Some(pane) = self.focus {
+ if let Some(Pane { is_pinned, .. }) = self.panes.get(&pane)
+ {
+ if !is_pinned {
+ if let Some((_, sibling)) = self.panes.close(&pane)
+ {
+ self.focus = Some(sibling);
+ }
+ }
+ }
}
}
}
+
+ Command::none()
+ }
+
+ fn subscription(&self) -> Subscription<Message> {
+ subscription::events_with(|event, status| {
+ if let event::Status::Captured = status {
+ return None;
+ }
+
+ match event {
+ Event::Keyboard(keyboard::Event::KeyPressed {
+ modifiers,
+ key_code,
+ }) if modifiers.command() => handle_hotkey(key_code),
+ _ => None,
+ }
+ })
}
fn view(&mut self) -> Element<Message> {
+ let focus = self.focus;
let total_panes = self.panes.len();
- let pane_grid =
- PaneGrid::new(&mut self.panes, |pane, content, focus| {
- let is_focused = focus.is_some();
- let title_bar =
- pane_grid::TitleBar::new(format!("Pane {}", content.id))
- .padding(10)
- .style(style::TitleBar { is_focused });
-
- pane_grid::Content::new(content.view(pane, total_panes))
- .title_bar(title_bar)
- .style(style::Pane { is_focused })
- })
- .width(Length::Fill)
- .height(Length::Fill)
- .spacing(10)
- .on_drag(Message::Dragged)
- .on_resize(10, Message::Resized)
- .on_key_press(handle_hotkey);
+ let pane_grid = PaneGrid::new(&mut self.panes, |id, pane| {
+ let is_focused = focus == Some(id);
+
+ let text = if pane.is_pinned { "Unpin" } else { "Pin" };
+ let pin_button =
+ Button::new(&mut pane.pin_button, Text::new(text).size(14))
+ .on_press(Message::TogglePin(id))
+ .style(style::Button::Pin)
+ .padding(3);
+
+ let title = Row::with_children(vec![
+ pin_button.into(),
+ Text::new("Pane").into(),
+ Text::new(pane.content.id.to_string())
+ .color(if is_focused {
+ PANE_ID_COLOR_FOCUSED
+ } else {
+ PANE_ID_COLOR_UNFOCUSED
+ })
+ .into(),
+ ])
+ .spacing(5);
+
+ let title_bar = pane_grid::TitleBar::new(title)
+ .controls(pane.controls.view(id, total_panes, pane.is_pinned))
+ .padding(10)
+ .style(style::TitleBar { is_focused });
+
+ pane_grid::Content::new(pane.content.view(
+ id,
+ total_panes,
+ pane.is_pinned,
+ ))
+ .title_bar(title_bar)
+ .style(style::Pane { is_focused })
+ })
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .spacing(10)
+ .on_click(Message::Clicked)
+ .on_drag(Message::Dragged)
+ .on_resize(10, Message::Resized);
Container::new(pane_grid)
.width(Length::Fill)
@@ -122,11 +207,22 @@ impl Sandbox for Example {
}
}
-fn handle_hotkey(event: pane_grid::KeyPressEvent) -> Option<Message> {
+const PANE_ID_COLOR_UNFOCUSED: Color = Color::from_rgb(
+ 0xFF as f32 / 255.0,
+ 0xC7 as f32 / 255.0,
+ 0xC7 as f32 / 255.0,
+);
+const PANE_ID_COLOR_FOCUSED: Color = Color::from_rgb(
+ 0xFF as f32 / 255.0,
+ 0x47 as f32 / 255.0,
+ 0x47 as f32 / 255.0,
+);
+
+fn handle_hotkey(key_code: keyboard::KeyCode) -> Option<Message> {
use keyboard::KeyCode;
use pane_grid::{Axis, Direction};
- let direction = match event.key_code {
+ let direction = match key_code {
KeyCode::Up => Some(Direction::Up),
KeyCode::Down => Some(Direction::Down),
KeyCode::Left => Some(Direction::Left),
@@ -134,7 +230,7 @@ fn handle_hotkey(event: pane_grid::KeyPressEvent) -> Option<Message> {
_ => None,
};
- match event.key_code {
+ match key_code {
KeyCode::V => Some(Message::SplitFocused(Axis::Vertical)),
KeyCode::H => Some(Message::SplitFocused(Axis::Horizontal)),
KeyCode::W => Some(Message::CloseFocused),
@@ -142,6 +238,13 @@ fn handle_hotkey(event: pane_grid::KeyPressEvent) -> Option<Message> {
}
}
+struct Pane {
+ pub is_pinned: bool,
+ pub pin_button: button::State,
+ pub content: Content,
+ pub controls: Controls,
+}
+
struct Content {
id: usize,
scroll: scrollable::State,
@@ -150,6 +253,21 @@ struct Content {
close: button::State,
}
+struct Controls {
+ close: button::State,
+}
+
+impl Pane {
+ fn new(id: usize) -> Self {
+ Self {
+ is_pinned: false,
+ pin_button: button::State::new(),
+ content: Content::new(id),
+ controls: Controls::new(),
+ }
+ }
+}
+
impl Content {
fn new(id: usize) -> Self {
Content {
@@ -164,6 +282,7 @@ impl Content {
&mut self,
pane: pane_grid::Pane,
total_panes: usize,
+ is_pinned: bool,
) -> Element<Message> {
let Content {
scroll,
@@ -203,7 +322,7 @@ impl Content {
style::Button::Primary,
));
- if total_panes > 1 {
+ if total_panes > 1 && !is_pinned {
controls = controls.push(button(
close,
"Close",
@@ -227,7 +346,32 @@ impl Content {
}
}
+impl Controls {
+ fn new() -> Self {
+ Self {
+ close: button::State::new(),
+ }
+ }
+
+ pub fn view(
+ &mut self,
+ pane: pane_grid::Pane,
+ total_panes: usize,
+ is_pinned: bool,
+ ) -> Element<Message> {
+ let mut button =
+ Button::new(&mut self.close, Text::new("Close").size(14))
+ .style(style::Button::Control)
+ .padding(3);
+ if total_panes > 1 && !is_pinned {
+ button = button.on_press(Message::Close(pane));
+ }
+ button.into()
+ }
+}
+
mod style {
+ use crate::PANE_ID_COLOR_FOCUSED;
use iced::{button, container, Background, Color, Vector};
const SURFACE: Color = Color::from_rgb(
@@ -275,7 +419,7 @@ mod style {
fn style(&self) -> container::Style {
container::Style {
background: Some(Background::Color(SURFACE)),
- border_width: 2,
+ border_width: 2.0,
border_color: if self.is_focused {
Color::BLACK
} else {
@@ -289,6 +433,8 @@ mod style {
pub enum Button {
Primary,
Destructive,
+ Control,
+ Pin,
}
impl button::StyleSheet for Button {
@@ -298,12 +444,14 @@ mod style {
Button::Destructive => {
(None, Color::from_rgb8(0xFF, 0x47, 0x47))
}
+ Button::Control => (Some(PANE_ID_COLOR_FOCUSED), Color::WHITE),
+ Button::Pin => (Some(ACTIVE), Color::WHITE),
};
button::Style {
text_color,
background: background.map(Background::Color),
- border_radius: 5,
+ border_radius: 5.0,
shadow_offset: Vector::new(0.0, 0.0),
..button::Style::default()
}
@@ -318,6 +466,8 @@ mod style {
a: 0.2,
..active.text_color
}),
+ Button::Control => Some(PANE_ID_COLOR_FOCUSED),
+ Button::Pin => Some(HOVERED),
};
button::Style {
diff --git a/examples/pick_list/src/main.rs b/examples/pick_list/src/main.rs
index 68662602..1eec9791 100644
--- a/examples/pick_list/src/main.rs
+++ b/examples/pick_list/src/main.rs
@@ -11,7 +11,7 @@ pub fn main() -> iced::Result {
struct Example {
scroll: scrollable::State,
pick_list: pick_list::State<Language>,
- selected_language: Language,
+ selected_language: Option<Language>,
}
#[derive(Debug, Clone, Copy)]
@@ -33,7 +33,7 @@ impl Sandbox for Example {
fn update(&mut self, message: Message) {
match message {
Message::LanguageSelected(language) => {
- self.selected_language = language;
+ self.selected_language = Some(language);
}
}
}
@@ -42,9 +42,10 @@ impl Sandbox for Example {
let pick_list = PickList::new(
&mut self.pick_list,
&Language::ALL[..],
- Some(self.selected_language),
+ self.selected_language,
Message::LanguageSelected,
- );
+ )
+ .placeholder("Choose a language...");
let mut content = Scrollable::new(&mut self.scroll)
.width(Length::Fill)
diff --git a/examples/pokedex/Cargo.toml b/examples/pokedex/Cargo.toml
index 94320086..05e73992 100644
--- a/examples/pokedex/Cargo.toml
+++ b/examples/pokedex/Cargo.toml
@@ -6,7 +6,7 @@ edition = "2018"
publish = false
[dependencies]
-iced = { path = "../..", features = ["image", "debug", "tokio"] }
+iced = { path = "../..", features = ["image", "debug", "tokio_old"] }
serde_json = "1.0"
[dependencies.serde]
diff --git a/examples/pokedex/src/main.rs b/examples/pokedex/src/main.rs
index 30674fa0..da1d5d5d 100644
--- a/examples/pokedex/src/main.rs
+++ b/examples/pokedex/src/main.rs
@@ -1,6 +1,6 @@
use iced::{
- button, futures, image, Align, Application, Button, Column, Command,
- Container, Element, Image, Length, Row, Settings, Text,
+ button, futures, image, Align, Application, Button, Clipboard, Column,
+ Command, Container, Element, Length, Row, Settings, Text,
};
pub fn main() -> iced::Result {
@@ -48,7 +48,11 @@ impl Application for Pokedex {
format!("{} - Pokédex", subtitle)
}
- fn update(&mut self, message: Message) -> Command<Message> {
+ fn update(
+ &mut self,
+ message: Message,
+ _clipboard: &mut Clipboard,
+ ) -> Command<Message> {
match message {
Message::PokemonFound(Ok(pokemon)) => {
*self = Pokedex::Loaded {
@@ -112,16 +116,20 @@ struct Pokemon {
name: String,
description: String,
image: image::Handle,
+ image_viewer: image::viewer::State,
}
impl Pokemon {
const TOTAL: u16 = 807;
- fn view(&self) -> Element<Message> {
+ fn view(&mut self) -> Element<Message> {
Row::new()
.spacing(20)
.align_items(Align::Center)
- .push(Image::new(self.image.clone()))
+ .push(image::Viewer::new(
+ &mut self.image_viewer,
+ self.image.clone(),
+ ))
.push(
Column::new()
.spacing(20)
@@ -200,11 +208,15 @@ impl Pokemon {
.map(|c| if c.is_control() { ' ' } else { c })
.collect(),
image,
+ image_viewer: image::viewer::State::new(),
})
}
async fn fetch_image(id: u16) -> Result<image::Handle, reqwest::Error> {
- let url = format!("https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{}.png", id);
+ let url = format!(
+ "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{}.png",
+ id
+ );
#[cfg(not(target_arch = "wasm32"))]
{
@@ -251,7 +263,7 @@ mod style {
background: Some(Background::Color(match self {
Button::Primary => Color::from_rgb(0.11, 0.42, 0.87),
})),
- border_radius: 12,
+ border_radius: 12.0,
shadow_offset: Vector::new(1.0, 1.0),
text_color: Color::WHITE,
..button::Style::default()
diff --git a/examples/qr_code/Cargo.toml b/examples/qr_code/Cargo.toml
new file mode 100644
index 00000000..7f2d4e42
--- /dev/null
+++ b/examples/qr_code/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "qr_code"
+version = "0.1.0"
+authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
+edition = "2018"
+publish = false
+
+[dependencies]
+iced = { path = "../..", features = ["qr_code"] }
diff --git a/examples/qr_code/README.md b/examples/qr_code/README.md
new file mode 100644
index 00000000..2dd89c26
--- /dev/null
+++ b/examples/qr_code/README.md
@@ -0,0 +1,18 @@
+## QR Code Generator
+
+A basic QR code generator that showcases the `QRCode` widget.
+
+The __[`main`]__ file contains all the code of the example.
+
+<div align="center">
+ <a href="https://gfycat.com/heavyexhaustedaracari">
+ <img src="https://thumbs.gfycat.com/HeavyExhaustedAracari-size_restricted.gif">
+ </a>
+</div>
+
+You can run it with `cargo run`:
+```
+cargo run --package qr_code
+```
+
+[`main`]: src/main.rs
diff --git a/examples/qr_code/src/main.rs b/examples/qr_code/src/main.rs
new file mode 100644
index 00000000..37b4855d
--- /dev/null
+++ b/examples/qr_code/src/main.rs
@@ -0,0 +1,81 @@
+use iced::qr_code::{self, QRCode};
+use iced::text_input::{self, TextInput};
+use iced::{
+ Align, Column, Container, Element, Length, Sandbox, Settings, Text,
+};
+
+pub fn main() -> iced::Result {
+ QRGenerator::run(Settings::default())
+}
+
+#[derive(Default)]
+struct QRGenerator {
+ data: String,
+ input: text_input::State,
+ qr_code: Option<qr_code::State>,
+}
+
+#[derive(Debug, Clone)]
+enum Message {
+ DataChanged(String),
+}
+
+impl Sandbox for QRGenerator {
+ type Message = Message;
+
+ fn new() -> Self {
+ QRGenerator {
+ qr_code: qr_code::State::new("").ok(),
+ ..Self::default()
+ }
+ }
+
+ fn title(&self) -> String {
+ String::from("QR Code Generator - Iced")
+ }
+
+ fn update(&mut self, message: Message) {
+ match message {
+ Message::DataChanged(mut data) => {
+ data.truncate(100);
+
+ self.qr_code = qr_code::State::new(&data).ok();
+ self.data = data;
+ }
+ }
+ }
+
+ fn view(&mut self) -> Element<Message> {
+ let title = Text::new("QR Code Generator")
+ .size(70)
+ .color([0.5, 0.5, 0.5]);
+
+ let input = TextInput::new(
+ &mut self.input,
+ "Type the data of your QR code here...",
+ &self.data,
+ Message::DataChanged,
+ )
+ .size(30)
+ .padding(15);
+
+ let mut content = Column::new()
+ .width(Length::Units(700))
+ .spacing(20)
+ .align_items(Align::Center)
+ .push(title)
+ .push(input);
+
+ if let Some(qr_code) = self.qr_code.as_mut() {
+ content = content.push(QRCode::new(qr_code).cell_size(10));
+ }
+
+ Container::new(content)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .padding(20)
+ .center_x()
+ .center_y()
+ .into()
+ }
+}
diff --git a/examples/scrollable/Cargo.toml b/examples/scrollable/Cargo.toml
new file mode 100644
index 00000000..08502458
--- /dev/null
+++ b/examples/scrollable/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "scrollable"
+version = "0.1.0"
+authors = ["Clark Moody <clark@clarkmoody.com>"]
+edition = "2018"
+publish = false
+
+[dependencies]
+iced = { path = "../..", features = ["debug"] }
diff --git a/examples/scrollable/README.md b/examples/scrollable/README.md
new file mode 100644
index 00000000..ed0e31b5
--- /dev/null
+++ b/examples/scrollable/README.md
@@ -0,0 +1,15 @@
+# Scrollable
+An example showcasing the various size and style options for the Scrollable.
+
+All the example code is located in the __[`main`](src/main.rs)__ file.
+
+<div align="center">
+ <a href="./screenshot.png">
+ <img src="./screenshot.png" height="640px">
+ </a>
+</div>
+
+You can run it with `cargo run`:
+```
+cargo run --package scrollable
+```
diff --git a/examples/scrollable/screenshot.png b/examples/scrollable/screenshot.png
new file mode 100644
index 00000000..2d800251
--- /dev/null
+++ b/examples/scrollable/screenshot.png
Binary files differ
diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs
new file mode 100644
index 00000000..3416b83d
--- /dev/null
+++ b/examples/scrollable/src/main.rs
@@ -0,0 +1,255 @@
+mod style;
+
+use iced::{
+ button, scrollable, Button, Column, Container, Element, Length,
+ ProgressBar, Radio, Row, Rule, Sandbox, Scrollable, Settings, Space, Text,
+};
+
+pub fn main() -> iced::Result {
+ ScrollableDemo::run(Settings::default())
+}
+
+struct ScrollableDemo {
+ theme: style::Theme,
+ variants: Vec<Variant>,
+}
+
+#[derive(Debug, Clone)]
+enum Message {
+ ThemeChanged(style::Theme),
+ ScrollToTop(usize),
+ ScrollToBottom(usize),
+ Scrolled(usize, f32),
+}
+
+impl Sandbox for ScrollableDemo {
+ type Message = Message;
+
+ fn new() -> Self {
+ ScrollableDemo {
+ theme: Default::default(),
+ variants: Variant::all(),
+ }
+ }
+
+ fn title(&self) -> String {
+ String::from("Scrollable - Iced")
+ }
+
+ fn update(&mut self, message: Message) {
+ match message {
+ Message::ThemeChanged(theme) => self.theme = theme,
+ Message::ScrollToTop(i) => {
+ if let Some(variant) = self.variants.get_mut(i) {
+ variant.scrollable.snap_to(0.0);
+
+ variant.latest_offset = 0.0;
+ }
+ }
+ Message::ScrollToBottom(i) => {
+ if let Some(variant) = self.variants.get_mut(i) {
+ variant.scrollable.snap_to(1.0);
+
+ variant.latest_offset = 1.0;
+ }
+ }
+ Message::Scrolled(i, offset) => {
+ if let Some(variant) = self.variants.get_mut(i) {
+ variant.latest_offset = offset;
+ }
+ }
+ }
+ }
+
+ fn view(&mut self) -> Element<Message> {
+ let ScrollableDemo {
+ theme, variants, ..
+ } = self;
+
+ let choose_theme = style::Theme::ALL.iter().fold(
+ Column::new().spacing(10).push(Text::new("Choose a theme:")),
+ |column, option| {
+ column.push(
+ Radio::new(
+ *option,
+ &format!("{:?}", option),
+ Some(*theme),
+ Message::ThemeChanged,
+ )
+ .style(*theme),
+ )
+ },
+ );
+
+ let scrollable_row = Row::with_children(
+ variants
+ .iter_mut()
+ .enumerate()
+ .map(|(i, variant)| {
+ let mut scrollable =
+ Scrollable::new(&mut variant.scrollable)
+ .padding(10)
+ .spacing(10)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .on_scroll(move |offset| {
+ Message::Scrolled(i, offset)
+ })
+ .style(*theme)
+ .push(Text::new(variant.title))
+ .push(
+ Button::new(
+ &mut variant.scroll_to_bottom,
+ Text::new("Scroll to bottom"),
+ )
+ .width(Length::Fill)
+ .padding(10)
+ .on_press(Message::ScrollToBottom(i)),
+ );
+
+ if let Some(scrollbar_width) = variant.scrollbar_width {
+ scrollable = scrollable
+ .scrollbar_width(scrollbar_width)
+ .push(Text::new(format!(
+ "scrollbar_width: {:?}",
+ scrollbar_width
+ )));
+ }
+
+ if let Some(scrollbar_margin) = variant.scrollbar_margin {
+ scrollable = scrollable
+ .scrollbar_margin(scrollbar_margin)
+ .push(Text::new(format!(
+ "scrollbar_margin: {:?}",
+ scrollbar_margin
+ )));
+ }
+
+ if let Some(scroller_width) = variant.scroller_width {
+ scrollable = scrollable
+ .scroller_width(scroller_width)
+ .push(Text::new(format!(
+ "scroller_width: {:?}",
+ scroller_width
+ )));
+ }
+
+ scrollable = scrollable
+ .push(Space::with_height(Length::Units(100)))
+ .push(Text::new(
+ "Some content that should wrap within the \
+ scrollable. Let's output a lot of short words, so \
+ that we'll make sure to see how wrapping works \
+ with these scrollbars.",
+ ))
+ .push(Space::with_height(Length::Units(1200)))
+ .push(Text::new("Middle"))
+ .push(Space::with_height(Length::Units(1200)))
+ .push(Text::new("The End."))
+ .push(
+ Button::new(
+ &mut variant.scroll_to_top,
+ Text::new("Scroll to top"),
+ )
+ .width(Length::Fill)
+ .padding(10)
+ .on_press(Message::ScrollToTop(i)),
+ );
+
+ Column::new()
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .spacing(10)
+ .push(
+ Container::new(scrollable)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .style(*theme),
+ )
+ .push(ProgressBar::new(
+ 0.0..=1.0,
+ variant.latest_offset,
+ ))
+ .into()
+ })
+ .collect(),
+ )
+ .spacing(20)
+ .width(Length::Fill)
+ .height(Length::Fill);
+
+ let content = Column::new()
+ .spacing(20)
+ .padding(20)
+ .push(choose_theme)
+ .push(Rule::horizontal(20).style(self.theme))
+ .push(scrollable_row);
+
+ Container::new(content)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .center_x()
+ .center_y()
+ .style(self.theme)
+ .into()
+ }
+}
+
+/// A version of a scrollable
+struct Variant {
+ title: &'static str,
+ scrollable: scrollable::State,
+ scroll_to_top: button::State,
+ scroll_to_bottom: button::State,
+ scrollbar_width: Option<u16>,
+ scrollbar_margin: Option<u16>,
+ scroller_width: Option<u16>,
+ latest_offset: f32,
+}
+
+impl Variant {
+ pub fn all() -> Vec<Self> {
+ vec![
+ Self {
+ title: "Default Scrollbar",
+ scrollable: scrollable::State::new(),
+ scroll_to_top: button::State::new(),
+ scroll_to_bottom: button::State::new(),
+ scrollbar_width: None,
+ scrollbar_margin: None,
+ scroller_width: None,
+ latest_offset: 0.0,
+ },
+ Self {
+ title: "Slimmed & Margin",
+ scrollable: scrollable::State::new(),
+ scroll_to_top: button::State::new(),
+ scroll_to_bottom: button::State::new(),
+ scrollbar_width: Some(4),
+ scrollbar_margin: Some(3),
+ scroller_width: Some(4),
+ latest_offset: 0.0,
+ },
+ Self {
+ title: "Wide Scroller",
+ scrollable: scrollable::State::new(),
+ scroll_to_top: button::State::new(),
+ scroll_to_bottom: button::State::new(),
+ scrollbar_width: Some(4),
+ scrollbar_margin: None,
+ scroller_width: Some(10),
+ latest_offset: 0.0,
+ },
+ Self {
+ title: "Narrow Scroller",
+ scrollable: scrollable::State::new(),
+ scroll_to_top: button::State::new(),
+ scroll_to_bottom: button::State::new(),
+ scrollbar_width: Some(10),
+ scrollbar_margin: None,
+ scroller_width: Some(4),
+ latest_offset: 0.0,
+ },
+ ]
+ }
+}
diff --git a/examples/scrollable/src/style.rs b/examples/scrollable/src/style.rs
new file mode 100644
index 00000000..ae449141
--- /dev/null
+++ b/examples/scrollable/src/style.rs
@@ -0,0 +1,190 @@
+use iced::{container, radio, rule, scrollable};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Theme {
+ Light,
+ Dark,
+}
+
+impl Theme {
+ pub const ALL: [Theme; 2] = [Theme::Light, Theme::Dark];
+}
+
+impl Default for Theme {
+ fn default() -> Theme {
+ Theme::Light
+ }
+}
+
+impl From<Theme> for Box<dyn container::StyleSheet> {
+ fn from(theme: Theme) -> Self {
+ match theme {
+ Theme::Light => Default::default(),
+ Theme::Dark => dark::Container.into(),
+ }
+ }
+}
+
+impl From<Theme> for Box<dyn radio::StyleSheet> {
+ fn from(theme: Theme) -> Self {
+ match theme {
+ Theme::Light => Default::default(),
+ Theme::Dark => dark::Radio.into(),
+ }
+ }
+}
+
+impl From<Theme> for Box<dyn scrollable::StyleSheet> {
+ fn from(theme: Theme) -> Self {
+ match theme {
+ Theme::Light => Default::default(),
+ Theme::Dark => dark::Scrollable.into(),
+ }
+ }
+}
+
+impl From<Theme> for Box<dyn rule::StyleSheet> {
+ fn from(theme: Theme) -> Self {
+ match theme {
+ Theme::Light => Default::default(),
+ Theme::Dark => dark::Rule.into(),
+ }
+ }
+}
+
+mod dark {
+ use iced::{container, radio, rule, scrollable, Color};
+
+ const BACKGROUND: Color = Color::from_rgb(
+ 0x36 as f32 / 255.0,
+ 0x39 as f32 / 255.0,
+ 0x3F as f32 / 255.0,
+ );
+
+ const SURFACE: Color = Color::from_rgb(
+ 0x40 as f32 / 255.0,
+ 0x44 as f32 / 255.0,
+ 0x4B as f32 / 255.0,
+ );
+
+ const ACCENT: Color = Color::from_rgb(
+ 0x6F as f32 / 255.0,
+ 0xFF as f32 / 255.0,
+ 0xE9 as f32 / 255.0,
+ );
+
+ const ACTIVE: Color = Color::from_rgb(
+ 0x72 as f32 / 255.0,
+ 0x89 as f32 / 255.0,
+ 0xDA as f32 / 255.0,
+ );
+
+ const SCROLLBAR: Color = Color::from_rgb(
+ 0x2E as f32 / 255.0,
+ 0x33 as f32 / 255.0,
+ 0x38 as f32 / 255.0,
+ );
+
+ const SCROLLER: Color = Color::from_rgb(
+ 0x20 as f32 / 255.0,
+ 0x22 as f32 / 255.0,
+ 0x25 as f32 / 255.0,
+ );
+
+ pub struct Container;
+
+ impl container::StyleSheet for Container {
+ fn style(&self) -> container::Style {
+ container::Style {
+ background: Color {
+ a: 0.99,
+ ..BACKGROUND
+ }
+ .into(),
+ text_color: Color::WHITE.into(),
+ ..container::Style::default()
+ }
+ }
+ }
+
+ pub struct Radio;
+
+ impl radio::StyleSheet for Radio {
+ fn active(&self) -> radio::Style {
+ radio::Style {
+ background: SURFACE.into(),
+ dot_color: ACTIVE,
+ border_width: 1.0,
+ border_color: ACTIVE,
+ }
+ }
+
+ fn hovered(&self) -> radio::Style {
+ radio::Style {
+ background: Color { a: 0.5, ..SURFACE }.into(),
+ ..self.active()
+ }
+ }
+ }
+
+ pub struct Scrollable;
+
+ impl scrollable::StyleSheet for Scrollable {
+ fn active(&self) -> scrollable::Scrollbar {
+ scrollable::Scrollbar {
+ background: Color {
+ a: 0.8,
+ ..SCROLLBAR
+ }
+ .into(),
+ border_radius: 2.0,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ scroller: scrollable::Scroller {
+ color: Color { a: 0.7, ..SCROLLER },
+ border_radius: 2.0,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ }
+ }
+
+ fn hovered(&self) -> scrollable::Scrollbar {
+ let active = self.active();
+
+ scrollable::Scrollbar {
+ background: SCROLLBAR.into(),
+ scroller: scrollable::Scroller {
+ color: SCROLLER,
+ ..active.scroller
+ },
+ ..active
+ }
+ }
+
+ fn dragging(&self) -> scrollable::Scrollbar {
+ let hovered = self.hovered();
+
+ scrollable::Scrollbar {
+ scroller: scrollable::Scroller {
+ color: ACCENT,
+ ..hovered.scroller
+ },
+ ..hovered
+ }
+ }
+ }
+
+ pub struct Rule;
+
+ impl rule::StyleSheet for Rule {
+ fn style(&self) -> rule::Style {
+ rule::Style {
+ color: SURFACE,
+ width: 2,
+ radius: 1.0,
+ fill_mode: rule::FillMode::Percent(30.0),
+ }
+ }
+ }
+}
diff --git a/examples/solar_system/Cargo.toml b/examples/solar_system/Cargo.toml
index 44ced729..327fe0aa 100644
--- a/examples/solar_system/Cargo.toml
+++ b/examples/solar_system/Cargo.toml
@@ -7,4 +7,4 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
-rand = "0.7"
+rand = "0.8.3"
diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs
index 6a2de736..8f844828 100644
--- a/examples/solar_system/src/main.rs
+++ b/examples/solar_system/src/main.rs
@@ -8,8 +8,8 @@
//! [1]: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations#An_animated_solar_system
use iced::{
canvas::{self, Cursor, Path, Stroke},
- executor, time, window, Application, Canvas, Color, Command, Element,
- Length, Point, Rectangle, Settings, Size, Subscription, Vector,
+ executor, time, window, Application, Canvas, Clipboard, Color, Command,
+ Element, Length, Point, Rectangle, Settings, Size, Subscription, Vector,
};
use std::time::Instant;
@@ -48,7 +48,11 @@ impl Application for SolarSystem {
String::from("Solar system - Iced")
}
- fn update(&mut self, message: Message) -> Command<Message> {
+ fn update(
+ &mut self,
+ message: Message,
+ _clipboard: &mut Clipboard,
+ ) -> Command<Message> {
match message {
Message::Tick(instant) => {
self.state.update(instant);
@@ -117,15 +121,13 @@ impl State {
(
Point::new(
rng.gen_range(
- -(width as f32) / 2.0,
- width as f32 / 2.0,
+ (-(width as f32) / 2.0)..(width as f32 / 2.0),
),
rng.gen_range(
- -(height as f32) / 2.0,
- height as f32 / 2.0,
+ (-(height as f32) / 2.0)..(height as f32 / 2.0),
),
),
- rng.gen_range(0.5, 1.0),
+ rng.gen_range(0.5..1.0),
)
})
.collect()
diff --git a/examples/stopwatch/Cargo.toml b/examples/stopwatch/Cargo.toml
index 075aa111..9f935951 100644
--- a/examples/stopwatch/Cargo.toml
+++ b/examples/stopwatch/Cargo.toml
@@ -6,4 +6,4 @@ edition = "2018"
publish = false
[dependencies]
-iced = { path = "../..", features = ["tokio"] }
+iced = { path = "../..", features = ["smol"] }
diff --git a/examples/stopwatch/src/main.rs b/examples/stopwatch/src/main.rs
index 5a69aa9a..51972e01 100644
--- a/examples/stopwatch/src/main.rs
+++ b/examples/stopwatch/src/main.rs
@@ -1,6 +1,6 @@
use iced::{
- button, executor, time, Align, Application, Button, Column, Command,
- Container, Element, HorizontalAlignment, Length, Row, Settings,
+ button, executor, time, Align, Application, Button, Clipboard, Column,
+ Command, Container, Element, HorizontalAlignment, Length, Row, Settings,
Subscription, Text,
};
use std::time::{Duration, Instant};
@@ -49,7 +49,11 @@ impl Application for Stopwatch {
String::from("Stopwatch - Iced")
}
- fn update(&mut self, message: Message) -> Command<Message> {
+ fn update(
+ &mut self,
+ message: Message,
+ _clipboard: &mut Clipboard,
+ ) -> Command<Message> {
match message {
Message::Toggle => match self.state {
State::Idle => {
@@ -161,7 +165,7 @@ mod style {
Button::Secondary => Color::from_rgb(0.5, 0.5, 0.5),
Button::Destructive => Color::from_rgb(0.8, 0.2, 0.2),
})),
- border_radius: 12,
+ border_radius: 12.0,
shadow_offset: Vector::new(1.0, 1.0),
text_color: Color::WHITE,
..button::Style::default()
diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs
index ef302e61..7bc49281 100644
--- a/examples/styling/src/main.rs
+++ b/examples/styling/src/main.rs
@@ -1,7 +1,7 @@
use iced::{
button, scrollable, slider, text_input, Align, Button, Checkbox, Column,
Container, Element, Length, ProgressBar, Radio, Row, Rule, Sandbox,
- Scrollable, Settings, Slider, Space, Text, TextInput,
+ Scrollable, Settings, Slider, Space, Text, TextInput, Toggler,
};
pub fn main() -> iced::Result {
@@ -17,7 +17,8 @@ struct Styling {
button: button::State,
slider: slider::State,
slider_value: f32,
- toggle_value: bool,
+ checkbox_value: bool,
+ toggler_value: bool,
}
#[derive(Debug, Clone)]
@@ -27,6 +28,7 @@ enum Message {
ButtonPressed,
SliderChanged(f32),
CheckboxToggled(bool),
+ TogglerToggled(bool),
}
impl Sandbox for Styling {
@@ -44,9 +46,10 @@ impl Sandbox for Styling {
match message {
Message::ThemeChanged(theme) => self.theme = theme,
Message::InputChanged(value) => self.input_value = value,
- Message::ButtonPressed => (),
+ Message::ButtonPressed => {}
Message::SliderChanged(value) => self.slider_value = value,
- Message::CheckboxToggled(value) => self.toggle_value = value,
+ Message::CheckboxToggled(value) => self.checkbox_value = value,
+ Message::TogglerToggled(value) => self.toggler_value = value,
}
}
@@ -101,11 +104,19 @@ impl Sandbox for Styling {
.push(Text::new("You did it!"));
let checkbox = Checkbox::new(
- self.toggle_value,
- "Toggle me!",
+ self.checkbox_value,
+ "Check me!",
Message::CheckboxToggled,
)
- .width(Length::Fill)
+ .style(self.theme);
+
+ let toggler = Toggler::new(
+ self.toggler_value,
+ String::from("Toggle me!"),
+ Message::TogglerToggled,
+ )
+ .width(Length::Shrink)
+ .spacing(10)
.style(self.theme);
let content = Column::new()
@@ -124,7 +135,13 @@ impl Sandbox for Styling {
.align_items(Align::Center)
.push(scrollable)
.push(Rule::vertical(38).style(self.theme))
- .push(checkbox),
+ .push(
+ Column::new()
+ .width(Length::Shrink)
+ .spacing(20)
+ .push(checkbox)
+ .push(toggler),
+ ),
);
Container::new(content)
@@ -140,7 +157,7 @@ impl Sandbox for Styling {
mod style {
use iced::{
button, checkbox, container, progress_bar, radio, rule, scrollable,
- slider, text_input,
+ slider, text_input, toggler,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -231,6 +248,15 @@ mod style {
}
}
+ impl From<Theme> for Box<dyn toggler::StyleSheet> {
+ fn from(theme: Theme) -> Self {
+ match theme {
+ Theme::Light => Default::default(),
+ Theme::Dark => dark::Toggler.into(),
+ }
+ }
+ }
+
impl From<Theme> for Box<dyn rule::StyleSheet> {
fn from(theme: Theme) -> Self {
match theme {
@@ -249,7 +275,7 @@ mod style {
fn active(&self) -> button::Style {
button::Style {
background: Color::from_rgb(0.11, 0.42, 0.87).into(),
- border_radius: 12,
+ border_radius: 12.0,
shadow_offset: Vector::new(1.0, 1.0),
text_color: Color::from_rgb8(0xEE, 0xEE, 0xEE),
..button::Style::default()
@@ -269,7 +295,7 @@ mod style {
mod dark {
use iced::{
button, checkbox, container, progress_bar, radio, rule, scrollable,
- slider, text_input, Color,
+ slider, text_input, toggler, Color,
};
const SURFACE: Color = Color::from_rgb(
@@ -315,7 +341,7 @@ mod style {
radio::Style {
background: SURFACE.into(),
dot_color: ACTIVE,
- border_width: 1,
+ border_width: 1.0,
border_color: ACTIVE,
}
}
@@ -334,15 +360,15 @@ mod style {
fn active(&self) -> text_input::Style {
text_input::Style {
background: SURFACE.into(),
- border_radius: 2,
- border_width: 0,
+ border_radius: 2.0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
}
}
fn focused(&self) -> text_input::Style {
text_input::Style {
- border_width: 1,
+ border_width: 1.0,
border_color: ACCENT,
..self.active()
}
@@ -350,7 +376,7 @@ mod style {
fn hovered(&self) -> text_input::Style {
text_input::Style {
- border_width: 1,
+ border_width: 1.0,
border_color: Color { a: 0.3, ..ACCENT },
..self.focused()
}
@@ -375,7 +401,7 @@ mod style {
fn active(&self) -> button::Style {
button::Style {
background: ACTIVE.into(),
- border_radius: 3,
+ border_radius: 3.0,
text_color: Color::WHITE,
..button::Style::default()
}
@@ -391,7 +417,7 @@ mod style {
fn pressed(&self) -> button::Style {
button::Style {
- border_width: 1,
+ border_width: 1.0,
border_color: Color::WHITE,
..self.hovered()
}
@@ -404,13 +430,13 @@ mod style {
fn active(&self) -> scrollable::Scrollbar {
scrollable::Scrollbar {
background: SURFACE.into(),
- border_radius: 2,
- border_width: 0,
+ border_radius: 2.0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
scroller: scrollable::Scroller {
color: ACTIVE,
- border_radius: 2,
- border_width: 0,
+ border_radius: 2.0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
},
}
@@ -449,9 +475,9 @@ mod style {
slider::Style {
rail_colors: (ACTIVE, Color { a: 0.1, ..ACTIVE }),
handle: slider::Handle {
- shape: slider::HandleShape::Circle { radius: 9 },
+ shape: slider::HandleShape::Circle { radius: 9.0 },
color: ACTIVE,
- border_width: 0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
},
}
@@ -489,7 +515,7 @@ mod style {
progress_bar::Style {
background: SURFACE.into(),
bar: ACTIVE.into(),
- border_radius: 10,
+ border_radius: 10.0,
}
}
}
@@ -502,8 +528,8 @@ mod style {
background: if is_checked { ACTIVE } else { SURFACE }
.into(),
checkmark_color: Color::WHITE,
- border_radius: 2,
- border_width: 1,
+ border_radius: 2.0,
+ border_width: 1.0,
border_color: ACTIVE,
}
}
@@ -520,6 +546,35 @@ mod style {
}
}
+ pub struct Toggler;
+
+ impl toggler::StyleSheet for Toggler {
+ fn active(&self, is_active: bool) -> toggler::Style {
+ toggler::Style {
+ background: if is_active { ACTIVE } else { SURFACE },
+ background_border: None,
+ foreground: if is_active { Color::WHITE } else { ACTIVE },
+ foreground_border: None,
+ }
+ }
+
+ fn hovered(&self, is_active: bool) -> toggler::Style {
+ toggler::Style {
+ background: if is_active { ACTIVE } else { SURFACE },
+ background_border: None,
+ foreground: if is_active {
+ Color {
+ a: 0.5,
+ ..Color::WHITE
+ }
+ } else {
+ Color { a: 0.5, ..ACTIVE }
+ },
+ foreground_border: None,
+ }
+ }
+ }
+
pub struct Rule;
impl rule::StyleSheet for Rule {
@@ -527,7 +582,7 @@ mod style {
rule::Style {
color: SURFACE,
width: 2,
- radius: 1,
+ radius: 1.0,
fill_mode: rule::FillMode::Padded(15),
}
}
diff --git a/examples/todos/Cargo.toml b/examples/todos/Cargo.toml
index b236cc0d..c8926c33 100644
--- a/examples/todos/Cargo.toml
+++ b/examples/todos/Cargo.toml
@@ -12,7 +12,7 @@ serde_json = "1.0"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
async-std = "1.0"
-directories = "2.0"
+directories-next = "2.0"
[target.'cfg(target_arch = "wasm32")'.dependencies]
web-sys = { version = "0.3", features = ["Window", "Storage"] }
diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs
index 7a546815..97415475 100644
--- a/examples/todos/src/main.rs
+++ b/examples/todos/src/main.rs
@@ -1,7 +1,7 @@
use iced::{
button, scrollable, text_input, Align, Application, Button, Checkbox,
- Column, Command, Container, Element, Font, HorizontalAlignment, Length,
- Row, Scrollable, Settings, Text, TextInput,
+ Clipboard, Column, Command, Container, Element, Font, HorizontalAlignment,
+ Length, Row, Scrollable, Settings, Text, TextInput,
};
use serde::{Deserialize, Serialize};
@@ -58,7 +58,11 @@ impl Application for Todos {
format!("Todos{} - Iced", if dirty { "*" } else { "" })
}
- fn update(&mut self, message: Message) -> Command<Message> {
+ fn update(
+ &mut self,
+ message: Message,
+ _clipboard: &mut Clipboard,
+ ) -> Command<Message> {
match self {
Todos::Loading => {
match message {
@@ -261,8 +265,11 @@ impl Task {
self.completed = completed;
}
TaskMessage::Edit => {
+ let mut text_input = text_input::State::focused();
+ text_input.select_all();
+
self.state = TaskState::Editing {
- text_input: text_input::State::focused(),
+ text_input,
delete_button: button::State::new(),
};
}
@@ -489,7 +496,6 @@ enum LoadError {
#[derive(Debug, Clone)]
enum SaveError {
- DirectoryError,
FileError,
WriteError,
FormatError,
@@ -499,7 +505,7 @@ enum SaveError {
impl SavedState {
fn path() -> std::path::PathBuf {
let mut path = if let Some(project_dirs) =
- directories::ProjectDirs::from("rs", "Iced", "Todos")
+ directories_next::ProjectDirs::from("rs", "Iced", "Todos")
{
project_dirs.data_dir().into()
} else {
@@ -538,7 +544,7 @@ impl SavedState {
if let Some(dir) = path.parent() {
async_std::fs::create_dir_all(dir)
.await
- .map_err(|_| SaveError::DirectoryError)?;
+ .map_err(|_| SaveError::FileError)?;
}
{
@@ -611,7 +617,7 @@ mod style {
background: Some(Background::Color(
Color::from_rgb(0.2, 0.2, 0.7),
)),
- border_radius: 10,
+ border_radius: 10.0,
text_color: Color::WHITE,
..button::Style::default()
}
@@ -627,7 +633,7 @@ mod style {
background: Some(Background::Color(Color::from_rgb(
0.8, 0.2, 0.2,
))),
- border_radius: 5,
+ border_radius: 5.0,
text_color: Color::WHITE,
shadow_offset: Vector::new(1.0, 1.0),
..button::Style::default()
diff --git a/examples/tooltip/Cargo.toml b/examples/tooltip/Cargo.toml
new file mode 100644
index 00000000..1171de00
--- /dev/null
+++ b/examples/tooltip/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "tooltip"
+version = "0.1.0"
+authors = ["Yusuf Bera Ertan <y.bera003.06@protonmail.com>"]
+edition = "2018"
+publish = false
+
+[dependencies]
+iced = { path = "../..", features = ["debug"] }
diff --git a/examples/tooltip/README.md b/examples/tooltip/README.md
new file mode 100644
index 00000000..4ccf6578
--- /dev/null
+++ b/examples/tooltip/README.md
@@ -0,0 +1,14 @@
+## Tooltip
+
+A tooltip.
+
+It displays and positions a widget on another based on cursor position.
+
+The __[`main`]__ file contains all the code of the example.
+
+You can run it with `cargo run`:
+```
+cargo run --package tooltip
+```
+
+[`main`]: src/main.rs
diff --git a/examples/tooltip/src/main.rs b/examples/tooltip/src/main.rs
new file mode 100644
index 00000000..d6c8b8e1
--- /dev/null
+++ b/examples/tooltip/src/main.rs
@@ -0,0 +1,138 @@
+use iced::tooltip::{self, Tooltip};
+use iced::{
+ button, Button, Column, Container, Element, HorizontalAlignment, Length,
+ Row, Sandbox, Settings, Text, VerticalAlignment,
+};
+
+pub fn main() {
+ Example::run(Settings::default()).unwrap()
+}
+
+#[derive(Default)]
+struct Example {
+ top: button::State,
+ bottom: button::State,
+ right: button::State,
+ left: button::State,
+ follow_cursor: button::State,
+}
+
+#[derive(Debug, Clone, Copy)]
+struct Message;
+
+impl Sandbox for Example {
+ type Message = Message;
+
+ fn new() -> Self {
+ Self::default()
+ }
+
+ fn title(&self) -> String {
+ String::from("Tooltip - Iced")
+ }
+
+ fn update(&mut self, _message: Message) {}
+
+ fn view(&mut self) -> Element<Message> {
+ let top =
+ tooltip("Tooltip at top", &mut self.top, tooltip::Position::Top);
+
+ let bottom = tooltip(
+ "Tooltip at bottom",
+ &mut self.bottom,
+ tooltip::Position::Bottom,
+ );
+
+ let left =
+ tooltip("Tooltip at left", &mut self.left, tooltip::Position::Left);
+
+ let right = tooltip(
+ "Tooltip at right",
+ &mut self.right,
+ tooltip::Position::Right,
+ );
+
+ let fixed_tooltips = Row::with_children(vec![
+ top.into(),
+ bottom.into(),
+ left.into(),
+ right.into(),
+ ])
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .align_items(iced::Align::Center)
+ .spacing(50);
+
+ let follow_cursor = tooltip(
+ "Tooltip follows cursor",
+ &mut self.follow_cursor,
+ tooltip::Position::FollowCursor,
+ );
+
+ let content = Column::with_children(vec![
+ Container::new(fixed_tooltips)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .center_x()
+ .center_y()
+ .into(),
+ follow_cursor.into(),
+ ])
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .spacing(50);
+
+ Container::new(content)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .center_x()
+ .center_y()
+ .padding(50)
+ .into()
+ }
+}
+
+fn tooltip<'a>(
+ label: &str,
+ button_state: &'a mut button::State,
+ position: tooltip::Position,
+) -> Element<'a, Message> {
+ Tooltip::new(
+ Button::new(
+ button_state,
+ Text::new(label)
+ .size(40)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .horizontal_alignment(HorizontalAlignment::Center)
+ .vertical_alignment(VerticalAlignment::Center),
+ )
+ .on_press(Message)
+ .width(Length::Fill)
+ .height(Length::Fill),
+ "Tooltip",
+ position,
+ )
+ .gap(5)
+ .padding(10)
+ .style(style::Tooltip)
+ .into()
+}
+
+mod style {
+ use iced::container;
+ use iced::Color;
+
+ pub struct Tooltip;
+
+ impl container::StyleSheet for Tooltip {
+ fn style(&self) -> container::Style {
+ container::Style {
+ text_color: Some(Color::from_rgb8(0xEE, 0xEE, 0xEE)),
+ background: Some(Color::from_rgb(0.11, 0.42, 0.87).into()),
+ border_radius: 12.0,
+ ..container::Style::default()
+ }
+ }
+ }
+}
diff --git a/examples/tour/Cargo.toml b/examples/tour/Cargo.toml
index 96749e90..bc7fac11 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.7"
+env_logger = "0.8"
diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs
index ec464801..1215f83d 100644
--- a/examples/tour/src/main.rs
+++ b/examples/tour/src/main.rs
@@ -1,7 +1,7 @@
use iced::{
button, scrollable, slider, text_input, Button, Checkbox, Color, Column,
Container, Element, HorizontalAlignment, Image, Length, Radio, Row,
- Sandbox, Scrollable, Settings, Slider, Space, Text, TextInput,
+ Sandbox, Scrollable, Settings, Slider, Space, Text, TextInput, Toggler,
};
pub fn main() -> iced::Result {
@@ -135,6 +135,9 @@ impl Steps {
color: Color::BLACK,
},
Step::Radio { selection: None },
+ Step::Toggler {
+ can_continue: false,
+ },
Step::Image {
width: 300,
slider: slider::State::new(),
@@ -206,6 +209,9 @@ enum Step {
Radio {
selection: Option<Language>,
},
+ Toggler {
+ can_continue: bool,
+ },
Image {
width: u16,
slider: slider::State,
@@ -232,6 +238,7 @@ pub enum StepMessage {
InputChanged(String),
ToggleSecureInput(bool),
DebugToggled(bool),
+ TogglerChanged(bool),
}
impl<'a> Step {
@@ -287,6 +294,11 @@ impl<'a> Step {
*is_secure = toggle;
}
}
+ StepMessage::TogglerChanged(value) => {
+ if let Step::Toggler { can_continue, .. } = self {
+ *can_continue = value;
+ }
+ }
};
}
@@ -294,6 +306,7 @@ impl<'a> Step {
match self {
Step::Welcome => "Welcome",
Step::Radio { .. } => "Radio button",
+ Step::Toggler { .. } => "Toggler",
Step::Slider { .. } => "Slider",
Step::Text { .. } => "Text",
Step::Image { .. } => "Image",
@@ -309,6 +322,7 @@ impl<'a> Step {
match self {
Step::Welcome => true,
Step::Radio { selection } => *selection == Some(Language::Rust),
+ Step::Toggler { can_continue } => *can_continue,
Step::Slider { .. } => true,
Step::Text { .. } => true,
Step::Image { .. } => true,
@@ -324,6 +338,7 @@ impl<'a> Step {
match self {
Step::Welcome => Self::welcome(),
Step::Radio { selection } => Self::radio(*selection),
+ Step::Toggler { can_continue } => Self::toggler(*can_continue),
Step::Slider { state, value } => Self::slider(state, *value),
Step::Text {
size_slider,
@@ -545,6 +560,21 @@ impl<'a> Step {
))
}
+ fn toggler(can_continue: bool) -> Column<'a, StepMessage> {
+ Self::container("Toggler")
+ .push(Text::new(
+ "A toggler is mostly used to enable or disable something.",
+ ))
+ .push(
+ Container::new(Toggler::new(
+ can_continue,
+ String::from("Toggle me to continue..."),
+ StepMessage::TogglerChanged,
+ ))
+ .padding([0, 40]),
+ )
+ }
+
fn image(
width: u16,
slider: &'a mut slider::State,
@@ -689,7 +719,7 @@ fn ferris<'a>(width: u16) -> Container<'a, StepMessage> {
.center_x()
}
-fn button<'a, Message>(
+fn button<'a, Message: Clone>(
state: &'a mut button::State,
label: &str,
) -> Button<'a, Message> {
@@ -769,7 +799,7 @@ mod style {
Button::Primary => Color::from_rgb(0.11, 0.42, 0.87),
Button::Secondary => Color::from_rgb(0.5, 0.5, 0.5),
})),
- border_radius: 12,
+ border_radius: 12.0,
shadow_offset: Vector::new(1.0, 1.0),
text_color: Color::from_rgb8(0xEE, 0xEE, 0xEE),
..button::Style::default()
diff --git a/examples/url_handler/Cargo.toml b/examples/url_handler/Cargo.toml
new file mode 100644
index 00000000..911b2f25
--- /dev/null
+++ b/examples/url_handler/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "url_handler"
+version = "0.1.0"
+authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
+edition = "2018"
+publish = false
+
+[dependencies]
+iced = { path = "../.." }
+iced_native = { path = "../../native" } \ No newline at end of file
diff --git a/examples/url_handler/src/main.rs b/examples/url_handler/src/main.rs
new file mode 100644
index 00000000..f14e5227
--- /dev/null
+++ b/examples/url_handler/src/main.rs
@@ -0,0 +1,73 @@
+use iced::{
+ executor, Application, Clipboard, Command, Container, Element, Length,
+ Settings, Subscription, Text,
+};
+use iced_native::{
+ event::{MacOS, PlatformSpecific},
+ Event,
+};
+
+pub fn main() -> iced::Result {
+ App::run(Settings::default())
+}
+
+#[derive(Debug, Default)]
+struct App {
+ url: Option<String>,
+}
+
+#[derive(Debug, Clone)]
+enum Message {
+ EventOccurred(iced_native::Event),
+}
+
+impl Application for App {
+ type Executor = executor::Default;
+ type Message = Message;
+ type Flags = ();
+
+ fn new(_flags: ()) -> (App, Command<Message>) {
+ (App::default(), Command::none())
+ }
+
+ fn title(&self) -> String {
+ String::from("Url - Iced")
+ }
+
+ fn update(
+ &mut self,
+ message: Message,
+ _clipboard: &mut Clipboard,
+ ) -> Command<Message> {
+ match message {
+ Message::EventOccurred(event) => {
+ if let Event::PlatformSpecific(PlatformSpecific::MacOS(
+ MacOS::ReceivedUrl(url),
+ )) = event
+ {
+ self.url = Some(url);
+ }
+ }
+ };
+
+ Command::none()
+ }
+
+ fn subscription(&self) -> Subscription<Message> {
+ iced_native::subscription::events().map(Message::EventOccurred)
+ }
+
+ fn view(&mut self) -> Element<Message> {
+ let content = match &self.url {
+ Some(url) => Text::new(format!("{}", url)),
+ None => Text::new("No URL received yet!"),
+ };
+
+ Container::new(content.size(48))
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .center_x()
+ .center_y()
+ .into()
+ }
+}