Merge remote-tracking branch 'origin/master' into feat/multi-window-support

# Conflicts:
#	Cargo.toml
#	core/src/window/icon.rs
#	core/src/window/id.rs
#	core/src/window/position.rs
#	core/src/window/settings.rs
#	examples/integration/src/main.rs
#	examples/integration_opengl/src/main.rs
#	glutin/src/application.rs
#	native/src/subscription.rs
#	native/src/window.rs
#	runtime/src/window/action.rs
#	src/lib.rs
#	src/window.rs
#	winit/Cargo.toml
#	winit/src/application.rs
#	winit/src/icon.rs
#	winit/src/settings.rs
#	winit/src/window.rs
This commit is contained in:
Bingus 2023-07-12 12:23:18 -07:00
commit 633f405f3f
No known key found for this signature in database
GPG key ID: 5F84D2AA40A9F170
394 changed files with 17278 additions and 13290 deletions

View file

@ -93,8 +93,7 @@ A bunch of simpler examples exist:
- [`download_progress`](download_progress), a basic application that asynchronously downloads a dummy file of 100 MB and tracks the download progress.
- [`events`](events), a log of native events displayed using a conditional `Subscription`.
- [`geometry`](geometry), a custom widget showcasing how to draw geometry with the `Mesh2D` primitive in [`iced_wgpu`](../wgpu).
- [`integration_opengl`](integration_opengl), a demonstration of how to integrate Iced in an existing OpenGL application.
- [`integration_wgpu`](integration_wgpu), a demonstration of how to integrate Iced in an existing [`wgpu`] application.
- [`integration`](integration), a demonstration of how to integrate Iced in an existing [`wgpu`] application.
- [`pane_grid`](pane_grid), a grid of panes that can be split, resized, and reorganized.
- [`pick_list`](pick_list), a dropdown list of selectable options.
- [`pokedex`](pokedex), an application that displays a random Pokédex entry (sprite included!) by using the [PokéAPI].

View file

@ -1,12 +1,13 @@
use std::{f32::consts::PI, time::Instant};
use iced::executor;
use iced::mouse;
use iced::widget::canvas::{
self, stroke, Cache, Canvas, Cursor, Geometry, Path, Stroke,
self, stroke, Cache, Canvas, Geometry, Path, Stroke,
};
use iced::{
Application, Command, Element, Length, Point, Rectangle, Settings,
Subscription, Theme,
Application, Command, Element, Length, Point, Rectangle, Renderer,
Settings, Subscription, Theme,
};
pub fn main() -> iced::Result {
@ -75,11 +76,12 @@ impl<Message> canvas::Program<Message> for Arc {
fn draw(
&self,
_state: &Self::State,
renderer: &Renderer,
theme: &Theme,
bounds: Rectangle,
_cursor: Cursor,
_cursor: mouse::Cursor,
) -> Vec<Geometry> {
let geometry = self.cache.draw(bounds.size(), |frame| {
let geometry = self.cache.draw(renderer, bounds.size(), |frame| {
let palette = theme.palette();
let center = frame.center();

View file

@ -61,10 +61,8 @@ impl Sandbox for Example {
mod bezier {
use iced::mouse;
use iced::widget::canvas::event::{self, Event};
use iced::widget::canvas::{
self, Canvas, Cursor, Frame, Geometry, Path, Stroke,
};
use iced::{Element, Length, Point, Rectangle, Theme};
use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path, Stroke};
use iced::{Element, Length, Point, Rectangle, Renderer, Theme};
#[derive(Default)]
pub struct State {
@ -100,10 +98,10 @@ mod bezier {
state: &mut Self::State,
event: Event,
bounds: Rectangle,
cursor: Cursor,
cursor: mouse::Cursor,
) -> (event::Status, Option<Curve>) {
let cursor_position =
if let Some(position) = cursor.position_in(&bounds) {
if let Some(position) = cursor.position_in(bounds) {
position
} else {
return (event::Status::Ignored, None);
@ -152,22 +150,26 @@ mod bezier {
fn draw(
&self,
state: &Self::State,
renderer: &Renderer,
_theme: &Theme,
bounds: Rectangle,
cursor: Cursor,
cursor: mouse::Cursor,
) -> Vec<Geometry> {
let content =
self.state.cache.draw(bounds.size(), |frame: &mut Frame| {
let content = self.state.cache.draw(
renderer,
bounds.size(),
|frame: &mut Frame| {
Curve::draw_all(self.curves, frame);
frame.stroke(
&Path::rectangle(Point::ORIGIN, frame.size()),
Stroke::default().with_width(2.0),
);
});
},
);
if let Some(pending) = state {
let pending_curve = pending.draw(bounds, cursor);
let pending_curve = pending.draw(renderer, bounds, cursor);
vec![content, pending_curve]
} else {
@ -179,9 +181,9 @@ mod bezier {
&self,
_state: &Self::State,
bounds: Rectangle,
cursor: Cursor,
cursor: mouse::Cursor,
) -> mouse::Interaction {
if cursor.is_over(&bounds) {
if cursor.is_over(bounds) {
mouse::Interaction::Crosshair
} else {
mouse::Interaction::default()
@ -216,10 +218,15 @@ mod bezier {
}
impl Pending {
fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Geometry {
let mut frame = Frame::new(bounds.size());
fn draw(
&self,
renderer: &Renderer,
bounds: Rectangle,
cursor: mouse::Cursor,
) -> Geometry {
let mut frame = Frame::new(renderer, bounds.size());
if let Some(cursor_position) = cursor.position_in(&bounds) {
if let Some(cursor_position) = cursor.position_in(bounds) {
match *self {
Pending::One { from } => {
let line = Path::line(from, cursor_position);

Binary file not shown.

View file

@ -1,10 +1,9 @@
use iced::widget::{checkbox, column, container};
use iced::{Element, Font, Length, Sandbox, Settings};
use iced::executor;
use iced::font::{self, Font};
use iced::widget::{checkbox, column, container, text};
use iced::{Application, Command, Element, Length, Settings, Theme};
const ICON_FONT: Font = Font::External {
name: "Icons",
bytes: include_bytes!("../fonts/icons.ttf"),
};
const ICON_FONT: Font = Font::with_name("icons");
pub fn main() -> iced::Result {
Example::run(Settings::default())
@ -20,24 +19,35 @@ struct Example {
enum Message {
DefaultChecked(bool),
CustomChecked(bool),
FontLoaded(Result<(), font::Error>),
}
impl Sandbox for Example {
impl Application for Example {
type Message = Message;
type Flags = ();
type Executor = executor::Default;
type Theme = Theme;
fn new() -> Self {
Default::default()
fn new(_flags: Self::Flags) -> (Self, Command<Message>) {
(
Self::default(),
font::load(include_bytes!("../fonts/icons.ttf").as_slice())
.map(Message::FontLoaded),
)
}
fn title(&self) -> String {
String::from("Checkbox - Iced")
}
fn update(&mut self, message: Message) {
fn update(&mut self, message: Message) -> Command<Message> {
match message {
Message::DefaultChecked(value) => self.default_checkbox = value,
Message::CustomChecked(value) => self.custom_checkbox = value,
Message::FontLoaded(_) => (),
}
Command::none()
}
fn view(&self) -> Element<Message> {
@ -49,6 +59,8 @@ impl Sandbox for Example {
font: ICON_FONT,
code_point: '\u{e901}',
size: None,
line_height: text::LineHeight::Relative(1.0),
shaping: text::Shaping::Basic,
});
let content = column![default_checkbox, custom_checkbox].spacing(22);

View file

@ -1,11 +1,10 @@
use iced::executor;
use iced::widget::canvas::{
stroke, Cache, Cursor, Geometry, LineCap, Path, Stroke,
};
use iced::mouse;
use iced::widget::canvas::{stroke, Cache, Geometry, LineCap, Path, Stroke};
use iced::widget::{canvas, container};
use iced::{
Application, Color, Command, Element, Length, Point, Rectangle, Settings,
Subscription, Theme, Vector,
Application, Color, Command, Element, Length, Point, Rectangle, Renderer,
Settings, Subscription, Theme, Vector,
};
pub fn main() -> iced::Result {
@ -83,17 +82,18 @@ impl Application for Clock {
}
}
impl<Message> canvas::Program<Message> for Clock {
impl<Message> canvas::Program<Message, Renderer> for Clock {
type State = ();
fn draw(
&self,
_state: &Self::State,
renderer: &Renderer,
_theme: &Theme,
bounds: Rectangle,
_cursor: Cursor,
_cursor: mouse::Cursor,
) -> Vec<Geometry> {
let clock = self.clock.draw(bounds.size(), |frame| {
let clock = self.clock.draw(renderer, bounds.size(), |frame| {
let center = frame.center();
let radius = frame.width().min(frame.height()) / 2.0;

View file

@ -7,4 +7,4 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["canvas", "palette"] }
palette = "0.6.0"
palette = "0.7.0"

View file

@ -1,10 +1,14 @@
use iced::widget::canvas::{self, Canvas, Cursor, Frame, Geometry, Path};
use iced::alignment::{self, Alignment};
use iced::mouse;
use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path};
use iced::widget::{column, row, text, Slider};
use iced::{
alignment, Alignment, Color, Element, Length, Point, Rectangle, Sandbox,
Settings, Size, Vector,
Color, Element, Length, Point, Rectangle, Renderer, Sandbox, Settings,
Size, Vector,
};
use palette::{
self, convert::FromColor, rgb::Rgb, Darken, Hsl, Lighten, ShiftHue,
};
use palette::{self, convert::FromColor, Hsl, Srgb};
use std::marker::PhantomData;
use std::ops::RangeInclusive;
@ -49,12 +53,12 @@ impl Sandbox for ColorPalette {
fn update(&mut self, message: Message) {
let srgb = match message {
Message::RgbColorChanged(rgb) => palette::Srgb::from(rgb),
Message::HslColorChanged(hsl) => palette::Srgb::from_color(hsl),
Message::HsvColorChanged(hsv) => palette::Srgb::from_color(hsv),
Message::HwbColorChanged(hwb) => palette::Srgb::from_color(hwb),
Message::LabColorChanged(lab) => palette::Srgb::from_color(lab),
Message::LchColorChanged(lch) => palette::Srgb::from_color(lch),
Message::RgbColorChanged(rgb) => Rgb::from(rgb),
Message::HslColorChanged(hsl) => Rgb::from_color(hsl),
Message::HsvColorChanged(hsv) => Rgb::from_color(hsv),
Message::HwbColorChanged(hwb) => Rgb::from_color(hwb),
Message::LabColorChanged(lab) => Rgb::from_color(lab),
Message::LchColorChanged(lch) => Rgb::from_color(lch),
};
self.theme = Theme::new(srgb);
@ -63,7 +67,7 @@ impl Sandbox for ColorPalette {
fn view(&self) -> Element<Message> {
let base = self.theme.base;
let srgb = palette::Srgb::from(base);
let srgb = Rgb::from(base);
let hsl = palette::Hsl::from_color(srgb);
let hsv = palette::Hsv::from_color(srgb);
let hwb = palette::Hwb::from_color(srgb);
@ -95,12 +99,10 @@ struct Theme {
impl Theme {
pub fn new(base: impl Into<Color>) -> Theme {
use palette::{Hue, Shade};
let base = base.into();
// Convert to HSL color for manipulation
let hsl = Hsl::from_color(Srgb::from(base));
let hsl = Hsl::from_color(Rgb::from(base));
let lower = [
hsl.shift_hue(-135.0).lighten(0.075),
@ -119,12 +121,12 @@ impl Theme {
Theme {
lower: lower
.iter()
.map(|&color| Srgb::from_color(color).into())
.map(|&color| Rgb::from_color(color).into())
.collect(),
base,
higher: higher
.iter()
.map(|&color| Srgb::from_color(color).into())
.map(|&color| Rgb::from_color(color).into())
.collect(),
canvas_cache: canvas::Cache::default(),
}
@ -209,14 +211,14 @@ impl Theme {
text.vertical_alignment = alignment::Vertical::Bottom;
let hsl = Hsl::from_color(Srgb::from(self.base));
let hsl = Hsl::from_color(Rgb::from(self.base));
for i in 0..self.len() {
let pct = (i as f32 + 1.0) / (self.len() as f32 + 1.0);
let graded = Hsl {
lightness: 1.0 - pct,
..hsl
};
let color: Color = Srgb::from_color(graded).into();
let color: Color = Rgb::from_color(graded).into();
let anchor = Point {
x: (i as f32) * box_size.width,
@ -243,11 +245,12 @@ impl<Message> canvas::Program<Message> for Theme {
fn draw(
&self,
_state: &Self::State,
renderer: &Renderer,
_theme: &iced::Theme,
bounds: Rectangle,
_cursor: Cursor,
_cursor: mouse::Cursor,
) -> Vec<Geometry> {
let theme = self.canvas_cache.draw(bounds.size(), |frame| {
let theme = self.canvas_cache.draw(renderer, bounds.size(), |frame| {
self.draw(frame);
});
@ -351,7 +354,7 @@ impl ColorSpace for palette::Hsl {
fn components(&self) -> [f32; 3] {
[
self.hue.to_positive_degrees(),
self.hue.into_positive_degrees(),
self.saturation,
self.lightness,
]
@ -360,7 +363,7 @@ impl ColorSpace for palette::Hsl {
fn to_string(&self) -> String {
format!(
"hsl({:.1}, {:.1}%, {:.1}%)",
self.hue.to_positive_degrees(),
self.hue.into_positive_degrees(),
100.0 * self.saturation,
100.0 * self.lightness
)
@ -377,13 +380,17 @@ impl ColorSpace for palette::Hsv {
}
fn components(&self) -> [f32; 3] {
[self.hue.to_positive_degrees(), self.saturation, self.value]
[
self.hue.into_positive_degrees(),
self.saturation,
self.value,
]
}
fn to_string(&self) -> String {
format!(
"hsv({:.1}, {:.1}%, {:.1}%)",
self.hue.to_positive_degrees(),
self.hue.into_positive_degrees(),
100.0 * self.saturation,
100.0 * self.value
)
@ -405,7 +412,7 @@ impl ColorSpace for palette::Hwb {
fn components(&self) -> [f32; 3] {
[
self.hue.to_positive_degrees(),
self.hue.into_positive_degrees(),
self.whiteness,
self.blackness,
]
@ -414,7 +421,7 @@ impl ColorSpace for palette::Hwb {
fn to_string(&self) -> String {
format!(
"hwb({:.1}, {:.1}%, {:.1}%)",
self.hue.to_positive_degrees(),
self.hue.into_positive_degrees(),
100.0 * self.whiteness,
100.0 * self.blackness
)
@ -449,7 +456,7 @@ impl ColorSpace for palette::Lch {
}
fn components(&self) -> [f32; 3] {
[self.l, self.chroma, self.hue.to_positive_degrees()]
[self.l, self.chroma, self.hue.into_positive_degrees()]
}
fn to_string(&self) -> String {
@ -457,7 +464,7 @@ impl ColorSpace for palette::Lch {
"Lch({:.1}, {:.1}, {:.1})",
self.l,
self.chroma,
self.hue.to_positive_degrees()
self.hue.into_positive_degrees()
)
}
}

View file

@ -6,6 +6,4 @@ edition = "2021"
publish = false
[dependencies]
iced = { path = "../..", features = ["debug"] }
iced_native = { path = "../../native" }
iced_lazy = { path = "../../lazy" }
iced = { path = "../..", features = ["debug", "lazy"] }

View file

@ -47,9 +47,8 @@ impl Sandbox for Component {
mod numeric_input {
use iced::alignment::{self, Alignment};
use iced::widget::{self, button, row, text, text_input};
use iced::{Element, Length};
use iced_lazy::{self, Component};
use iced::widget::{button, component, row, text, text_input, Component};
use iced::{Element, Length, Renderer};
pub struct NumericInput<Message> {
value: Option<u32>,
@ -82,13 +81,7 @@ mod numeric_input {
}
}
impl<Message, Renderer> Component<Message, Renderer> for NumericInput<Message>
where
Renderer: iced_native::text::Renderer + 'static,
Renderer::Theme: widget::button::StyleSheet
+ widget::text_input::StyleSheet
+ widget::text::StyleSheet,
{
impl<Message> Component<Message, Renderer> for NumericInput<Message> {
type State = ();
type Event = Event;
@ -141,8 +134,8 @@ mod numeric_input {
.map(u32::to_string)
.as_deref()
.unwrap_or(""),
Event::InputChanged,
)
.on_input(Event::InputChanged)
.padding(10),
button("+", Event::IncrementPressed),
]
@ -152,17 +145,12 @@ mod numeric_input {
}
}
impl<'a, Message, Renderer> From<NumericInput<Message>>
for Element<'a, Message, Renderer>
impl<'a, Message> From<NumericInput<Message>> for Element<'a, Message, Renderer>
where
Message: 'a,
Renderer: 'static + iced_native::text::Renderer,
Renderer::Theme: widget::button::StyleSheet
+ widget::text_input::StyleSheet
+ widget::text::StyleSheet,
{
fn from(numeric_input: NumericInput<Message>) -> Self {
iced_lazy::component(numeric_input)
component(numeric_input)
}
}
}

View file

@ -6,5 +6,4 @@ edition = "2021"
publish = false
[dependencies]
iced = { path = "../.." }
iced_native = { path = "../../native" }
iced = { path = "../..", features = ["advanced"] }

View file

@ -1,9 +1,10 @@
//! This example showcases a drawing a quad.
mod quad {
use iced_native::layout::{self, Layout};
use iced_native::renderer;
use iced_native::widget::{self, Widget};
use iced_native::{Color, Element, Length, Point, Rectangle, Size};
use iced::advanced::layout::{self, Layout};
use iced::advanced::renderer;
use iced::advanced::widget::{self, Widget};
use iced::mouse;
use iced::{Color, Element, Length, Rectangle, Size};
pub struct CustomQuad {
size: f32,
@ -48,7 +49,7 @@ mod quad {
_theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
renderer.fill_quad(

View file

@ -6,5 +6,4 @@ edition = "2021"
publish = false
[dependencies]
iced = { path = "../.." }
iced_native = { path = "../../native" }
iced = { path = "../..", features = ["advanced"] }

View file

@ -9,10 +9,11 @@ mod circle {
// Of course, you can choose to make the implementation renderer-agnostic,
// if you wish to, by creating your own `Renderer` trait, which could be
// implemented by `iced_wgpu` and other renderers.
use iced_native::layout::{self, Layout};
use iced_native::renderer;
use iced_native::widget::{self, Widget};
use iced_native::{Color, Element, Length, Point, Rectangle, Size};
use iced::advanced::layout::{self, Layout};
use iced::advanced::renderer;
use iced::advanced::widget::{self, Widget};
use iced::mouse;
use iced::{Color, Element, Length, Rectangle, Size};
pub struct Circle {
radius: f32,
@ -55,7 +56,7 @@ mod circle {
_theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
renderer.fill_quad(

View file

@ -7,8 +7,6 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["tokio"] }
iced_native = { path = "../../native" }
iced_futures = { path = "../../futures" }
[dependencies.reqwest]
version = "0.11"

View file

@ -1,4 +1,4 @@
use iced_native::subscription;
use iced::subscription;
use std::hash::Hash;
@ -18,10 +18,7 @@ pub struct Download<I> {
url: String,
}
async fn download<I: Copy>(
id: I,
state: State,
) -> (Option<(I, Progress)>, State) {
async fn download<I: Copy>(id: I, state: State) -> ((I, Progress), State) {
match state {
State::Ready(url) => {
let response = reqwest::get(&url).await;
@ -30,7 +27,7 @@ async fn download<I: Copy>(
Ok(response) => {
if let Some(total) = response.content_length() {
(
Some((id, Progress::Started)),
(id, Progress::Started),
State::Downloading {
response,
total,
@ -38,10 +35,10 @@ async fn download<I: Copy>(
},
)
} else {
(Some((id, Progress::Errored)), State::Finished)
((id, Progress::Errored), State::Finished)
}
}
Err(_) => (Some((id, Progress::Errored)), State::Finished),
Err(_) => ((id, Progress::Errored), State::Finished),
}
}
State::Downloading {
@ -55,7 +52,7 @@ async fn download<I: Copy>(
let percentage = (downloaded as f32 / total as f32) * 100.0;
(
Some((id, Progress::Advanced(percentage))),
(id, Progress::Advanced(percentage)),
State::Downloading {
response,
total,
@ -63,8 +60,8 @@ async fn download<I: Copy>(
},
)
}
Ok(None) => (Some((id, Progress::Finished)), State::Finished),
Err(_) => (Some((id, Progress::Errored)), State::Finished),
Ok(None) => ((id, Progress::Finished), State::Finished),
Err(_) => ((id, Progress::Errored), State::Finished),
},
State::Finished => {
// We do not let the stream die, as it would start a

View file

@ -7,4 +7,3 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["debug"] }
iced_native = { path = "../../native" }

View file

@ -1,12 +1,13 @@
use iced::alignment;
use iced::executor;
use iced::subscription;
use iced::widget::{button, checkbox, container, text, Column};
use iced::window;
use iced::Event;
use iced::{
Alignment, Application, Command, Element, Length, Settings, Subscription,
Theme,
};
use iced_native::Event;
pub fn main() -> iced::Result {
Events::run(Settings {
@ -17,13 +18,13 @@ pub fn main() -> iced::Result {
#[derive(Debug, Default)]
struct Events {
last: Vec<iced_native::Event>,
last: Vec<Event>,
enabled: bool,
}
#[derive(Debug, Clone)]
enum Message {
EventOccurred(iced_native::Event),
EventOccurred(Event),
Toggled(bool),
Exit(window::Id),
}
@ -70,7 +71,7 @@ impl Application for Events {
}
fn subscription(&self) -> Subscription<Message> {
iced_native::subscription::events().map(Message::EventOccurred)
subscription::events().map(Message::EventOccurred)
}
fn view(&self) -> Element<Message> {

View file

@ -145,7 +145,7 @@ impl Application for GameOfLife {
self.grid
.view()
.map(move |message| Message::Grid(message, version)),
controls
controls,
];
container(content)
@ -204,15 +204,14 @@ fn view_controls<'a>(
mod grid {
use crate::Preset;
use iced::alignment;
use iced::mouse;
use iced::touch;
use iced::widget::canvas;
use iced::widget::canvas::event::{self, Event};
use iced::widget::canvas::{
Cache, Canvas, Cursor, Frame, Geometry, Path, Text,
};
use iced::widget::canvas::{Cache, Canvas, Frame, Geometry, Path, Text};
use iced::{
alignment, mouse, Color, Element, Length, Point, Rectangle, Size,
Theme, Vector,
Color, Element, Length, Point, Rectangle, Renderer, Size, Theme, Vector,
};
use rustc_hash::{FxHashMap, FxHashSet};
use std::future::Future;
@ -401,14 +400,14 @@ mod grid {
interaction: &mut Interaction,
event: Event,
bounds: Rectangle,
cursor: Cursor,
cursor: mouse::Cursor,
) -> (event::Status, Option<Message>) {
if let Event::Mouse(mouse::Event::ButtonReleased(_)) = event {
*interaction = Interaction::None;
}
let cursor_position =
if let Some(position) = cursor.position_in(&bounds) {
if let Some(position) = cursor.position_in(bounds) {
position
} else {
return (event::Status::Ignored, None);
@ -536,13 +535,14 @@ mod grid {
fn draw(
&self,
_interaction: &Interaction,
renderer: &Renderer,
_theme: &Theme,
bounds: Rectangle,
cursor: Cursor,
cursor: mouse::Cursor,
) -> Vec<Geometry> {
let center = Vector::new(bounds.width / 2.0, bounds.height / 2.0);
let life = self.life_cache.draw(bounds.size(), |frame| {
let life = self.life_cache.draw(renderer, bounds.size(), |frame| {
let background = Path::rectangle(Point::ORIGIN, frame.size());
frame.fill(&background, Color::from_rgb8(0x40, 0x44, 0x4B));
@ -565,12 +565,11 @@ mod grid {
});
let overlay = {
let mut frame = Frame::new(bounds.size());
let mut frame = Frame::new(renderer, bounds.size());
let hovered_cell =
cursor.position_in(&bounds).map(|position| {
Cell::at(self.project(position, frame.size()))
});
let hovered_cell = cursor.position_in(bounds).map(|position| {
Cell::at(self.project(position, frame.size()))
});
if let Some(cell) = hovered_cell {
frame.with_save(|frame| {
@ -626,38 +625,40 @@ mod grid {
if self.scaling < 0.2 || !self.show_lines {
vec![life, overlay]
} else {
let grid = self.grid_cache.draw(bounds.size(), |frame| {
frame.translate(center);
frame.scale(self.scaling);
frame.translate(self.translation);
frame.scale(Cell::SIZE as f32);
let grid =
self.grid_cache.draw(renderer, bounds.size(), |frame| {
frame.translate(center);
frame.scale(self.scaling);
frame.translate(self.translation);
frame.scale(Cell::SIZE as f32);
let region = self.visible_region(frame.size());
let rows = region.rows();
let columns = region.columns();
let (total_rows, total_columns) =
(rows.clone().count(), columns.clone().count());
let width = 2.0 / Cell::SIZE as f32;
let color = Color::from_rgb8(70, 74, 83);
let region = self.visible_region(frame.size());
let rows = region.rows();
let columns = region.columns();
let (total_rows, total_columns) =
(rows.clone().count(), columns.clone().count());
let width = 2.0 / Cell::SIZE as f32;
let color = Color::from_rgb8(70, 74, 83);
frame.translate(Vector::new(-width / 2.0, -width / 2.0));
frame
.translate(Vector::new(-width / 2.0, -width / 2.0));
for row in region.rows() {
frame.fill_rectangle(
Point::new(*columns.start() as f32, row as f32),
Size::new(total_columns as f32, width),
color,
);
}
for row in region.rows() {
frame.fill_rectangle(
Point::new(*columns.start() as f32, row as f32),
Size::new(total_columns as f32, width),
color,
);
}
for column in region.columns() {
frame.fill_rectangle(
Point::new(column as f32, *rows.start() as f32),
Size::new(width, total_rows as f32),
color,
);
}
});
for column in region.columns() {
frame.fill_rectangle(
Point::new(column as f32, *rows.start() as f32),
Size::new(width, total_rows as f32),
color,
);
}
});
vec![life, grid, overlay]
}
@ -667,13 +668,13 @@ mod grid {
&self,
interaction: &Interaction,
bounds: Rectangle,
cursor: Cursor,
cursor: mouse::Cursor,
) -> mouse::Interaction {
match interaction {
Interaction::Drawing => mouse::Interaction::Crosshair,
Interaction::Erasing => mouse::Interaction::Crosshair,
Interaction::Panning { .. } => mouse::Interaction::Grabbing,
Interaction::None if cursor.is_over(&bounds) => {
Interaction::None if cursor.is_over(bounds) => {
mouse::Interaction::Crosshair
}
_ => mouse::Interaction::default(),

View file

@ -6,6 +6,4 @@ edition = "2021"
publish = false
[dependencies]
iced = { path = "../.." }
iced_native = { path = "../../native" }
iced_graphics = { path = "../../graphics" }
iced = { path = "../..", features = ["advanced"] }

View file

@ -1,24 +1,12 @@
//! This example showcases a simple native custom widget that renders using
//! arbitrary low-level geometry.
mod rainbow {
// For now, to implement a custom native widget you will need to add
// `iced_native` and `iced_wgpu` to your dependencies.
//
// Then, you simply need to define your widget type and implement the
// `iced_native::Widget` trait with the `iced_wgpu::Renderer`.
//
// Of course, you can choose to make the implementation renderer-agnostic,
// if you wish to, by creating your own `Renderer` trait, which could be
// implemented by `iced_wgpu` and other renderers.
use iced_graphics::renderer::{self, Renderer};
use iced_graphics::triangle::ColoredVertex2D;
use iced_graphics::{Backend, Primitive};
use iced_native::layout;
use iced_native::widget::{self, Widget};
use iced_native::{
Element, Layout, Length, Point, Rectangle, Size, Vector,
};
use iced::advanced::graphics::color;
use iced::advanced::layout::{self, Layout};
use iced::advanced::renderer;
use iced::advanced::widget::{self, Widget};
use iced::mouse;
use iced::{Element, Length, Rectangle, Renderer, Size, Theme, Vector};
#[derive(Debug, Clone, Copy, Default)]
pub struct Rainbow;
@ -27,10 +15,7 @@ mod rainbow {
Rainbow
}
impl<Message, B, T> Widget<Message, Renderer<B, T>> for Rainbow
where
B: Backend,
{
impl<Message> Widget<Message, Renderer> for Rainbow {
fn width(&self) -> Length {
Length::Fill
}
@ -41,7 +26,7 @@ mod rainbow {
fn layout(
&self,
_renderer: &Renderer<B, T>,
_renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let size = limits.width(Length::Fill).resolve(Size::ZERO);
@ -52,17 +37,17 @@ mod rainbow {
fn draw(
&self,
_tree: &widget::Tree,
renderer: &mut Renderer<B, T>,
_theme: &T,
renderer: &mut Renderer,
_theme: &Theme,
_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
use iced_graphics::triangle::Mesh2D;
use iced_native::Renderer as _;
use iced::advanced::graphics::mesh::{self, Mesh, SolidVertex2D};
use iced::advanced::Renderer as _;
let b = layout.bounds();
let bounds = layout.bounds();
// R O Y G B I V
let color_r = [1.0, 0.0, 0.0, 1.0];
@ -75,61 +60,61 @@ mod rainbow {
let color_v = [0.75, 0.0, 0.5, 1.0];
let posn_center = {
if b.contains(cursor_position) {
[cursor_position.x - b.x, cursor_position.y - b.y]
if let Some(cursor_position) = cursor.position_in(bounds) {
[cursor_position.x, cursor_position.y]
} else {
[b.width / 2.0, b.height / 2.0]
[bounds.width / 2.0, bounds.height / 2.0]
}
};
let posn_tl = [0.0, 0.0];
let posn_t = [b.width / 2.0, 0.0];
let posn_tr = [b.width, 0.0];
let posn_r = [b.width, b.height / 2.0];
let posn_br = [b.width, b.height];
let posn_b = [(b.width / 2.0), b.height];
let posn_bl = [0.0, b.height];
let posn_l = [0.0, b.height / 2.0];
let posn_t = [bounds.width / 2.0, 0.0];
let posn_tr = [bounds.width, 0.0];
let posn_r = [bounds.width, bounds.height / 2.0];
let posn_br = [bounds.width, bounds.height];
let posn_b = [(bounds.width / 2.0), bounds.height];
let posn_bl = [0.0, bounds.height];
let posn_l = [0.0, bounds.height / 2.0];
let mesh = Primitive::SolidMesh {
size: b.size(),
buffers: Mesh2D {
let mesh = Mesh::Solid {
size: bounds.size(),
buffers: mesh::Indexed {
vertices: vec![
ColoredVertex2D {
SolidVertex2D {
position: posn_center,
color: [1.0, 1.0, 1.0, 1.0],
color: color::pack([1.0, 1.0, 1.0, 1.0]),
},
ColoredVertex2D {
SolidVertex2D {
position: posn_tl,
color: color_r,
color: color::pack(color_r),
},
ColoredVertex2D {
SolidVertex2D {
position: posn_t,
color: color_o,
color: color::pack(color_o),
},
ColoredVertex2D {
SolidVertex2D {
position: posn_tr,
color: color_y,
color: color::pack(color_y),
},
ColoredVertex2D {
SolidVertex2D {
position: posn_r,
color: color_g,
color: color::pack(color_g),
},
ColoredVertex2D {
SolidVertex2D {
position: posn_br,
color: color_gb,
color: color::pack(color_gb),
},
ColoredVertex2D {
SolidVertex2D {
position: posn_b,
color: color_b,
color: color::pack(color_b),
},
ColoredVertex2D {
SolidVertex2D {
position: posn_bl,
color: color_i,
color: color::pack(color_i),
},
ColoredVertex2D {
SolidVertex2D {
position: posn_l,
color: color_v,
color: color::pack(color_v),
},
],
indices: vec![
@ -145,16 +130,16 @@ mod rainbow {
},
};
renderer.with_translation(Vector::new(b.x, b.y), |renderer| {
renderer.draw_primitive(mesh);
});
renderer.with_translation(
Vector::new(bounds.x, bounds.y),
|renderer| {
renderer.draw_mesh(mesh);
},
);
}
}
impl<'a, Message, B, T> From<Rainbow> for Element<'a, Message, Renderer<B, T>>
where
B: Backend,
{
impl<'a, Message> From<Rainbow> for Element<'a, Message, Renderer> {
fn from(rainbow: Rainbow) -> Self {
Self::new(rainbow)
}

View file

@ -1,5 +1,5 @@
[package]
name = "integration_wgpu"
name = "integration"
version = "0.1.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
@ -7,8 +7,10 @@ publish = false
[dependencies]
iced_winit = { path = "../../winit" }
iced_wgpu = { path = "../../wgpu", features = ["webgl"] }
env_logger = "0.8"
iced_wgpu = { path = "../../wgpu" }
iced_widget = { path = "../../widget" }
iced_renderer = { path = "../../renderer", features = ["wgpu"] }
env_logger = "0.10"
[target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1.7"

View file

@ -8,8 +8,8 @@
<h1>integration_wgpu</h1>
<canvas id="iced_canvas"></canvas>
<script type="module">
import init from "./integration_wgpu.js";
init('./integration_wgpu_bg.wasm');
import init from "./integration.js";
init('./integration_bg.wasm');
</script>
<style>
body {

View file

@ -1,6 +1,8 @@
use iced_wgpu::Renderer;
use iced_winit::widget::{slider, text_input, Column, Row, Text};
use iced_winit::{Alignment, Color, Command, Element, Length, Program};
use iced_widget::{slider, text_input, Column, Row, Text};
use iced_winit::core::{Alignment, Color, Element, Length};
use iced_winit::runtime::{Command, Program};
use iced_winit::style::Theme;
pub struct Controls {
background_color: Color,
@ -27,7 +29,7 @@ impl Controls {
}
impl Program for Controls {
type Renderer = Renderer;
type Renderer = Renderer<Theme>;
type Message = Message;
fn update(&mut self, message: Message) -> Command<Message> {
@ -43,7 +45,7 @@ impl Program for Controls {
Command::none()
}
fn view(&self) -> Element<Message, Renderer> {
fn view(&self) -> Element<Message, Renderer<Theme>> {
let background_color = self.background_color;
let text = &self.text;
@ -100,11 +102,10 @@ impl Program for Controls {
.size(14)
.style(Color::WHITE),
)
.push(text_input(
"Placeholder",
text,
Message::TextChanged,
)),
.push(
text_input("Placeholder", text)
.on_input(Message::TextChanged),
),
),
)
.into()

View file

@ -4,14 +4,17 @@ mod scene;
use controls::Controls;
use scene::Scene;
use iced_wgpu::{wgpu, Backend, Renderer, Settings, Viewport};
use iced_winit::{
conversion, futures, program, renderer, window, winit, Clipboard, Color,
Debug, Size,
};
use iced_wgpu::graphics::Viewport;
use iced_wgpu::{wgpu, Backend, Renderer, Settings};
use iced_winit::core::mouse;
use iced_winit::core::renderer;
use iced_winit::core::{Color, Size};
use iced_winit::runtime::program;
use iced_winit::runtime::Debug;
use iced_winit::style::Theme;
use iced_winit::{conversion, futures, winit, Clipboard};
use winit::{
dpi::PhysicalPosition,
event::{Event, ModifiersState, WindowEvent},
event_loop::{ControlFlow, EventLoop},
};
@ -23,19 +26,20 @@ use web_sys::HtmlCanvasElement;
#[cfg(target_arch = "wasm32")]
use winit::platform::web::WindowBuilderExtWebSys;
pub fn main() {
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
#[cfg(target_arch = "wasm32")]
let canvas_element = {
console_log::init_with_level(log::Level::Debug)
.expect("could not initialize logger");
console_log::init_with_level(log::Level::Debug)?;
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
web_sys::window()
.and_then(|win| win.document())
.and_then(|doc| doc.get_element_by_id("iced_canvas"))
.and_then(|element| element.dyn_into::<HtmlCanvasElement>().ok())
.expect("Canvas with id `iced_canvas` is missing")
.expect("Get canvas element")
};
#[cfg(not(target_arch = "wasm32"))]
env_logger::init();
@ -45,23 +49,21 @@ pub fn main() {
#[cfg(target_arch = "wasm32")]
let window = winit::window::WindowBuilder::new()
.with_canvas(Some(canvas_element))
.build(&event_loop)
.expect("Failed to build winit window");
.build(&event_loop)?;
#[cfg(not(target_arch = "wasm32"))]
let window = winit::window::Window::new(&event_loop).unwrap();
let window = winit::window::Window::new(&event_loop)?;
let physical_size = window.inner_size();
let mut viewport = Viewport::with_physical_size(
Size::new(physical_size.width, physical_size.height),
window.scale_factor(),
);
let mut cursor_position = PhysicalPosition::new(-1.0, -1.0);
let mut cursor_position = None;
let mut modifiers = ModifiersState::default();
let mut clipboard = Clipboard::connect(&window);
// Initialize wgpu
#[cfg(target_arch = "wasm32")]
let default_backend = wgpu::Backends::GL;
#[cfg(not(target_arch = "wasm32"))]
@ -70,46 +72,55 @@ pub fn main() {
let backend =
wgpu::util::backend_bits_from_env().unwrap_or(default_backend);
let instance = wgpu::Instance::new(backend);
let surface = unsafe { instance.create_surface(&window) };
let (format, (device, queue)) = futures::executor::block_on(async {
let adapter = wgpu::util::initialize_adapter_from_env_or_default(
&instance,
backend,
Some(&surface),
)
.await
.expect("No suitable GPU adapters found on the system!");
let adapter_features = adapter.features();
#[cfg(target_arch = "wasm32")]
let needed_limits = wgpu::Limits::downlevel_webgl2_defaults()
.using_resolution(adapter.limits());
#[cfg(not(target_arch = "wasm32"))]
let needed_limits = wgpu::Limits::default();
(
surface
.get_supported_formats(&adapter)
.first()
.copied()
.expect("Get preferred format"),
adapter
.request_device(
&wgpu::DeviceDescriptor {
label: None,
features: adapter_features & wgpu::Features::default(),
limits: needed_limits,
},
None,
)
.await
.expect("Request device"),
)
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: backend,
..Default::default()
});
let surface = unsafe { instance.create_surface(&window) }?;
let (format, (device, queue)) =
futures::futures::executor::block_on(async {
let adapter = wgpu::util::initialize_adapter_from_env_or_default(
&instance,
backend,
Some(&surface),
)
.await
.expect("Create adapter");
let adapter_features = adapter.features();
#[cfg(target_arch = "wasm32")]
let needed_limits = wgpu::Limits::downlevel_webgl2_defaults()
.using_resolution(adapter.limits());
#[cfg(not(target_arch = "wasm32"))]
let needed_limits = wgpu::Limits::default();
let capabilities = surface.get_capabilities(&adapter);
(
capabilities
.formats
.iter()
.copied()
.find(wgpu::TextureFormat::is_srgb)
.or_else(|| capabilities.formats.first().copied())
.expect("Get preferred format"),
adapter
.request_device(
&wgpu::DeviceDescriptor {
label: None,
features: adapter_features
& wgpu::Features::default(),
limits: needed_limits,
},
None,
)
.await
.expect("Request device"),
)
});
surface.configure(
&device,
@ -120,22 +131,24 @@ pub fn main() {
height: physical_size.height,
present_mode: wgpu::PresentMode::AutoVsync,
alpha_mode: wgpu::CompositeAlphaMode::Auto,
view_formats: vec![],
},
);
let mut resized = false;
// Initialize staging belt
let mut staging_belt = wgpu::util::StagingBelt::new(5 * 1024);
// Initialize scene and GUI controls
let scene = Scene::new(&device, format);
let controls = Controls::new();
// Initialize iced
let mut debug = Debug::new();
let mut renderer =
Renderer::new(Backend::new(&device, Settings::default(), format));
let mut renderer = Renderer::new(Backend::new(
&device,
&queue,
Settings::default(),
format,
));
let mut state = program::State::new(
controls,
@ -153,7 +166,7 @@ pub fn main() {
Event::WindowEvent { event, .. } => {
match event {
WindowEvent::CursorMoved { position, .. } => {
cursor_position = position;
cursor_position = Some(position);
}
WindowEvent::ModifiersChanged(new_modifiers) => {
modifiers = new_modifiers;
@ -183,13 +196,20 @@ pub fn main() {
// We update iced
let _ = state.update(
viewport.logical_size(),
conversion::cursor_position(
cursor_position,
viewport.scale_factor(),
),
cursor_position
.map(|p| {
conversion::cursor_position(
p,
viewport.scale_factor(),
)
})
.map(mouse::Cursor::Available)
.unwrap_or(mouse::Cursor::Unavailable),
&mut renderer,
&iced_wgpu::Theme::Dark,
&renderer::Style { text_color: Color::WHITE },
&Theme::Dark,
&renderer::Style {
text_color: Color::WHITE,
},
&mut clipboard,
&mut debug,
);
@ -215,7 +235,8 @@ pub fn main() {
width: size.width,
height: size.height,
present_mode: wgpu::PresentMode::AutoVsync,
alpha_mode: wgpu::CompositeAlphaMode::Auto
alpha_mode: wgpu::CompositeAlphaMode::Auto,
view_formats: vec![],
},
);
@ -230,7 +251,9 @@ pub fn main() {
let program = state.program();
let view = frame.texture.create_view(&wgpu::TextureViewDescriptor::default());
let view = frame.texture.create_view(
&wgpu::TextureViewDescriptor::default(),
);
{
// We clear the frame
@ -248,8 +271,9 @@ pub fn main() {
renderer.with_primitives(|backend, primitive| {
backend.present(
&device,
&mut staging_belt,
&queue,
&mut encoder,
None,
&view,
primitive,
&viewport,
@ -258,24 +282,22 @@ pub fn main() {
});
// Then we submit the work
staging_belt.finish();
queue.submit(Some(encoder.finish()));
frame.present();
// Update the mouse cursor
window.set_cursor_icon(
iced_winit::conversion::mouse_interaction(
state.mouse_interaction(),
),
);
// And recall staging buffers
staging_belt.recall();
window.set_cursor_icon(
iced_winit::conversion::mouse_interaction(
state.mouse_interaction(),
),
);
}
Err(error) => match error {
wgpu::SurfaceError::OutOfMemory => {
panic!("Swapchain error: {error}. Rendering cannot continue.")
panic!(
"Swapchain error: {error}. \
Rendering cannot continue."
)
}
_ => {
// Try rendering again next frame.

View file

@ -1,5 +1,5 @@
use iced_wgpu::wgpu;
use iced_winit::Color;
use iced_winit::core::Color;
pub struct Scene {
pipeline: wgpu::RenderPipeline,

View file

@ -1,12 +0,0 @@
[package]
name = "integration_opengl"
version = "0.1.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
publish = false
[dependencies]
iced_glutin = { path = "../../glutin" }
iced_glow = { path = "../../glow" }
iced_winit = { path = "../../winit" }
env_logger = "0.8"

View file

@ -1,16 +0,0 @@
## OpenGL integration
A demonstration of how to integrate Iced in an existing graphical OpenGL application.
The __[`main`]__ file contains all the code of the example.
<div align="center">
<a href="https://imgbox.com/9P9ETcod" target="_blank"><img src="https://images2.imgbox.com/2a/51/9P9ETcod_o.gif" alt="image host"/></a>
</div>
You can run it with `cargo run`:
```
cargo run --package integration_opengl
```
[`main`]: src/main.rs

View file

@ -1,101 +0,0 @@
use iced_glow::Renderer;
use iced_glutin::widget::Slider;
use iced_glutin::widget::{Column, Row, Text};
use iced_glutin::{Alignment, Color, Command, Element, Length, Program};
pub struct Controls {
background_color: Color,
}
#[derive(Debug, Clone)]
pub enum Message {
BackgroundColorChanged(Color),
}
impl Controls {
pub fn new() -> Controls {
Controls {
background_color: Color::BLACK,
}
}
pub fn background_color(&self) -> Color {
self.background_color
}
}
impl Program for Controls {
type Renderer = Renderer;
type Message = Message;
fn update(&mut self, message: Message) -> Command<Message> {
match message {
Message::BackgroundColorChanged(color) => {
self.background_color = color;
}
}
Command::none()
}
fn view(&self) -> Element<Message, Renderer> {
let background_color = self.background_color;
let sliders = Row::new()
.width(500)
.spacing(20)
.push(
Slider::new(0.0..=1.0, background_color.r, move |r| {
Message::BackgroundColorChanged(Color {
r,
..background_color
})
})
.step(0.01),
)
.push(
Slider::new(0.0..=1.0, background_color.g, move |g| {
Message::BackgroundColorChanged(Color {
g,
..background_color
})
})
.step(0.01),
)
.push(
Slider::new(0.0..=1.0, background_color.b, move |b| {
Message::BackgroundColorChanged(Color {
b,
..background_color
})
})
.step(0.01),
);
Row::new()
.width(Length::Fill)
.height(Length::Fill)
.align_items(Alignment::End)
.push(
Column::new()
.width(Length::Fill)
.align_items(Alignment::End)
.push(
Column::new()
.padding(10)
.spacing(10)
.push(
Text::new("Background color")
.style(Color::WHITE),
)
.push(sliders)
.push(
Text::new(format!("{background_color:?}"))
.size(14)
.style(Color::WHITE),
),
),
)
.into()
}
}

View file

@ -1,188 +0,0 @@
mod controls;
mod scene;
use controls::Controls;
use scene::Scene;
use glow::*;
use glutin::dpi::PhysicalPosition;
use glutin::event::{Event, ModifiersState, WindowEvent};
use glutin::event_loop::ControlFlow;
use iced_glow::glow;
use iced_glow::{Backend, Renderer, Settings, Viewport};
use iced_glutin::conversion;
use iced_glutin::glutin;
use iced_glutin::renderer;
use iced_glutin::{program, Clipboard, Color, Debug, Size};
pub fn main() {
env_logger::init();
let (gl, event_loop, windowed_context, shader_version) = {
let el = glutin::event_loop::EventLoop::new();
let wb = glutin::window::WindowBuilder::new()
.with_title("OpenGL integration example")
.with_inner_size(glutin::dpi::LogicalSize::new(1024.0, 768.0));
let windowed_context = glutin::ContextBuilder::new()
.with_vsync(true)
.build_windowed(wb, &el)
.unwrap();
unsafe {
let windowed_context = windowed_context.make_current().unwrap();
let gl = glow::Context::from_loader_function(|s| {
windowed_context.get_proc_address(s) as *const _
});
// Enable auto-conversion from/to sRGB
gl.enable(glow::FRAMEBUFFER_SRGB);
// Enable alpha blending
gl.enable(glow::BLEND);
gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA);
// Disable multisampling by default
gl.disable(glow::MULTISAMPLE);
(gl, el, windowed_context, "#version 410")
}
};
let physical_size = windowed_context.window().inner_size();
let mut viewport = Viewport::with_physical_size(
Size::new(physical_size.width, physical_size.height),
windowed_context.window().scale_factor(),
);
let mut cursor_position = PhysicalPosition::new(-1.0, -1.0);
let mut modifiers = ModifiersState::default();
let mut clipboard = Clipboard::connect(windowed_context.window());
let mut renderer = Renderer::new(Backend::new(&gl, Settings::default()));
let mut debug = Debug::new();
let controls = Controls::new();
let mut state = program::State::new(
controls,
viewport.logical_size(),
&mut renderer,
&mut debug,
);
let mut resized = false;
let scene = Scene::new(&gl, shader_version);
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent { event, .. } => {
match event {
WindowEvent::CursorMoved { position, .. } => {
cursor_position = position;
}
WindowEvent::ModifiersChanged(new_modifiers) => {
modifiers = new_modifiers;
}
WindowEvent::Resized(physical_size) => {
viewport = Viewport::with_physical_size(
Size::new(
physical_size.width,
physical_size.height,
),
windowed_context.window().scale_factor(),
);
resized = true;
}
WindowEvent::CloseRequested => {
scene.cleanup(&gl);
*control_flow = ControlFlow::Exit
}
_ => (),
}
// Map window event to iced event
if let Some(event) = iced_winit::conversion::window_event(
iced_winit::window::Id::MAIN,
&event,
windowed_context.window().scale_factor(),
modifiers,
) {
state.queue_event(event);
}
}
Event::MainEventsCleared => {
// If there are events pending
if !state.is_queue_empty() {
// We update iced
let _ = state.update(
viewport.logical_size(),
conversion::cursor_position(
cursor_position,
viewport.scale_factor(),
),
&mut renderer,
&iced_glow::Theme::Dark,
&renderer::Style {
text_color: Color::WHITE,
},
&mut clipboard,
&mut debug,
);
// and request a redraw
windowed_context.window().request_redraw();
}
}
Event::RedrawRequested(_) => {
if resized {
let size = windowed_context.window().inner_size();
unsafe {
gl.viewport(
0,
0,
size.width as i32,
size.height as i32,
);
}
resized = false;
}
let program = state.program();
{
// We clear the frame
scene.clear(&gl, program.background_color());
// Draw the scene
scene.draw(&gl);
}
// And then iced on top
renderer.with_primitives(|backend, primitive| {
backend.present(
&gl,
primitive,
&viewport,
&debug.overlay(),
);
});
// Update the mouse cursor
windowed_context.window().set_cursor_icon(
iced_winit::conversion::mouse_interaction(
state.mouse_interaction(),
),
);
windowed_context.swap_buffers().unwrap();
}
_ => (),
}
});
}

View file

@ -1,102 +0,0 @@
use glow::*;
use iced_glow::glow;
use iced_glow::Color;
pub struct Scene {
program: glow::Program,
vertex_array: glow::VertexArray,
}
impl Scene {
pub fn new(gl: &glow::Context, shader_version: &str) -> Self {
unsafe {
let vertex_array = gl
.create_vertex_array()
.expect("Cannot create vertex array");
gl.bind_vertex_array(Some(vertex_array));
let program = gl.create_program().expect("Cannot create program");
let (vertex_shader_source, fragment_shader_source) = (
r#"const vec2 verts[3] = vec2[3](
vec2(0.5f, 1.0f),
vec2(0.0f, 0.0f),
vec2(1.0f, 0.0f)
);
out vec2 vert;
void main() {
vert = verts[gl_VertexID];
gl_Position = vec4(vert - 0.5, 0.0, 1.0);
}"#,
r#"precision highp float;
in vec2 vert;
out vec4 color;
void main() {
color = vec4(vert, 0.5, 1.0);
}"#,
);
let shader_sources = [
(glow::VERTEX_SHADER, vertex_shader_source),
(glow::FRAGMENT_SHADER, fragment_shader_source),
];
let mut shaders = Vec::with_capacity(shader_sources.len());
for (shader_type, shader_source) in shader_sources.iter() {
let shader = gl
.create_shader(*shader_type)
.expect("Cannot create shader");
gl.shader_source(
shader,
&format!("{shader_version}\n{shader_source}"),
);
gl.compile_shader(shader);
if !gl.get_shader_compile_status(shader) {
panic!("{}", gl.get_shader_info_log(shader));
}
gl.attach_shader(program, shader);
shaders.push(shader);
}
gl.link_program(program);
if !gl.get_program_link_status(program) {
panic!("{}", gl.get_program_info_log(program));
}
for shader in shaders {
gl.detach_shader(program, shader);
gl.delete_shader(shader);
}
gl.use_program(Some(program));
Self {
program,
vertex_array,
}
}
}
pub fn clear(&self, gl: &glow::Context, background_color: Color) {
let [r, g, b, a] = background_color.into_linear();
unsafe {
gl.clear_color(r, g, b, a);
gl.clear(glow::COLOR_BUFFER_BIT);
}
}
pub fn draw(&self, gl: &glow::Context) {
unsafe {
gl.bind_vertex_array(Some(self.vertex_array));
gl.use_program(Some(self.program));
gl.draw_arrays(glow::TRIANGLES, 0, 3);
}
}
pub fn cleanup(&self, gl: &glow::Context) {
unsafe {
gl.delete_program(self.program);
gl.delete_vertex_array(self.vertex_array);
}
}
}

View file

@ -6,5 +6,4 @@ edition = "2021"
publish = false
[dependencies]
iced = { path = "../..", features = ["debug"] }
iced_lazy = { path = "../../lazy" }
iced = { path = "../..", features = ["debug", "lazy"] }

View file

@ -1,10 +1,9 @@
use iced::theme;
use iced::widget::{
button, column, horizontal_space, pick_list, row, scrollable, text,
button, column, horizontal_space, lazy, pick_list, row, scrollable, text,
text_input,
};
use iced::{Element, Length, Sandbox, Settings};
use iced_lazy::lazy;
use std::collections::HashSet;
use std::hash::Hash;
@ -214,12 +213,9 @@ impl Sandbox for App {
column![
scrollable(options).height(Length::Fill),
row![
text_input(
"Add a new option",
&self.input,
Message::InputChanged,
)
.on_submit(Message::AddItem(self.input.clone())),
text_input("Add a new option", &self.input)
.on_input(Message::InputChanged)
.on_submit(Message::AddItem(self.input.clone())),
button(text(format!("Toggle Order ({})", self.order)))
.on_press(Message::ToggleOrder)
]

View file

@ -0,0 +1,11 @@
[package]
name = "loading_spinners"
version = "0.1.0"
authors = ["Nick Senger <dev@nsenger.com>"]
edition = "2021"
publish = false
[dependencies]
iced = { path = "../..", features = ["advanced", "canvas"] }
lyon_algorithms = "1"
once_cell = "1"

View file

@ -0,0 +1,14 @@
## Loading Spinners
Example implementation of animated indeterminate loading spinners.
<div align="center">
<a href="https://gfycat.com/importantdevotedhammerheadbird">
<img src="https://thumbs.gfycat.com/ImportantDevotedHammerheadbird-small.gif">
</a>
</div>
You can run it with `cargo run`:
```
cargo run --package loading_spinners
```

View file

@ -0,0 +1,421 @@
//! Show a circular progress indicator.
use iced::advanced::layout;
use iced::advanced::renderer;
use iced::advanced::widget::tree::{self, Tree};
use iced::advanced::{Clipboard, Layout, Renderer, Shell, Widget};
use iced::event;
use iced::mouse;
use iced::time::Instant;
use iced::widget::canvas;
use iced::window::{self, RedrawRequest};
use iced::{
Background, Color, Element, Event, Length, Rectangle, Size, Vector,
};
use super::easing::{self, Easing};
use std::f32::consts::PI;
use std::time::Duration;
const MIN_RADIANS: f32 = PI / 8.0;
const WRAP_RADIANS: f32 = 2.0 * PI - PI / 4.0;
const BASE_ROTATION_SPEED: u32 = u32::MAX / 80;
#[allow(missing_debug_implementations)]
pub struct Circular<'a, Theme>
where
Theme: StyleSheet,
{
size: f32,
bar_height: f32,
style: <Theme as StyleSheet>::Style,
easing: &'a Easing,
cycle_duration: Duration,
rotation_duration: Duration,
}
impl<'a, Theme> Circular<'a, Theme>
where
Theme: StyleSheet,
{
/// Creates a new [`Circular`] with the given content.
pub fn new() -> Self {
Circular {
size: 40.0,
bar_height: 4.0,
style: <Theme as StyleSheet>::Style::default(),
easing: &easing::STANDARD,
cycle_duration: Duration::from_millis(600),
rotation_duration: Duration::from_secs(2),
}
}
/// Sets the size of the [`Circular`].
pub fn size(mut self, size: f32) -> Self {
self.size = size;
self
}
/// Sets the bar height of the [`Circular`].
pub fn bar_height(mut self, bar_height: f32) -> Self {
self.bar_height = bar_height;
self
}
/// Sets the style variant of this [`Circular`].
pub fn style(mut self, style: <Theme as StyleSheet>::Style) -> Self {
self.style = style;
self
}
/// Sets the easing of this [`Circular`].
pub fn easing(mut self, easing: &'a Easing) -> Self {
self.easing = easing;
self
}
/// Sets the cycle duration of this [`Circular`].
pub fn cycle_duration(mut self, duration: Duration) -> Self {
self.cycle_duration = duration / 2;
self
}
/// Sets the base rotation duration of this [`Circular`]. This is the duration that a full
/// rotation would take if the cycle rotation were set to 0.0 (no expanding or contracting)
pub fn rotation_duration(mut self, duration: Duration) -> Self {
self.rotation_duration = duration;
self
}
}
impl<'a, Theme> Default for Circular<'a, Theme>
where
Theme: StyleSheet,
{
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy)]
enum Animation {
Expanding {
start: Instant,
progress: f32,
rotation: u32,
last: Instant,
},
Contracting {
start: Instant,
progress: f32,
rotation: u32,
last: Instant,
},
}
impl Default for Animation {
fn default() -> Self {
Self::Expanding {
start: Instant::now(),
progress: 0.0,
rotation: 0,
last: Instant::now(),
}
}
}
impl Animation {
fn next(&self, additional_rotation: u32, now: Instant) -> Self {
match self {
Self::Expanding { rotation, .. } => Self::Contracting {
start: now,
progress: 0.0,
rotation: rotation.wrapping_add(additional_rotation),
last: now,
},
Self::Contracting { rotation, .. } => Self::Expanding {
start: now,
progress: 0.0,
rotation: rotation.wrapping_add(
BASE_ROTATION_SPEED.wrapping_add(
((WRAP_RADIANS / (2.0 * PI)) * u32::MAX as f32) as u32,
),
),
last: now,
},
}
}
fn start(&self) -> Instant {
match self {
Self::Expanding { start, .. } | Self::Contracting { start, .. } => {
*start
}
}
}
fn last(&self) -> Instant {
match self {
Self::Expanding { last, .. } | Self::Contracting { last, .. } => {
*last
}
}
}
fn timed_transition(
&self,
cycle_duration: Duration,
rotation_duration: Duration,
now: Instant,
) -> Self {
let elapsed = now.duration_since(self.start());
let additional_rotation = ((now - self.last()).as_secs_f32()
/ rotation_duration.as_secs_f32()
* (u32::MAX) as f32) as u32;
match elapsed {
elapsed if elapsed > cycle_duration => {
self.next(additional_rotation, now)
}
_ => self.with_elapsed(
cycle_duration,
additional_rotation,
elapsed,
now,
),
}
}
fn with_elapsed(
&self,
cycle_duration: Duration,
additional_rotation: u32,
elapsed: Duration,
now: Instant,
) -> Self {
let progress = elapsed.as_secs_f32() / cycle_duration.as_secs_f32();
match self {
Self::Expanding {
start, rotation, ..
} => Self::Expanding {
start: *start,
progress,
rotation: rotation.wrapping_add(additional_rotation),
last: now,
},
Self::Contracting {
start, rotation, ..
} => Self::Contracting {
start: *start,
progress,
rotation: rotation.wrapping_add(additional_rotation),
last: now,
},
}
}
fn rotation(&self) -> f32 {
match self {
Self::Expanding { rotation, .. }
| Self::Contracting { rotation, .. } => {
*rotation as f32 / u32::MAX as f32
}
}
}
}
#[derive(Default)]
struct State {
animation: Animation,
cache: canvas::Cache,
}
impl<'a, Message, Theme> Widget<Message, iced::Renderer<Theme>>
for Circular<'a, Theme>
where
Message: 'a + Clone,
Theme: StyleSheet,
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<State>()
}
fn state(&self) -> tree::State {
tree::State::new(State::default())
}
fn width(&self) -> Length {
Length::Fixed(self.size)
}
fn height(&self) -> Length {
Length::Fixed(self.size)
}
fn layout(
&self,
_renderer: &iced::Renderer<Theme>,
limits: &layout::Limits,
) -> layout::Node {
let limits = limits.width(self.size).height(self.size);
let size = limits.resolve(Size::ZERO);
layout::Node::new(size)
}
fn on_event(
&mut self,
tree: &mut Tree,
event: Event,
_layout: Layout<'_>,
_cursor: mouse::Cursor,
_renderer: &iced::Renderer<Theme>,
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
const FRAME_RATE: u64 = 60;
let state = tree.state.downcast_mut::<State>();
if let Event::Window(window::Event::RedrawRequested(now)) = event {
state.animation = state.animation.timed_transition(
self.cycle_duration,
self.rotation_duration,
now,
);
state.cache.clear();
shell.request_redraw(RedrawRequest::At(
now + Duration::from_millis(1000 / FRAME_RATE),
));
}
event::Status::Ignored
}
fn draw(
&self,
tree: &Tree,
renderer: &mut iced::Renderer<Theme>,
theme: &Theme,
_style: &renderer::Style,
layout: Layout<'_>,
_cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
let state = tree.state.downcast_ref::<State>();
let bounds = layout.bounds();
let custom_style =
<Theme as StyleSheet>::appearance(theme, &self.style);
let geometry = state.cache.draw(renderer, bounds.size(), |frame| {
let track_radius = frame.width() / 2.0 - self.bar_height;
let track_path = canvas::Path::circle(frame.center(), track_radius);
frame.stroke(
&track_path,
canvas::Stroke::default()
.with_color(custom_style.track_color)
.with_width(self.bar_height),
);
let mut builder = canvas::path::Builder::new();
let start = state.animation.rotation() * 2.0 * PI;
match state.animation {
Animation::Expanding { progress, .. } => {
builder.arc(canvas::path::Arc {
center: frame.center(),
radius: track_radius,
start_angle: start,
end_angle: start
+ MIN_RADIANS
+ WRAP_RADIANS * (self.easing.y_at_x(progress)),
});
}
Animation::Contracting { progress, .. } => {
builder.arc(canvas::path::Arc {
center: frame.center(),
radius: track_radius,
start_angle: start
+ WRAP_RADIANS * (self.easing.y_at_x(progress)),
end_angle: start + MIN_RADIANS + WRAP_RADIANS,
});
}
}
let bar_path = builder.build();
frame.stroke(
&bar_path,
canvas::Stroke::default()
.with_color(custom_style.bar_color)
.with_width(self.bar_height),
);
});
renderer.with_translation(
Vector::new(bounds.x, bounds.y),
|renderer| {
use iced::advanced::graphics::geometry::Renderer as _;
renderer.draw(vec![geometry]);
},
);
}
}
impl<'a, Message, Theme> From<Circular<'a, Theme>>
for Element<'a, Message, iced::Renderer<Theme>>
where
Message: Clone + 'a,
Theme: StyleSheet + 'a,
{
fn from(circular: Circular<'a, Theme>) -> Self {
Self::new(circular)
}
}
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
/// The [`Background`] of the progress indicator.
pub background: Option<Background>,
/// The track [`Color`] of the progress indicator.
pub track_color: Color,
/// The bar [`Color`] of the progress indicator.
pub bar_color: Color,
}
impl std::default::Default for Appearance {
fn default() -> Self {
Self {
background: None,
track_color: Color::TRANSPARENT,
bar_color: Color::BLACK,
}
}
}
/// A set of rules that dictate the style of an indicator.
pub trait StyleSheet {
/// The supported style of the [`StyleSheet`].
type Style: Default;
/// Produces the active [`Appearance`] of a indicator.
fn appearance(&self, style: &Self::Style) -> Appearance;
}
impl StyleSheet for iced::Theme {
type Style = ();
fn appearance(&self, _style: &Self::Style) -> Appearance {
let palette = self.extended_palette();
Appearance {
background: None,
track_color: palette.background.weak.color,
bar_color: palette.primary.base.color,
}
}
}

View file

@ -0,0 +1,133 @@
use iced::Point;
use lyon_algorithms::measure::PathMeasurements;
use lyon_algorithms::path::{builder::NoAttributes, path::BuilderImpl, Path};
use once_cell::sync::Lazy;
pub static EMPHASIZED: Lazy<Easing> = Lazy::new(|| {
Easing::builder()
.cubic_bezier_to([0.05, 0.0], [0.133333, 0.06], [0.166666, 0.4])
.cubic_bezier_to([0.208333, 0.82], [0.25, 1.0], [1.0, 1.0])
.build()
});
pub static EMPHASIZED_DECELERATE: Lazy<Easing> = Lazy::new(|| {
Easing::builder()
.cubic_bezier_to([0.05, 0.7], [0.1, 1.0], [1.0, 1.0])
.build()
});
pub static EMPHASIZED_ACCELERATE: Lazy<Easing> = Lazy::new(|| {
Easing::builder()
.cubic_bezier_to([0.3, 0.0], [0.8, 0.15], [1.0, 1.0])
.build()
});
pub static STANDARD: Lazy<Easing> = Lazy::new(|| {
Easing::builder()
.cubic_bezier_to([0.2, 0.0], [0.0, 1.0], [1.0, 1.0])
.build()
});
pub static STANDARD_DECELERATE: Lazy<Easing> = Lazy::new(|| {
Easing::builder()
.cubic_bezier_to([0.0, 0.0], [0.0, 1.0], [1.0, 1.0])
.build()
});
pub static STANDARD_ACCELERATE: Lazy<Easing> = Lazy::new(|| {
Easing::builder()
.cubic_bezier_to([0.3, 0.0], [1.0, 1.0], [1.0, 1.0])
.build()
});
pub struct Easing {
path: Path,
measurements: PathMeasurements,
}
impl Easing {
pub fn builder() -> Builder {
Builder::new()
}
pub fn y_at_x(&self, x: f32) -> f32 {
let mut sampler = self.measurements.create_sampler(
&self.path,
lyon_algorithms::measure::SampleType::Normalized,
);
let sample = sampler.sample(x);
sample.position().y
}
}
pub struct Builder(NoAttributes<BuilderImpl>);
impl Builder {
pub fn new() -> Self {
let mut builder = Path::builder();
builder.begin(lyon_algorithms::geom::point(0.0, 0.0));
Self(builder)
}
/// Adds a line segment. Points must be between 0,0 and 1,1
pub fn line_to(mut self, to: impl Into<Point>) -> Self {
self.0.line_to(Self::point(to));
self
}
/// Adds a quadratic bézier curve. Points must be between 0,0 and 1,1
pub fn quadratic_bezier_to(
mut self,
ctrl: impl Into<Point>,
to: impl Into<Point>,
) -> Self {
self.0
.quadratic_bezier_to(Self::point(ctrl), Self::point(to));
self
}
/// Adds a cubic bézier curve. Points must be between 0,0 and 1,1
pub fn cubic_bezier_to(
mut self,
ctrl1: impl Into<Point>,
ctrl2: impl Into<Point>,
to: impl Into<Point>,
) -> Self {
self.0.cubic_bezier_to(
Self::point(ctrl1),
Self::point(ctrl2),
Self::point(to),
);
self
}
pub fn build(mut self) -> Easing {
self.0.line_to(lyon_algorithms::geom::point(1.0, 1.0));
self.0.end(false);
let path = self.0.build();
let measurements = PathMeasurements::from_path(&path, 0.0);
Easing { path, measurements }
}
fn point(p: impl Into<Point>) -> lyon_algorithms::geom::Point<f32> {
let p: Point = p.into();
lyon_algorithms::geom::point(
p.x.min(1.0).max(0.0),
p.y.min(1.0).max(0.0),
)
}
}
impl Default for Builder {
fn default() -> Self {
Self::new()
}
}

View file

@ -0,0 +1,326 @@
//! Show a linear progress indicator.
use iced::advanced::layout;
use iced::advanced::renderer::{self, Quad};
use iced::advanced::widget::tree::{self, Tree};
use iced::advanced::{Clipboard, Layout, Shell, Widget};
use iced::event;
use iced::mouse;
use iced::time::Instant;
use iced::window::{self, RedrawRequest};
use iced::{Background, Color, Element, Event, Length, Rectangle, Size};
use super::easing::{self, Easing};
use std::time::Duration;
#[allow(missing_debug_implementations)]
pub struct Linear<'a, Renderer>
where
Renderer: iced::advanced::Renderer,
Renderer::Theme: StyleSheet,
{
width: Length,
height: Length,
style: <Renderer::Theme as StyleSheet>::Style,
easing: &'a Easing,
cycle_duration: Duration,
}
impl<'a, Renderer> Linear<'a, Renderer>
where
Renderer: iced::advanced::Renderer,
Renderer::Theme: StyleSheet,
{
/// Creates a new [`Linear`] with the given content.
pub fn new() -> Self {
Linear {
width: Length::Fixed(100.0),
height: Length::Fixed(4.0),
style: <Renderer::Theme as StyleSheet>::Style::default(),
easing: &easing::STANDARD,
cycle_duration: Duration::from_millis(600),
}
}
/// Sets the width of the [`Linear`].
pub fn width(mut self, width: impl Into<Length>) -> Self {
self.width = width.into();
self
}
/// Sets the height of the [`Linear`].
pub fn height(mut self, height: impl Into<Length>) -> Self {
self.height = height.into();
self
}
/// Sets the style variant of this [`Linear`].
pub fn style(
mut self,
style: <Renderer::Theme as StyleSheet>::Style,
) -> Self {
self.style = style;
self
}
/// Sets the motion easing of this [`Linear`].
pub fn easing(mut self, easing: &'a Easing) -> Self {
self.easing = easing;
self
}
/// Sets the cycle duration of this [`Linear`].
pub fn cycle_duration(mut self, duration: Duration) -> Self {
self.cycle_duration = duration / 2;
self
}
}
impl<'a, Renderer> Default for Linear<'a, Renderer>
where
Renderer: iced::advanced::Renderer,
Renderer::Theme: StyleSheet,
{
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy)]
enum State {
Expanding { start: Instant, progress: f32 },
Contracting { start: Instant, progress: f32 },
}
impl Default for State {
fn default() -> Self {
Self::Expanding {
start: Instant::now(),
progress: 0.0,
}
}
}
impl State {
fn next(&self, now: Instant) -> Self {
match self {
Self::Expanding { .. } => Self::Contracting {
start: now,
progress: 0.0,
},
Self::Contracting { .. } => Self::Expanding {
start: now,
progress: 0.0,
},
}
}
fn start(&self) -> Instant {
match self {
Self::Expanding { start, .. } | Self::Contracting { start, .. } => {
*start
}
}
}
fn timed_transition(&self, cycle_duration: Duration, now: Instant) -> Self {
let elapsed = now.duration_since(self.start());
match elapsed {
elapsed if elapsed > cycle_duration => self.next(now),
_ => self.with_elapsed(cycle_duration, elapsed),
}
}
fn with_elapsed(
&self,
cycle_duration: Duration,
elapsed: Duration,
) -> Self {
let progress = elapsed.as_secs_f32() / cycle_duration.as_secs_f32();
match self {
Self::Expanding { start, .. } => Self::Expanding {
start: *start,
progress,
},
Self::Contracting { start, .. } => Self::Contracting {
start: *start,
progress,
},
}
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer> for Linear<'a, Renderer>
where
Message: 'a + Clone,
Renderer: 'a + iced::advanced::Renderer,
Renderer::Theme: StyleSheet,
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<State>()
}
fn state(&self) -> tree::State {
tree::State::new(State::default())
}
fn width(&self) -> Length {
self.width
}
fn height(&self) -> Length {
self.height
}
fn layout(
&self,
_renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let limits = limits.width(self.width).height(self.height);
let size = limits.resolve(Size::ZERO);
layout::Node::new(size)
}
fn on_event(
&mut self,
tree: &mut Tree,
event: Event,
_layout: Layout<'_>,
_cursor: mouse::Cursor,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
const FRAME_RATE: u64 = 60;
let state = tree.state.downcast_mut::<State>();
if let Event::Window(window::Event::RedrawRequested(now)) = event {
*state = state.timed_transition(self.cycle_duration, now);
shell.request_redraw(RedrawRequest::At(
now + Duration::from_millis(1000 / FRAME_RATE),
));
}
event::Status::Ignored
}
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
_cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
let bounds = layout.bounds();
let custom_style = theme.appearance(&self.style);
let state = tree.state.downcast_ref::<State>();
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
x: bounds.x,
y: bounds.y,
width: bounds.width,
height: bounds.height,
},
border_radius: 0.0.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
Background::Color(custom_style.track_color),
);
match state {
State::Expanding { progress, .. } => renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
x: bounds.x,
y: bounds.y,
width: self.easing.y_at_x(*progress) * bounds.width,
height: bounds.height,
},
border_radius: 0.0.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
Background::Color(custom_style.bar_color),
),
State::Contracting { progress, .. } => renderer.fill_quad(
Quad {
bounds: Rectangle {
x: bounds.x
+ self.easing.y_at_x(*progress) * bounds.width,
y: bounds.y,
width: (1.0 - self.easing.y_at_x(*progress))
* bounds.width,
height: bounds.height,
},
border_radius: 0.0.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
Background::Color(custom_style.bar_color),
),
}
}
}
impl<'a, Message, Renderer> From<Linear<'a, Renderer>>
for Element<'a, Message, Renderer>
where
Message: Clone + 'a,
Renderer: iced::advanced::Renderer + 'a,
Renderer::Theme: StyleSheet,
{
fn from(linear: Linear<'a, Renderer>) -> Self {
Self::new(linear)
}
}
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
/// The track [`Color`] of the progress indicator.
pub track_color: Color,
/// The bar [`Color`] of the progress indicator.
pub bar_color: Color,
}
impl std::default::Default for Appearance {
fn default() -> Self {
Self {
track_color: Color::TRANSPARENT,
bar_color: Color::BLACK,
}
}
}
/// A set of rules that dictate the style of an indicator.
pub trait StyleSheet {
/// The supported style of the [`StyleSheet`].
type Style: Default;
/// Produces the active [`Appearance`] of a indicator.
fn appearance(&self, style: &Self::Style) -> Appearance;
}
impl StyleSheet for iced::Theme {
type Style = ();
fn appearance(&self, _style: &Self::Style) -> Appearance {
let palette = self.extended_palette();
Appearance {
track_color: palette.background.weak.color,
bar_color: palette.primary.base.color,
}
}
}

View file

@ -0,0 +1,118 @@
use iced::executor;
use iced::widget::{column, container, row, slider, text};
use iced::{Application, Command, Element, Length, Settings, Theme};
use std::time::Duration;
mod circular;
mod easing;
mod linear;
use circular::Circular;
use linear::Linear;
pub fn main() -> iced::Result {
LoadingSpinners::run(Settings {
antialiasing: true,
..Default::default()
})
}
struct LoadingSpinners {
cycle_duration: f32,
}
impl Default for LoadingSpinners {
fn default() -> Self {
Self {
cycle_duration: 2.0,
}
}
}
#[derive(Debug, Clone, Copy)]
enum Message {
CycleDurationChanged(f32),
}
impl Application for LoadingSpinners {
type Message = Message;
type Flags = ();
type Executor = executor::Default;
type Theme = Theme;
fn new(_flags: Self::Flags) -> (Self, Command<Message>) {
(Self::default(), Command::none())
}
fn title(&self) -> String {
String::from("Loading Spinners - Iced")
}
fn update(&mut self, message: Message) -> Command<Message> {
match message {
Message::CycleDurationChanged(duration) => {
self.cycle_duration = duration;
}
}
Command::none()
}
fn view(&self) -> Element<Message> {
let column = [
&easing::EMPHASIZED,
&easing::EMPHASIZED_DECELERATE,
&easing::EMPHASIZED_ACCELERATE,
&easing::STANDARD,
&easing::STANDARD_DECELERATE,
&easing::STANDARD_ACCELERATE,
]
.iter()
.zip([
"Emphasized:",
"Emphasized Decelerate:",
"Emphasized Accelerate:",
"Standard:",
"Standard Decelerate:",
"Standard Accelerate:",
])
.fold(column![], |column, (easing, label)| {
column.push(
row![
text(label).width(250),
Linear::new().easing(easing).cycle_duration(
Duration::from_secs_f32(self.cycle_duration)
),
Circular::new().easing(easing).cycle_duration(
Duration::from_secs_f32(self.cycle_duration)
)
]
.align_items(iced::Alignment::Center)
.spacing(20.0),
)
})
.spacing(20);
container(
column.push(
row(vec![
text("Cycle duration:").into(),
slider(1.0..=1000.0, self.cycle_duration * 100.0, |x| {
Message::CycleDurationChanged(x / 100.0)
})
.width(200.0)
.into(),
text(format!("{:.2}s", self.cycle_duration)).into(),
])
.align_items(iced::Alignment::Center)
.spacing(20.0),
),
)
.width(Length::Fill)
.height(Length::Fill)
.center_x()
.center_y()
.into()
}
}

View file

@ -6,5 +6,4 @@ edition = "2021"
publish = false
[dependencies]
iced = { path = "../..", features = [] }
iced_native = { path = "../../native" }
iced = { path = "../..", features = ["advanced"] }

View file

@ -1,12 +1,15 @@
use iced::executor;
use iced::keyboard;
use iced::subscription::{self, Subscription};
use iced::theme;
use iced::widget::{
self, button, column, container, horizontal_space, row, text, text_input,
};
use iced::{
executor, keyboard, subscription, theme, Alignment, Application, Command,
Element, Event, Length, Settings, Subscription,
self, button, column, container, horizontal_space, pick_list, row, text,
text_input,
};
use iced::{Alignment, Application, Command, Element, Event, Length, Settings};
use self::modal::Modal;
use modal::Modal;
use std::fmt;
pub fn main() -> iced::Result {
App::run(Settings::default())
@ -17,6 +20,7 @@ struct App {
show_modal: bool,
email: String,
password: String,
plan: Plan,
}
#[derive(Debug, Clone)]
@ -25,6 +29,7 @@ enum Message {
HideModal,
Email(String),
Password(String),
Plan(Plan),
Submit,
Event(Event),
}
@ -65,6 +70,10 @@ impl Application for App {
self.password = password;
Command::none()
}
Message::Plan(plan) => {
self.plan = plan;
Command::none()
}
Message::Submit => {
if !self.email.is_empty() && !self.password.is_empty() {
self.hide_modal();
@ -133,23 +142,31 @@ impl Application for App {
column![
column![
text("Email").size(12),
text_input(
"abc@123.com",
&self.email,
Message::Email
)
.on_submit(Message::Submit)
.padding(5),
text_input("abc@123.com", &self.email,)
.on_input(Message::Email)
.on_submit(Message::Submit)
.padding(5),
]
.spacing(5),
column![
text("Password").size(12),
text_input("", &self.password, Message::Password)
text_input("", &self.password)
.on_input(Message::Password)
.on_submit(Message::Submit)
.password()
.padding(5),
]
.spacing(5),
column![
text("Plan").size(12),
pick_list(
Plan::ALL,
Some(self.plan),
Message::Plan
)
.padding(5),
]
.spacing(5),
button(text("Submit")).on_press(Message::HideModal),
]
.spacing(10)
@ -177,13 +194,39 @@ impl App {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
enum Plan {
#[default]
Basic,
Pro,
Enterprise,
}
impl Plan {
pub const ALL: &[Self] = &[Self::Basic, Self::Pro, Self::Enterprise];
}
impl fmt::Display for Plan {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Plan::Basic => "Basic",
Plan::Pro => "Pro",
Plan::Enterprise => "Enterprise",
}
.fmt(f)
}
}
mod modal {
use iced_native::alignment::Alignment;
use iced_native::widget::{self, Tree};
use iced_native::{
event, layout, mouse, overlay, renderer, Clipboard, Color, Element,
Event, Layout, Length, Point, Rectangle, Shell, Size, Widget,
};
use iced::advanced::layout::{self, Layout};
use iced::advanced::overlay;
use iced::advanced::renderer;
use iced::advanced::widget::{self, Widget};
use iced::advanced::{self, Clipboard, Shell};
use iced::alignment::Alignment;
use iced::event;
use iced::mouse;
use iced::{Color, Element, Event, Length, Point, Rectangle, Size};
/// A widget that centers a modal element over some base element
pub struct Modal<'a, Message, Renderer> {
@ -218,14 +261,17 @@ mod modal {
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Modal<'a, Message, Renderer>
where
Renderer: iced_native::Renderer,
Renderer: advanced::Renderer,
Message: Clone,
{
fn children(&self) -> Vec<Tree> {
vec![Tree::new(&self.base), Tree::new(&self.modal)]
fn children(&self) -> Vec<widget::Tree> {
vec![
widget::Tree::new(&self.base),
widget::Tree::new(&self.modal),
]
}
fn diff(&self, tree: &mut Tree) {
fn diff(&self, tree: &mut widget::Tree) {
tree.diff_children(&[&self.base, &self.modal]);
}
@ -247,10 +293,10 @@ mod modal {
fn on_event(
&mut self,
state: &mut Tree,
state: &mut widget::Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@ -259,7 +305,7 @@ mod modal {
&mut state.children[0],
event,
layout,
cursor_position,
cursor,
renderer,
clipboard,
shell,
@ -268,12 +314,12 @@ mod modal {
fn draw(
&self,
state: &Tree,
state: &widget::Tree,
renderer: &mut Renderer,
theme: &<Renderer as iced_native::Renderer>::Theme,
theme: &<Renderer as advanced::Renderer>::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
viewport: &Rectangle,
) {
self.base.as_widget().draw(
@ -282,14 +328,14 @@ mod modal {
theme,
style,
layout,
cursor_position,
cursor,
viewport,
);
}
fn overlay<'b>(
&'b mut self,
state: &'b mut Tree,
state: &'b mut widget::Tree,
layout: Layout<'_>,
_renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
@ -306,16 +352,16 @@ mod modal {
fn mouse_interaction(
&self,
state: &Tree,
state: &widget::Tree,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.base.as_widget().mouse_interaction(
&state.children[0],
layout,
cursor_position,
cursor,
viewport,
renderer,
)
@ -323,7 +369,7 @@ mod modal {
fn operate(
&self,
state: &mut Tree,
state: &mut widget::Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn widget::Operation<Message>,
@ -339,7 +385,7 @@ mod modal {
struct Overlay<'a, 'b, Message, Renderer> {
content: &'b mut Element<'a, Message, Renderer>,
tree: &'b mut Tree,
tree: &'b mut widget::Tree,
size: Size,
on_blur: Option<Message>,
}
@ -347,7 +393,7 @@ mod modal {
impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, Renderer>
for Overlay<'a, 'b, Message, Renderer>
where
Renderer: iced_native::Renderer,
Renderer: advanced::Renderer,
Message: Clone,
{
fn layout(
@ -373,7 +419,7 @@ mod modal {
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@ -385,7 +431,7 @@ mod modal {
mouse::Button::Left,
)) = &event
{
if !content_bounds.contains(cursor_position) {
if !cursor.is_over(content_bounds) {
shell.publish(message.clone());
return event::Status::Captured;
}
@ -396,7 +442,7 @@ mod modal {
self.tree,
event,
layout.children().next().unwrap(),
cursor_position,
cursor,
renderer,
clipboard,
shell,
@ -409,12 +455,12 @@ mod modal {
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
) {
renderer.fill_quad(
renderer::Quad {
bounds: layout.bounds(),
border_radius: renderer::BorderRadius::from(0.0),
border_radius: Default::default(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
@ -430,7 +476,7 @@ mod modal {
theme,
style,
layout.children().next().unwrap(),
cursor_position,
cursor,
&layout.bounds(),
);
}
@ -452,24 +498,36 @@ mod modal {
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.content.as_widget().mouse_interaction(
self.tree,
layout.children().next().unwrap(),
cursor_position,
cursor,
viewport,
renderer,
)
}
fn overlay<'c>(
&'c mut self,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'c, Message, Renderer>> {
self.content.as_widget_mut().overlay(
self.tree,
layout.children().next().unwrap(),
renderer,
)
}
}
impl<'a, Message, Renderer> From<Modal<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Renderer: 'a + iced_native::Renderer,
Renderer: 'a + advanced::Renderer,
Message: 'a + Clone,
{
fn from(modal: Modal<'a, Message, Renderer>) -> Self {

View file

@ -1,11 +0,0 @@
[package]
name = "modern_art"
version = "0.1.0"
authors = ["Bingus <shankern@protonmail.com>"]
edition = "2021"
publish = false
[dependencies]
iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
rand = "0.8.5"
env_logger = "0.9"

View file

@ -1,142 +0,0 @@
use iced::widget::canvas::{
self, gradient::Location, gradient::Position, Cache, Canvas, Cursor, Frame,
Geometry, Gradient,
};
use iced::{
executor, Application, Color, Command, Element, Length, Point, Rectangle,
Renderer, Settings, Size, Theme,
};
use rand::{thread_rng, Rng};
fn main() -> iced::Result {
env_logger::builder().format_timestamp(None).init();
ModernArt::run(Settings {
antialiasing: true,
..Settings::default()
})
}
#[derive(Debug, Clone, Copy)]
enum Message {}
struct ModernArt {
cache: Cache,
}
impl Application for ModernArt {
type Executor = executor::Default;
type Message = Message;
type Theme = Theme;
type Flags = ();
fn new(_flags: Self::Flags) -> (Self, Command<Self::Message>) {
(
ModernArt {
cache: Default::default(),
},
Command::none(),
)
}
fn title(&self) -> String {
String::from("Modern Art")
}
fn update(&mut self, _message: Message) -> Command<Message> {
Command::none()
}
fn view(&self) -> Element<'_, Self::Message, Renderer<Self::Theme>> {
Canvas::new(self)
.width(Length::Fill)
.height(Length::Fill)
.into()
}
}
impl<Message> canvas::Program<Message> for ModernArt {
type State = ();
fn draw(
&self,
_state: &Self::State,
_theme: &Theme,
bounds: Rectangle,
_cursor: Cursor,
) -> Vec<Geometry> {
let geometry = self.cache.draw(bounds.size(), |frame| {
let num_squares = thread_rng().gen_range(0..1200);
let mut i = 0;
while i <= num_squares {
generate_box(frame, bounds.size());
i += 1;
}
});
vec![geometry]
}
}
fn random_direction() -> Location {
match thread_rng().gen_range(0..8) {
0 => Location::TopLeft,
1 => Location::Top,
2 => Location::TopRight,
3 => Location::Right,
4 => Location::BottomRight,
5 => Location::Bottom,
6 => Location::BottomLeft,
7 => Location::Left,
_ => Location::TopLeft,
}
}
fn generate_box(frame: &mut Frame, bounds: Size) -> bool {
let solid = rand::random::<bool>();
let random_color = || -> Color {
Color::from_rgb(
thread_rng().gen_range(0.0..1.0),
thread_rng().gen_range(0.0..1.0),
thread_rng().gen_range(0.0..1.0),
)
};
let gradient = |top_left: Point, size: Size| -> Gradient {
let mut builder = Gradient::linear(Position::Relative {
top_left,
size,
start: random_direction(),
end: random_direction(),
});
let stops = thread_rng().gen_range(1..15u32);
let mut i = 0;
while i <= stops {
builder = builder.add_stop(i as f32 / stops as f32, random_color());
i += 1;
}
builder.build().unwrap()
};
let top_left = Point::new(
thread_rng().gen_range(0.0..bounds.width),
thread_rng().gen_range(0.0..bounds.height),
);
let size = Size::new(
thread_rng().gen_range(50.0..200.0),
thread_rng().gen_range(50.0..200.0),
);
if solid {
frame.fill_rectangle(top_left, size, random_color());
} else {
frame.fill_rectangle(top_left, size, gradient(top_left, size));
};
solid
}

View file

@ -1,12 +1,13 @@
//! This example shows how to use touch events in `Canvas` to draw
//! a circle around each fingertip. This only works on touch-enabled
//! computers like Microsoft Surface.
use iced::mouse;
use iced::widget::canvas::event;
use iced::widget::canvas::stroke::{self, Stroke};
use iced::widget::canvas::{self, Canvas, Cursor, Geometry};
use iced::widget::canvas::{self, Canvas, Geometry};
use iced::{
executor, touch, window, Application, Color, Command, Element, Length,
Point, Rectangle, Settings, Subscription, Theme,
Point, Rectangle, Renderer, Settings, Subscription, Theme,
};
use std::collections::HashMap;
@ -95,7 +96,7 @@ impl Application for Multitouch {
}
}
impl canvas::Program<Message> for State {
impl canvas::Program<Message, Renderer> for State {
type State = ();
fn update(
@ -103,7 +104,7 @@ impl canvas::Program<Message> for State {
_state: &mut Self::State,
event: event::Event,
_bounds: Rectangle,
_cursor: Cursor,
_cursor: mouse::Cursor,
) -> (event::Status, Option<Message>) {
match event {
event::Event::Touch(touch_event) => match touch_event {
@ -125,11 +126,12 @@ impl canvas::Program<Message> for State {
fn draw(
&self,
_state: &Self::State,
renderer: &Renderer,
_theme: &Theme,
bounds: Rectangle,
_cursor: Cursor,
_cursor: mouse::Cursor,
) -> Vec<Geometry> {
let fingerweb = self.cache.draw(bounds.size(), |frame| {
let fingerweb = self.cache.draw(renderer, bounds.size(), |frame| {
if self.fingers.len() < 2 {
return;
}

View file

@ -6,6 +6,4 @@ edition = "2021"
publish = false
[dependencies]
iced = { path = "../..", features = ["debug"] }
iced_native = { path = "../../native" }
iced_lazy = { path = "../../lazy" }
iced = { path = "../..", features = ["debug", "lazy"] }

View file

@ -1,14 +1,16 @@
use iced::alignment::{self, Alignment};
use iced::event::{self, Event};
use iced::executor;
use iced::keyboard;
use iced::subscription;
use iced::theme::{self, Theme};
use iced::widget::pane_grid::{self, PaneGrid};
use iced::widget::{button, column, container, row, scrollable, text};
use iced::widget::{
button, column, container, responsive, row, scrollable, text,
};
use iced::{
Application, Color, Command, Element, Length, Settings, Size, Subscription,
};
use iced_lazy::responsive;
use iced_native::{event, subscription, Event};
pub fn main() -> iced::Result {
Example::run(Settings::default())
@ -107,7 +109,7 @@ impl Application for Example {
pane,
target,
}) => {
self.panes.swap(&pane, &target);
self.panes.drop(&pane, target);
}
Message::Dragged(_) => {}
Message::TogglePin(pane) => {
@ -253,6 +255,7 @@ fn handle_hotkey(key_code: keyboard::KeyCode) -> Option<Message> {
}
}
#[derive(Clone, Copy)]
struct Pane {
id: usize,
pub is_pinned: bool,
@ -296,7 +299,7 @@ fn view_content<'a>(
)
]
.spacing(5)
.max_width(150);
.max_width(160);
if total_panes > 1 && !is_pinned {
controls = controls.push(

View file

@ -61,8 +61,9 @@ impl Sandbox for Example {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Language {
#[default]
Rust,
Elm,
Ruby,
@ -84,12 +85,6 @@ impl Language {
];
}
impl Default for Language {
fn default() -> Language {
Language::Rust
}
}
impl std::fmt::Display for Language {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(

View file

@ -49,13 +49,11 @@ impl Sandbox for QRGenerator {
.size(70)
.style(Color::from([0.5, 0.5, 0.5]));
let input = text_input(
"Type the data of your QR code here...",
&self.data,
Message::DataChanged,
)
.size(30)
.padding(15);
let input =
text_input("Type the data of your QR code here...", &self.data)
.on_input(Message::DataChanged)
.size(30)
.padding(15);
let mut content = column![title, input]
.width(700)

View file

@ -0,0 +1,11 @@
[package]
name = "screenshot"
version = "0.1.0"
authors = ["Bingus <shankern@protonmail.com>"]
edition = "2021"
publish = false
[dependencies]
iced = { path = "../..", features = ["debug", "image", "advanced"] }
image = { version = "0.24.6", features = ["png"]}
env_logger = "0.10.0"

View file

@ -0,0 +1,320 @@
use iced::alignment;
use iced::keyboard::KeyCode;
use iced::theme::{Button, Container};
use iced::widget::{button, column, container, image, row, text, text_input};
use iced::window::screenshot::{self, Screenshot};
use iced::{
event, executor, keyboard, subscription, Alignment, Application, Command,
ContentFit, Element, Event, Length, Rectangle, Renderer, Subscription,
Theme,
};
use ::image as img;
use ::image::ColorType;
fn main() -> iced::Result {
env_logger::builder().format_timestamp(None).init();
Example::run(iced::Settings::default())
}
struct Example {
screenshot: Option<Screenshot>,
saved_png_path: Option<Result<String, PngError>>,
png_saving: bool,
crop_error: Option<screenshot::CropError>,
x_input_value: Option<u32>,
y_input_value: Option<u32>,
width_input_value: Option<u32>,
height_input_value: Option<u32>,
}
#[derive(Clone, Debug)]
enum Message {
Crop,
Screenshot,
ScreenshotData(Screenshot),
Png,
PngSaved(Result<String, PngError>),
XInputChanged(Option<u32>),
YInputChanged(Option<u32>),
WidthInputChanged(Option<u32>),
HeightInputChanged(Option<u32>),
}
impl Application for Example {
type Executor = executor::Default;
type Message = Message;
type Theme = Theme;
type Flags = ();
fn new(_flags: Self::Flags) -> (Self, Command<Self::Message>) {
(
Example {
screenshot: None,
saved_png_path: None,
png_saving: false,
crop_error: None,
x_input_value: None,
y_input_value: None,
width_input_value: None,
height_input_value: None,
},
Command::none(),
)
}
fn title(&self) -> String {
"Screenshot".to_string()
}
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
match message {
Message::Screenshot => {
return iced::window::screenshot(Message::ScreenshotData);
}
Message::ScreenshotData(screenshot) => {
self.screenshot = Some(screenshot);
}
Message::Png => {
if let Some(screenshot) = &self.screenshot {
self.png_saving = true;
return Command::perform(
save_to_png(screenshot.clone()),
Message::PngSaved,
);
}
}
Message::PngSaved(res) => {
self.png_saving = false;
self.saved_png_path = Some(res);
}
Message::XInputChanged(new_value) => {
self.x_input_value = new_value;
}
Message::YInputChanged(new_value) => {
self.y_input_value = new_value;
}
Message::WidthInputChanged(new_value) => {
self.width_input_value = new_value;
}
Message::HeightInputChanged(new_value) => {
self.height_input_value = new_value;
}
Message::Crop => {
if let Some(screenshot) = &self.screenshot {
let cropped = screenshot.crop(Rectangle::<u32> {
x: self.x_input_value.unwrap_or(0),
y: self.y_input_value.unwrap_or(0),
width: self.width_input_value.unwrap_or(0),
height: self.height_input_value.unwrap_or(0),
});
match cropped {
Ok(screenshot) => {
self.screenshot = Some(screenshot);
self.crop_error = None;
}
Err(crop_error) => {
self.crop_error = Some(crop_error);
}
}
}
}
}
Command::none()
}
fn view(&self) -> Element<'_, Self::Message, Renderer<Self::Theme>> {
let image: Element<Message> = if let Some(screenshot) = &self.screenshot
{
image(image::Handle::from_pixels(
screenshot.size.width,
screenshot.size.height,
screenshot.clone(),
))
.content_fit(ContentFit::Contain)
.width(Length::Fill)
.height(Length::Fill)
.into()
} else {
text("Press the button to take a screenshot!").into()
};
let image = container(image)
.padding(10)
.style(Container::Box)
.width(Length::FillPortion(2))
.height(Length::Fill)
.center_x()
.center_y();
let crop_origin_controls = row![
text("X:")
.vertical_alignment(alignment::Vertical::Center)
.width(30),
numeric_input("0", self.x_input_value).map(Message::XInputChanged),
text("Y:")
.vertical_alignment(alignment::Vertical::Center)
.width(30),
numeric_input("0", self.y_input_value).map(Message::YInputChanged)
]
.spacing(10)
.align_items(Alignment::Center);
let crop_dimension_controls = row![
text("W:")
.vertical_alignment(alignment::Vertical::Center)
.width(30),
numeric_input("0", self.width_input_value)
.map(Message::WidthInputChanged),
text("H:")
.vertical_alignment(alignment::Vertical::Center)
.width(30),
numeric_input("0", self.height_input_value)
.map(Message::HeightInputChanged)
]
.spacing(10)
.align_items(Alignment::Center);
let mut crop_controls =
column![crop_origin_controls, crop_dimension_controls]
.spacing(10)
.align_items(Alignment::Center);
if let Some(crop_error) = &self.crop_error {
crop_controls = crop_controls
.push(text(format!("Crop error! \n{}", crop_error)));
}
let mut controls = column![
column![
button(centered_text("Screenshot!"))
.padding([10, 20, 10, 20])
.width(Length::Fill)
.on_press(Message::Screenshot),
if !self.png_saving {
button(centered_text("Save as png")).on_press_maybe(
self.screenshot.is_some().then(|| Message::Png),
)
} else {
button(centered_text("Saving...")).style(Button::Secondary)
}
.style(Button::Secondary)
.padding([10, 20, 10, 20])
.width(Length::Fill)
]
.spacing(10),
column![
crop_controls,
button(centered_text("Crop"))
.on_press(Message::Crop)
.style(Button::Destructive)
.padding([10, 20, 10, 20])
.width(Length::Fill),
]
.spacing(10)
.align_items(Alignment::Center),
]
.spacing(40);
if let Some(png_result) = &self.saved_png_path {
let msg = match png_result {
Ok(path) => format!("Png saved as: {:?}!", path),
Err(msg) => {
format!("Png could not be saved due to:\n{:?}", msg)
}
};
controls = controls.push(text(msg));
}
let side_content = container(controls)
.align_x(alignment::Horizontal::Center)
.width(Length::FillPortion(1))
.height(Length::Fill)
.center_y()
.center_x();
let content = row![side_content, image]
.spacing(10)
.width(Length::Fill)
.height(Length::Fill)
.align_items(Alignment::Center);
container(content)
.width(Length::Fill)
.height(Length::Fill)
.padding(10)
.center_x()
.center_y()
.into()
}
fn subscription(&self) -> Subscription<Self::Message> {
subscription::events_with(|event, status| {
if let event::Status::Captured = status {
return None;
}
if let Event::Keyboard(keyboard::Event::KeyPressed {
key_code: KeyCode::F5,
..
}) = event
{
Some(Message::Screenshot)
} else {
None
}
})
}
}
async fn save_to_png(screenshot: Screenshot) -> Result<String, PngError> {
let path = "screenshot.png".to_string();
img::save_buffer(
&path,
&screenshot.bytes,
screenshot.size.width,
screenshot.size.height,
ColorType::Rgba8,
)
.map(|_| path)
.map_err(|err| PngError(format!("{:?}", err)))
}
#[derive(Clone, Debug)]
struct PngError(String);
fn numeric_input(
placeholder: &str,
value: Option<u32>,
) -> Element<'_, Option<u32>> {
text_input(
placeholder,
&value
.as_ref()
.map(ToString::to_string)
.unwrap_or_else(String::new),
)
.on_input(move |text| {
if text.is_empty() {
None
} else if let Ok(new_value) = text.parse() {
Some(new_value)
} else {
value
}
})
.width(40)
.into()
}
fn centered_text(content: &str) -> Element<'_, Message> {
text(content)
.width(Length::Fill)
.horizontal_alignment(alignment::Horizontal::Center)
.into()
}

View file

@ -5,6 +5,7 @@ use iced::widget::{
};
use iced::{executor, theme, Alignment, Color};
use iced::{Application, Command, Element, Length, Settings, Theme};
use once_cell::sync::Lazy;
static SCROLLABLE_ID: Lazy<scrollable::Id> = Lazy::new(scrollable::Id::unique);
@ -19,6 +20,7 @@ struct ScrollableDemo {
scrollbar_margin: u16,
scroller_width: u16,
current_scroll_offset: scrollable::RelativeOffset,
alignment: scrollable::Alignment,
}
#[derive(Debug, Clone, Eq, PartialEq, Copy)]
@ -31,12 +33,13 @@ enum Direction {
#[derive(Debug, Clone)]
enum Message {
SwitchDirection(Direction),
AlignmentChanged(scrollable::Alignment),
ScrollbarWidthChanged(u16),
ScrollbarMarginChanged(u16),
ScrollerWidthChanged(u16),
ScrollToBeginning,
ScrollToEnd,
Scrolled(scrollable::RelativeOffset),
Scrolled(scrollable::Viewport),
}
impl Application for ScrollableDemo {
@ -53,6 +56,7 @@ impl Application for ScrollableDemo {
scrollbar_margin: 0,
scroller_width: 10,
current_scroll_offset: scrollable::RelativeOffset::START,
alignment: scrollable::Alignment::Start,
},
Command::none(),
)
@ -73,6 +77,15 @@ impl Application for ScrollableDemo {
self.current_scroll_offset,
)
}
Message::AlignmentChanged(alignment) => {
self.current_scroll_offset = scrollable::RelativeOffset::START;
self.alignment = alignment;
scrollable::snap_to(
SCROLLABLE_ID.clone(),
self.current_scroll_offset,
)
}
Message::ScrollbarWidthChanged(width) => {
self.scrollbar_width = width;
@ -104,8 +117,8 @@ impl Application for ScrollableDemo {
self.current_scroll_offset,
)
}
Message::Scrolled(offset) => {
self.current_scroll_offset = offset;
Message::Scrolled(viewport) => {
self.current_scroll_offset = viewport.relative_offset();
Command::none()
}
@ -164,10 +177,33 @@ impl Application for ScrollableDemo {
.spacing(10)
.width(Length::Fill);
let scroll_controls =
row![scroll_slider_controls, scroll_orientation_controls]
.spacing(20)
.width(Length::Fill);
let scroll_alignment_controls = column(vec![
text("Scrollable alignment:").into(),
radio(
"Start",
scrollable::Alignment::Start,
Some(self.alignment),
Message::AlignmentChanged,
)
.into(),
radio(
"End",
scrollable::Alignment::End,
Some(self.alignment),
Message::AlignmentChanged,
)
.into(),
])
.spacing(10)
.width(Length::Fill);
let scroll_controls = row![
scroll_slider_controls,
scroll_orientation_controls,
scroll_alignment_controls
]
.spacing(20)
.width(Length::Fill);
let scroll_to_end_button = || {
button("Scroll to end")
@ -199,12 +235,13 @@ impl Application for ScrollableDemo {
.spacing(40),
)
.height(Length::Fill)
.vertical_scroll(
.direction(scrollable::Direction::Vertical(
Properties::new()
.width(self.scrollbar_width)
.margin(self.scrollbar_margin)
.scroller_width(self.scroller_width),
)
.scroller_width(self.scroller_width)
.alignment(self.alignment),
))
.id(SCROLLABLE_ID.clone())
.on_scroll(Message::Scrolled),
Direction::Horizontal => scrollable(
@ -223,12 +260,13 @@ impl Application for ScrollableDemo {
.spacing(40),
)
.height(Length::Fill)
.horizontal_scroll(
.direction(scrollable::Direction::Horizontal(
Properties::new()
.width(self.scrollbar_width)
.margin(self.scrollbar_margin)
.scroller_width(self.scroller_width),
)
.scroller_width(self.scroller_width)
.alignment(self.alignment),
))
.style(theme::Scrollable::custom(ScrollbarCustomStyle))
.id(SCROLLABLE_ID.clone())
.on_scroll(Message::Scrolled),
@ -264,18 +302,18 @@ impl Application for ScrollableDemo {
.spacing(40),
)
.height(Length::Fill)
.vertical_scroll(
Properties::new()
.direction({
let properties = Properties::new()
.width(self.scrollbar_width)
.margin(self.scrollbar_margin)
.scroller_width(self.scroller_width),
)
.horizontal_scroll(
Properties::new()
.width(self.scrollbar_width)
.margin(self.scrollbar_margin)
.scroller_width(self.scroller_width),
)
.scroller_width(self.scroller_width)
.alignment(self.alignment);
scrollable::Direction::Both {
horizontal: properties,
vertical: properties,
}
})
.style(theme::Scrollable::Custom(Box::new(
ScrollbarCustomStyle,
)))
@ -289,18 +327,13 @@ impl Application for ScrollableDemo {
}
Direction::Horizontal => {
progress_bar(0.0..=1.0, self.current_scroll_offset.x)
.style(theme::ProgressBar::Custom(Box::new(
ProgressBarCustomStyle,
)))
.style(progress_bar_custom_style)
.into()
}
Direction::Multi => column![
progress_bar(0.0..=1.0, self.current_scroll_offset.y),
progress_bar(0.0..=1.0, self.current_scroll_offset.x).style(
theme::ProgressBar::Custom(Box::new(
ProgressBarCustomStyle,
))
)
progress_bar(0.0..=1.0, self.current_scroll_offset.x)
.style(progress_bar_custom_style)
]
.spacing(10)
.into(),
@ -338,36 +371,44 @@ impl scrollable::StyleSheet for ScrollbarCustomStyle {
style.active(&theme::Scrollable::Default)
}
fn hovered(&self, style: &Self::Style) -> Scrollbar {
style.hovered(&theme::Scrollable::Default)
fn hovered(
&self,
style: &Self::Style,
is_mouse_over_scrollbar: bool,
) -> Scrollbar {
style.hovered(&theme::Scrollable::Default, is_mouse_over_scrollbar)
}
fn hovered_horizontal(&self, style: &Self::Style) -> Scrollbar {
Scrollbar {
background: style.active(&theme::Scrollable::default()).background,
border_radius: 0.0,
border_width: 0.0,
border_color: Default::default(),
scroller: Scroller {
color: Color::from_rgb8(250, 85, 134),
border_radius: 0.0,
fn hovered_horizontal(
&self,
style: &Self::Style,
is_mouse_over_scrollbar: bool,
) -> Scrollbar {
if is_mouse_over_scrollbar {
Scrollbar {
background: style
.active(&theme::Scrollable::default())
.background,
border_radius: 0.0.into(),
border_width: 0.0,
border_color: Default::default(),
},
scroller: Scroller {
color: Color::from_rgb8(250, 85, 134),
border_radius: 0.0.into(),
border_width: 0.0,
border_color: Default::default(),
},
}
} else {
self.active(style)
}
}
}
struct ProgressBarCustomStyle;
impl progress_bar::StyleSheet for ProgressBarCustomStyle {
type Style = Theme;
fn appearance(&self, style: &Self::Style) -> progress_bar::Appearance {
progress_bar::Appearance {
background: style.extended_palette().background.strong.color.into(),
bar: Color::from_rgb8(250, 85, 134).into(),
border_radius: 0.0,
}
fn progress_bar_custom_style(theme: &Theme) -> progress_bar::Appearance {
progress_bar::Appearance {
background: theme.extended_palette().background.strong.color.into(),
bar: Color::from_rgb8(250, 85, 134).into(),
border_radius: 0.0.into(),
}
}

View file

@ -1,12 +1,13 @@
use std::fmt::Debug;
use iced::executor;
use iced::mouse;
use iced::widget::canvas::event::{self, Event};
use iced::widget::canvas::{self, Canvas};
use iced::widget::{column, row, slider, text};
use iced::{
Application, Color, Command, Length, Point, Rectangle, Settings, Size,
Theme,
Application, Color, Command, Length, Point, Rectangle, Renderer, Settings,
Size, Theme,
};
use rand::Rng;
@ -105,14 +106,14 @@ impl canvas::Program<Message> for SierpinskiGraph {
_state: &mut Self::State,
event: Event,
bounds: Rectangle,
cursor: canvas::Cursor,
cursor: mouse::Cursor,
) -> (event::Status, Option<Message>) {
let cursor_position =
if let Some(position) = cursor.position_in(&bounds) {
position
} else {
return (event::Status::Ignored, None);
};
let cursor_position = if let Some(position) = cursor.position_in(bounds)
{
position
} else {
return (event::Status::Ignored, None);
};
match event {
Event::Mouse(mouse_event) => {
@ -134,11 +135,12 @@ impl canvas::Program<Message> for SierpinskiGraph {
fn draw(
&self,
_state: &Self::State,
renderer: &Renderer,
_theme: &Theme,
bounds: Rectangle,
_cursor: canvas::Cursor,
_cursor: mouse::Cursor,
) -> Vec<canvas::Geometry> {
let geom = self.cache.draw(bounds.size(), |frame| {
let geom = self.cache.draw(renderer, bounds.size(), |frame| {
frame.stroke(
&canvas::Path::rectangle(Point::ORIGIN, frame.size()),
canvas::Stroke::default(),

View file

@ -7,4 +7,5 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
env_logger = "0.10.0"
rand = "0.8.3"

View file

@ -8,20 +8,23 @@
//! [1]: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations#An_animated_solar_system
use iced::application;
use iced::executor;
use iced::mouse;
use iced::theme::{self, Theme};
use iced::widget::canvas;
use iced::widget::canvas::gradient::{self, Gradient};
use iced::widget::canvas::gradient;
use iced::widget::canvas::stroke::{self, Stroke};
use iced::widget::canvas::{Cursor, Path};
use iced::widget::canvas::Path;
use iced::window;
use iced::{
Application, Color, Command, Element, Length, Point, Rectangle, Settings,
Size, Subscription, Vector,
Application, Color, Command, Element, Length, Point, Rectangle, Renderer,
Settings, Size, Subscription, Vector,
};
use std::time::Instant;
pub fn main() -> iced::Result {
env_logger::builder().format_timestamp(None).init();
SolarSystem::run(Settings {
antialiasing: true,
..Settings::default()
@ -156,24 +159,26 @@ impl<Message> canvas::Program<Message> for State {
fn draw(
&self,
_state: &Self::State,
renderer: &Renderer,
_theme: &Theme,
bounds: Rectangle,
_cursor: Cursor,
_cursor: mouse::Cursor,
) -> Vec<canvas::Geometry> {
use std::f32::consts::PI;
let background = self.space_cache.draw(bounds.size(), |frame| {
let stars = Path::new(|path| {
for (p, size) in &self.stars {
path.rectangle(*p, Size::new(*size, *size));
}
let background =
self.space_cache.draw(renderer, bounds.size(), |frame| {
let stars = Path::new(|path| {
for (p, size) in &self.stars {
path.rectangle(*p, Size::new(*size, *size));
}
});
frame.translate(frame.center() - Point::ORIGIN);
frame.fill(&stars, Color::WHITE);
});
frame.translate(frame.center() - Point::ORIGIN);
frame.fill(&stars, Color::WHITE);
});
let system = self.system_cache.draw(bounds.size(), |frame| {
let system = self.system_cache.draw(renderer, bounds.size(), |frame| {
let center = frame.center();
let sun = Path::circle(center, Self::SUN_RADIUS);
@ -206,15 +211,12 @@ impl<Message> canvas::Program<Message> for State {
let earth = Path::circle(Point::ORIGIN, Self::EARTH_RADIUS);
let earth_fill =
Gradient::linear(gradient::Position::Absolute {
start: Point::new(-Self::EARTH_RADIUS, 0.0),
end: Point::new(Self::EARTH_RADIUS, 0.0),
})
.add_stop(0.2, Color::from_rgb(0.15, 0.50, 1.0))
.add_stop(0.8, Color::from_rgb(0.0, 0.20, 0.47))
.build()
.expect("Build Earth fill gradient");
let earth_fill = gradient::Linear::new(
Point::new(-Self::EARTH_RADIUS, 0.0),
Point::new(Self::EARTH_RADIUS, 0.0),
)
.add_stop(0.2, Color::from_rgb(0.15, 0.50, 1.0))
.add_stop(0.8, Color::from_rgb(0.0, 0.20, 0.47));
frame.fill(&earth, earth_fill);

View file

@ -90,13 +90,10 @@ impl Sandbox for Styling {
},
);
let text_input = text_input(
"Type something...",
&self.input_value,
Message::InputChanged,
)
.padding(10)
.size(20);
let text_input = text_input("Type something...", &self.input_value)
.on_input(Message::InputChanged)
.padding(10)
.size(20);
let button = button("Submit")
.padding(10)
@ -130,7 +127,9 @@ impl Sandbox for Styling {
let content = column![
choose_theme,
horizontal_rule(38),
row![text_input, button].spacing(10),
row![text_input, button]
.spacing(10)
.align_items(Alignment::Center),
slider,
progress_bar,
row![

View file

@ -6,5 +6,4 @@ edition = "2021"
publish = false
[dependencies]
iced = { path = "../..", features = [] }
iced_native = { path = "../../native" }
iced = { path = "../..", features = ["advanced"] }

View file

@ -1,10 +1,10 @@
use iced::executor;
use iced::keyboard;
use iced::subscription::{self, Subscription};
use iced::widget::{
self, button, column, container, pick_list, row, slider, text, text_input,
};
use iced::{
executor, keyboard, subscription, Alignment, Application, Command, Element,
Event, Length, Settings, Subscription,
};
use iced::{Alignment, Application, Command, Element, Event, Length, Settings};
use toast::{Status, Toast};
@ -119,13 +119,15 @@ impl Application for App {
column![
subtitle(
"Title",
text_input("", &self.editing.title, Message::Title)
text_input("", &self.editing.title)
.on_input(Message::Title)
.on_submit(Message::Add)
.into()
),
subtitle(
"Message",
text_input("", &self.editing.body, Message::Body)
text_input("", &self.editing.body)
.on_input(Message::Body)
.on_submit(Message::Add)
.into()
),
@ -176,17 +178,23 @@ mod toast {
use std::fmt;
use std::time::{Duration, Instant};
use iced::advanced;
use iced::advanced::layout::{self, Layout};
use iced::advanced::overlay;
use iced::advanced::renderer;
use iced::advanced::widget::{self, Operation, Tree};
use iced::advanced::{Clipboard, Shell, Widget};
use iced::event::{self, Event};
use iced::mouse;
use iced::theme;
use iced::widget::{
button, column, container, horizontal_rule, horizontal_space, row, text,
};
use iced::window;
use iced::{
Alignment, Element, Length, Point, Rectangle, Renderer, Size, Theme,
Vector,
};
use iced_native::widget::{tree, Operation, Tree};
use iced_native::{event, layout, mouse, overlay, renderer, window};
use iced_native::{Clipboard, Event, Layout, Shell, Widget};
pub const DEFAULT_TIMEOUT: u64 = 5;
@ -218,7 +226,7 @@ mod toast {
};
container::Appearance {
background: pair.color.into(),
background: Some(pair.color.into()),
text_color: pair.text.into(),
..Default::default()
}
@ -324,13 +332,13 @@ mod toast {
self.content.as_widget().layout(renderer, limits)
}
fn tag(&self) -> tree::Tag {
fn tag(&self) -> widget::tree::Tag {
struct Marker(Vec<Instant>);
iced_native::widget::tree::Tag::of::<Marker>()
widget::tree::Tag::of::<Marker>()
}
fn state(&self) -> tree::State {
iced_native::widget::tree::State::new(Vec::<Option<Instant>>::new())
fn state(&self) -> widget::tree::State {
widget::tree::State::new(Vec::<Option<Instant>>::new())
}
fn children(&self) -> Vec<Tree> {
@ -388,7 +396,7 @@ mod toast {
state: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@ -397,7 +405,7 @@ mod toast {
&mut state.children[0],
event,
layout,
cursor_position,
cursor,
renderer,
clipboard,
shell,
@ -411,7 +419,7 @@ mod toast {
theme: &Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
viewport: &Rectangle,
) {
self.content.as_widget().draw(
@ -420,7 +428,7 @@ mod toast {
theme,
style,
layout,
cursor_position,
cursor,
viewport,
);
}
@ -429,14 +437,14 @@ mod toast {
&self,
state: &Tree,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.content.as_widget().mouse_interaction(
&state.children[0],
layout,
cursor_position,
cursor,
viewport,
renderer,
)
@ -515,7 +523,7 @@ mod toast {
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@ -564,7 +572,7 @@ mod toast {
state,
event.clone(),
layout,
cursor_position,
cursor,
renderer,
clipboard,
&mut local_shell,
@ -584,10 +592,10 @@ mod toast {
fn draw(
&self,
renderer: &mut Renderer,
theme: &<Renderer as iced_native::Renderer>::Theme,
theme: &<Renderer as advanced::Renderer>::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
) {
let viewport = layout.bounds();
@ -598,13 +606,7 @@ mod toast {
.zip(layout.children())
{
child.as_widget().draw(
state,
renderer,
theme,
style,
layout,
cursor_position,
&viewport,
state, renderer, theme, style, layout, cursor, &viewport,
);
}
}
@ -613,7 +615,7 @@ mod toast {
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn iced_native::widget::Operation<Message>,
operation: &mut dyn widget::Operation<Message>,
) {
operation.container(None, &mut |operation| {
self.toasts
@ -631,7 +633,7 @@ mod toast {
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
@ -641,18 +643,19 @@ mod toast {
.zip(layout.children())
.map(|((child, state), layout)| {
child.as_widget().mouse_interaction(
state,
layout,
cursor_position,
viewport,
renderer,
state, layout, cursor, viewport, renderer,
)
})
.max()
.unwrap_or_default()
}
fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
fn is_over(
&self,
layout: Layout<'_>,
_renderer: &Renderer,
cursor_position: Point,
) -> bool {
layout
.children()
.any(|layout| layout.bounds().contains(cursor_position))

Binary file not shown.

View file

@ -1,5 +1,6 @@
use iced::alignment::{self, Alignment};
use iced::event::{self, Event};
use iced::font::{self, Font};
use iced::keyboard::{self, KeyCode, Modifiers};
use iced::subscription;
use iced::theme::{self, Theme};
@ -9,7 +10,7 @@ use iced::widget::{
};
use iced::window;
use iced::{Application, Element};
use iced::{Color, Command, Font, Length, Settings, Subscription};
use iced::{Color, Command, Length, Settings, Subscription};
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
@ -44,6 +45,7 @@ struct State {
#[derive(Debug, Clone)]
enum Message {
Loaded(Result<SavedState, LoadError>),
FontLoaded(Result<(), font::Error>),
Saved(Result<(), SaveError>),
InputChanged(String),
CreateTask,
@ -62,7 +64,11 @@ impl Application for Todos {
fn new(_flags: ()) -> (Todos, Command<Message>) {
(
Todos::Loading,
Command::perform(SavedState::load(), Message::Loaded),
Command::batch(vec![
font::load(include_bytes!("../fonts/icons.ttf").as_slice())
.map(Message::FontLoaded),
Command::perform(SavedState::load(), Message::Loaded),
]),
)
}
@ -204,15 +210,12 @@ impl Application for Todos {
.style(Color::from([0.5, 0.5, 0.5]))
.horizontal_alignment(alignment::Horizontal::Center);
let input = text_input(
"What needs to be done?",
input_value,
Message::InputChanged,
)
.id(INPUT_ID.clone())
.padding(15)
.size(30)
.on_submit(Message::CreateTask);
let input = text_input("What needs to be done?", input_value)
.id(INPUT_ID.clone())
.on_input(Message::InputChanged)
.on_submit(Message::CreateTask)
.padding(15)
.size(30);
let controls = view_controls(tasks, *filter);
let filtered_tasks =
@ -361,7 +364,8 @@ impl Task {
self.completed,
TaskMessage::Completed,
)
.width(Length::Fill);
.width(Length::Fill)
.text_shaping(text::Shaping::Advanced);
row![
checkbox,
@ -375,21 +379,23 @@ impl Task {
.into()
}
TaskState::Editing => {
let text_input = text_input(
"Describe your task...",
&self.description,
TaskMessage::DescriptionEdited,
)
.id(Self::text_input_id(i))
.on_submit(TaskMessage::FinishEdition)
.padding(10);
let text_input =
text_input("Describe your task...", &self.description)
.id(Self::text_input_id(i))
.on_input(TaskMessage::DescriptionEdited)
.on_submit(TaskMessage::FinishEdition)
.padding(10);
row![
text_input,
button(row![delete_icon(), "Delete"].spacing(10))
.on_press(TaskMessage::Delete)
.padding(10)
.style(theme::Button::Destructive)
button(
row![delete_icon(), "Delete"]
.spacing(10)
.align_items(Alignment::Center)
)
.on_press(TaskMessage::Delete)
.padding(10)
.style(theme::Button::Destructive)
]
.spacing(20)
.align_items(Alignment::Center)
@ -403,7 +409,7 @@ fn view_controls(tasks: &[Task], current_filter: Filter) -> Element<Message> {
let tasks_left = tasks.iter().filter(|task| !task.completed).count();
let filter_button = |label, filter, current_filter| {
let label = text(label).size(16);
let label = text(label);
let button = button(label).style(if filter == current_filter {
theme::Button::Primary
@ -420,8 +426,7 @@ fn view_controls(tasks: &[Task], current_filter: Filter) -> Element<Message> {
tasks_left,
if tasks_left == 1 { "task" } else { "tasks" }
))
.width(Length::Fill)
.size(16),
.width(Length::Fill),
row![
filter_button("All", Filter::All, current_filter),
filter_button("Active", Filter::Active, current_filter),
@ -435,19 +440,16 @@ fn view_controls(tasks: &[Task], current_filter: Filter) -> Element<Message> {
.into()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
)]
pub enum Filter {
#[default]
All,
Active,
Completed,
}
impl Default for Filter {
fn default() -> Self {
Filter::All
}
}
impl Filter {
fn matches(&self, task: &Task) -> bool {
match self {
@ -485,17 +487,13 @@ fn empty_message(message: &str) -> Element<'_, Message> {
}
// Fonts
const ICONS: Font = Font::External {
name: "Icons",
bytes: include_bytes!("../../todos/fonts/icons.ttf"),
};
const ICONS: Font = Font::with_name("Iced-Todos-Icons");
fn icon(unicode: char) -> Text<'static> {
text(unicode.to_string())
.font(ICONS)
.width(20)
.horizontal_alignment(alignment::Horizontal::Center)
.size(20)
}
fn edit_icon() -> Text<'static> {

View file

@ -7,4 +7,4 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["image", "debug"] }
env_logger = "0.8"
env_logger = "0.10.0"

View file

@ -5,7 +5,7 @@ use iced::widget::{
scrollable, slider, text, text_input, toggler, vertical_space,
};
use iced::widget::{Button, Column, Container, Slider};
use iced::{Color, Element, Length, Renderer, Sandbox, Settings};
use iced::{Color, Element, Font, Length, Renderer, Sandbox, Settings};
pub fn main() -> iced::Result {
env_logger::init();
@ -62,11 +62,8 @@ impl Sandbox for Tour {
controls = controls.push(horizontal_space(Length::Fill));
if steps.can_continue() {
controls = controls.push(
button("Next")
.on_press(Message::NextPressed)
.style(theme::Button::Primary),
);
controls =
controls.push(button("Next").on_press(Message::NextPressed));
}
let content: Element<_> = column![
@ -127,6 +124,7 @@ impl Steps {
Step::TextInput {
value: String::new(),
is_secure: false,
is_showing_icon: false,
},
Step::Debugger,
Step::End,
@ -171,14 +169,32 @@ impl Steps {
enum Step {
Welcome,
Slider { value: u8 },
RowsAndColumns { layout: Layout, spacing: u16 },
Text { size: u16, color: Color },
Radio { selection: Option<Language> },
Toggler { can_continue: bool },
Image { width: u16 },
Slider {
value: u8,
},
RowsAndColumns {
layout: Layout,
spacing: u16,
},
Text {
size: u16,
color: Color,
},
Radio {
selection: Option<Language>,
},
Toggler {
can_continue: bool,
},
Image {
width: u16,
},
Scrollable,
TextInput { value: String, is_secure: bool },
TextInput {
value: String,
is_secure: bool,
is_showing_icon: bool,
},
Debugger,
End,
}
@ -194,6 +210,7 @@ pub enum StepMessage {
ImageWidthChanged(u16),
InputChanged(String),
ToggleSecureInput(bool),
ToggleTextInputIcon(bool),
DebugToggled(bool),
TogglerChanged(bool),
}
@ -256,6 +273,14 @@ impl<'a> Step {
*can_continue = value;
}
}
StepMessage::ToggleTextInputIcon(toggle) => {
if let Step::TextInput {
is_showing_icon, ..
} = self
{
*is_showing_icon = toggle
}
}
};
}
@ -303,9 +328,11 @@ impl<'a> Step {
Self::rows_and_columns(*layout, *spacing)
}
Step::Scrollable => Self::scrollable(),
Step::TextInput { value, is_secure } => {
Self::text_input(value, *is_secure)
}
Step::TextInput {
value,
is_secure,
is_showing_icon,
} => Self::text_input(value, *is_secure, *is_showing_icon),
Step::Debugger => Self::debugger(debug),
Step::End => Self::end(),
}
@ -530,14 +557,25 @@ impl<'a> Step {
)
}
fn text_input(value: &str, is_secure: bool) -> Column<'a, StepMessage> {
let text_input = text_input(
"Type something to continue...",
value,
StepMessage::InputChanged,
)
.padding(10)
.size(30);
fn text_input(
value: &str,
is_secure: bool,
is_showing_icon: bool,
) -> Column<'a, StepMessage> {
let mut text_input = text_input("Type something to continue...", value)
.on_input(StepMessage::InputChanged)
.padding(10)
.size(30);
if is_showing_icon {
text_input = text_input.icon(text_input::Icon {
font: Font::default(),
code_point: '🚀',
size: Some(28.0),
spacing: 10.0,
side: text_input::Side::Right,
});
}
Self::container("Text input")
.push("Use a text input to ask for different kinds of information.")
@ -551,6 +589,11 @@ impl<'a> Step {
is_secure,
StepMessage::ToggleSecureInput,
))
.push(checkbox(
"Show icon",
is_showing_icon,
StepMessage::ToggleTextInputIcon,
))
.push(
"A text input produces a message every time it changes. It is \
very easy to keep track of its contents:",

View file

@ -7,4 +7,3 @@ publish = false
[dependencies]
iced = { path = "../.." }
iced_native = { path = "../../native" }

View file

@ -1,12 +1,10 @@
use iced::event::{Event, MacOS, PlatformSpecific};
use iced::executor;
use iced::subscription;
use iced::widget::{container, text};
use iced::{
Application, Command, Element, Length, Settings, Subscription, Theme,
};
use iced_native::{
event::{MacOS, PlatformSpecific},
Event,
};
pub fn main() -> iced::Result {
App::run(Settings::default())
@ -19,7 +17,7 @@ struct App {
#[derive(Debug, Clone)]
enum Message {
EventOccurred(iced_native::Event),
EventOccurred(Event),
}
impl Application for App {
@ -52,7 +50,7 @@ impl Application for App {
}
fn subscription(&self) -> Subscription<Message> {
iced_native::subscription::events().map(Message::EventOccurred)
subscription::events().map(Message::EventOccurred)
}
fn view(&self) -> Element<Message> {

View file

@ -7,8 +7,6 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["tokio", "debug"] }
iced_native = { path = "../../native" }
iced_futures = { path = "../../futures" }
once_cell = "1.15"
[dependencies.async-tungstenite]

View file

@ -1,7 +1,7 @@
pub mod server;
use iced_futures::futures;
use iced_native::subscription::{self, Subscription};
use iced::futures;
use iced::subscription::{self, Subscription};
use futures::channel::mpsc;
use futures::sink::SinkExt;
@ -13,63 +13,67 @@ use std::fmt;
pub fn connect() -> Subscription<Event> {
struct Connect;
subscription::unfold(
subscription::channel(
std::any::TypeId::of::<Connect>(),
State::Disconnected,
|state| async move {
match state {
State::Disconnected => {
const ECHO_SERVER: &str = "ws://localhost:3030";
100,
|mut output| async move {
let mut state = State::Disconnected;
match async_tungstenite::tokio::connect_async(ECHO_SERVER)
loop {
match &mut state {
State::Disconnected => {
const ECHO_SERVER: &str = "ws://127.0.0.1:3030";
match async_tungstenite::tokio::connect_async(
ECHO_SERVER,
)
.await
{
Ok((websocket, _)) => {
let (sender, receiver) = mpsc::channel(100);
{
Ok((websocket, _)) => {
let (sender, receiver) = mpsc::channel(100);
(
Some(Event::Connected(Connection(sender))),
State::Connected(websocket, receiver),
)
}
Err(_) => {
tokio::time::sleep(
tokio::time::Duration::from_secs(1),
)
.await;
let _ = output
.send(Event::Connected(Connection(sender)))
.await;
(Some(Event::Disconnected), State::Disconnected)
}
}
}
State::Connected(mut websocket, mut input) => {
let mut fused_websocket = websocket.by_ref().fuse();
state = State::Connected(websocket, receiver);
}
Err(_) => {
tokio::time::sleep(
tokio::time::Duration::from_secs(1),
)
.await;
futures::select! {
received = fused_websocket.select_next_some() => {
match received {
Ok(tungstenite::Message::Text(message)) => {
(
Some(Event::MessageReceived(Message::User(message))),
State::Connected(websocket, input)
)
}
Ok(_) => {
(None, State::Connected(websocket, input))
}
Err(_) => {
(Some(Event::Disconnected), State::Disconnected)
}
let _ = output.send(Event::Disconnected).await;
}
}
}
State::Connected(websocket, input) => {
let mut fused_websocket = websocket.by_ref().fuse();
message = input.select_next_some() => {
let result = websocket.send(tungstenite::Message::Text(message.to_string())).await;
futures::select! {
received = fused_websocket.select_next_some() => {
match received {
Ok(tungstenite::Message::Text(message)) => {
let _ = output.send(Event::MessageReceived(Message::User(message))).await;
}
Err(_) => {
let _ = output.send(Event::Disconnected).await;
if result.is_ok() {
(None, State::Connected(websocket, input))
} else {
(Some(Event::Disconnected), State::Disconnected)
state = State::Disconnected;
}
Ok(_) => continue,
}
}
message = input.select_next_some() => {
let result = websocket.send(tungstenite::Message::Text(message.to_string())).await;
if result.is_err() {
let _ = output.send(Event::Disconnected).await;
state = State::Disconnected;
}
}
}
}

View file

@ -1,4 +1,4 @@
use iced_futures::futures;
use iced::futures;
use futures::channel::mpsc;
use futures::{SinkExt, StreamExt};

View file

@ -125,12 +125,9 @@ impl Application for WebSocket {
};
let new_message_input = {
let mut input = text_input(
"Type a message...",
&self.new_message,
Message::NewMessageChanged,
)
.padding(10);
let mut input = text_input("Type a message...", &self.new_message)
.on_input(Message::NewMessageChanged)
.padding(10);
let mut button = button(
text("Send")