Merge branch 'master' into feat/slider-orientation

This commit is contained in:
Casper Storm 2022-12-13 09:31:57 +01:00
commit 2e6d90f141
No known key found for this signature in database
GPG key ID: BABF49AA70C405C2
174 changed files with 7170 additions and 1989 deletions

View file

@ -2,7 +2,7 @@ use std::{f32::consts::PI, time::Instant};
use iced::executor;
use iced::widget::canvas::{
self, Cache, Canvas, Cursor, Geometry, Path, Stroke,
self, stroke, Cache, Canvas, Cursor, Geometry, Path, Stroke,
};
use iced::{
Application, Command, Element, Length, Point, Rectangle, Settings,
@ -52,11 +52,6 @@ impl Application for Arc {
Command::none()
}
fn subscription(&self) -> Subscription<Message> {
iced::time::every(std::time::Duration::from_millis(10))
.map(|_| Message::Tick)
}
fn view(&self) -> Element<Message> {
Canvas::new(self)
.width(Length::Fill)
@ -67,6 +62,11 @@ impl Application for Arc {
fn theme(&self) -> Theme {
Theme::Dark
}
fn subscription(&self) -> Subscription<Message> {
iced::time::every(std::time::Duration::from_millis(10))
.map(|_| Message::Tick)
}
}
impl<Message> canvas::Program<Message> for Arc {
@ -114,7 +114,7 @@ impl<Message> canvas::Program<Message> for Arc {
frame.stroke(
&path,
Stroke {
color: palette.text,
style: stroke::Style::Solid(palette.text),
width: 10.0,
..Stroke::default()
},

View file

@ -0,0 +1,10 @@
[package]
name = "cached"
version = "0.1.0"
authors = ["Nick Senger <dev@nsenger.com>"]
edition = "2021"
publish = false
[dependencies]
iced = { path = "../..", features = ["debug"] }
iced_lazy = { path = "../../lazy" }

139
examples/cached/src/main.rs Normal file
View file

@ -0,0 +1,139 @@
use iced::theme;
use iced::widget::{
button, column, horizontal_space, row, scrollable, text, text_input,
};
use iced::{Element, Length, Sandbox, Settings};
use iced_lazy::lazy;
use std::collections::HashSet;
pub fn main() -> iced::Result {
App::run(Settings::default())
}
struct App {
options: HashSet<String>,
input: String,
order: Order,
}
impl Default for App {
fn default() -> Self {
Self {
options: ["Foo", "Bar", "Baz", "Qux", "Corge", "Waldo", "Fred"]
.into_iter()
.map(ToString::to_string)
.collect(),
input: Default::default(),
order: Order::Ascending,
}
}
}
#[derive(Debug, Clone)]
enum Message {
InputChanged(String),
ToggleOrder,
DeleteOption(String),
AddOption(String),
}
impl Sandbox for App {
type Message = Message;
fn new() -> Self {
Self::default()
}
fn title(&self) -> String {
String::from("Cached - Iced")
}
fn update(&mut self, message: Message) {
match message {
Message::InputChanged(input) => {
self.input = input;
}
Message::ToggleOrder => {
self.order = match self.order {
Order::Ascending => Order::Descending,
Order::Descending => Order::Ascending,
}
}
Message::AddOption(option) => {
self.options.insert(option);
self.input.clear();
}
Message::DeleteOption(option) => {
self.options.remove(&option);
}
}
}
fn view(&self) -> Element<Message> {
let options = lazy((&self.order, self.options.len()), || {
let mut options: Vec<_> = self.options.iter().collect();
options.sort_by(|a, b| match self.order {
Order::Ascending => a.to_lowercase().cmp(&b.to_lowercase()),
Order::Descending => b.to_lowercase().cmp(&a.to_lowercase()),
});
column(
options
.into_iter()
.map(|option| {
row![
text(option),
horizontal_space(Length::Fill),
button("Delete")
.on_press(Message::DeleteOption(
option.to_string(),
),)
.style(theme::Button::Destructive)
]
.into()
})
.collect(),
)
.spacing(10)
});
column![
scrollable(options).height(Length::Fill),
row![
text_input(
"Add a new option",
&self.input,
Message::InputChanged,
)
.on_submit(Message::AddOption(self.input.clone())),
button(text(format!("Toggle Order ({})", self.order)))
.on_press(Message::ToggleOrder)
]
.spacing(10)
]
.spacing(20)
.padding(20)
.into()
}
}
#[derive(Debug, Hash)]
enum Order {
Ascending,
Descending,
}
impl std::fmt::Display for Order {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Ascending => "Ascending",
Self::Descending => "Descending",
}
)
}
}

View file

@ -1,5 +1,7 @@
use iced::executor;
use iced::widget::canvas::{Cache, Cursor, Geometry, LineCap, Path, Stroke};
use iced::widget::canvas::{
stroke, Cache, Cursor, Geometry, LineCap, Path, Stroke,
};
use iced::widget::{canvas, container};
use iced::{
Application, Color, Command, Element, Length, Point, Rectangle, Settings,
@ -24,9 +26,9 @@ enum Message {
}
impl Application for Clock {
type Executor = executor::Default;
type Message = Message;
type Theme = Theme;
type Executor = executor::Default;
type Flags = ();
fn new(_flags: ()) -> (Self, Command<Message>) {
@ -59,15 +61,6 @@ impl Application for Clock {
Command::none()
}
fn subscription(&self) -> Subscription<Message> {
iced::time::every(std::time::Duration::from_millis(500)).map(|_| {
Message::Tick(
time::OffsetDateTime::now_local()
.unwrap_or_else(|_| time::OffsetDateTime::now_utc()),
)
})
}
fn view(&self) -> Element<Message> {
let canvas = canvas(self as &Self)
.width(Length::Fill)
@ -79,6 +72,15 @@ impl Application for Clock {
.padding(20)
.into()
}
fn subscription(&self) -> Subscription<Message> {
iced::time::every(std::time::Duration::from_millis(500)).map(|_| {
Message::Tick(
time::OffsetDateTime::now_local()
.unwrap_or_else(|_| time::OffsetDateTime::now_utc()),
)
})
}
}
impl<Message> canvas::Program<Message> for Clock {
@ -104,33 +106,41 @@ impl<Message> canvas::Program<Message> for Clock {
let long_hand =
Path::line(Point::ORIGIN, Point::new(0.0, -0.8 * radius));
let thin_stroke = Stroke {
width: radius / 100.0,
color: Color::WHITE,
line_cap: LineCap::Round,
..Stroke::default()
let width = radius / 100.0;
let thin_stroke = || -> Stroke {
Stroke {
width,
style: stroke::Style::Solid(Color::WHITE),
line_cap: LineCap::Round,
..Stroke::default()
}
};
let wide_stroke = Stroke {
width: thin_stroke.width * 3.0,
..thin_stroke
let wide_stroke = || -> Stroke {
Stroke {
width: width * 3.0,
style: stroke::Style::Solid(Color::WHITE),
line_cap: LineCap::Round,
..Stroke::default()
}
};
frame.translate(Vector::new(center.x, center.y));
frame.with_save(|frame| {
frame.rotate(hand_rotation(self.now.hour(), 12));
frame.stroke(&short_hand, wide_stroke);
frame.stroke(&short_hand, wide_stroke());
});
frame.with_save(|frame| {
frame.rotate(hand_rotation(self.now.minute(), 60));
frame.stroke(&long_hand, wide_stroke);
frame.stroke(&long_hand, wide_stroke());
});
frame.with_save(|frame| {
frame.rotate(hand_rotation(self.now.second(), 60));
frame.stroke(&long_hand, thin_stroke);
frame.stroke(&long_hand, thin_stroke());
})
});

View file

@ -0,0 +1,10 @@
[package]
name = "custom_quad"
version = "0.1.0"
authors = ["Robert Krahn"]
edition = "2021"
publish = false
[dependencies]
iced = { path = "../.." }
iced_native = { path = "../../native" }

View file

@ -0,0 +1,160 @@
//! 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};
pub struct CustomQuad {
size: f32,
radius: [f32; 4],
border_width: f32,
}
impl CustomQuad {
pub fn new(size: f32, radius: [f32; 4], border_width: f32) -> Self {
Self {
size,
radius,
border_width,
}
}
}
impl<Message, Renderer> Widget<Message, Renderer> for CustomQuad
where
Renderer: renderer::Renderer,
{
fn width(&self) -> Length {
Length::Shrink
}
fn height(&self) -> Length {
Length::Shrink
}
fn layout(
&self,
_renderer: &Renderer,
_limits: &layout::Limits,
) -> layout::Node {
layout::Node::new(Size::new(self.size, self.size))
}
fn draw(
&self,
_state: &widget::Tree,
renderer: &mut Renderer,
_theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
) {
renderer.fill_quad(
renderer::Quad {
bounds: layout.bounds(),
border_radius: self.radius.into(),
border_width: self.border_width,
border_color: Color::from_rgb(1.0, 0.0, 0.0),
},
Color::BLACK,
);
}
}
impl<'a, Message, Renderer> From<CustomQuad> for Element<'a, Message, Renderer>
where
Renderer: renderer::Renderer,
{
fn from(circle: CustomQuad) -> Self {
Self::new(circle)
}
}
}
use iced::widget::{column, container, slider, text};
use iced::{Alignment, Element, Length, Sandbox, Settings};
pub fn main() -> iced::Result {
Example::run(Settings::default())
}
struct Example {
radius: [f32; 4],
border_width: f32,
}
#[derive(Debug, Clone, Copy)]
#[allow(clippy::enum_variant_names)]
enum Message {
RadiusTopLeftChanged(f32),
RadiusTopRightChanged(f32),
RadiusBottomRightChanged(f32),
RadiusBottomLeftChanged(f32),
BorderWidthChanged(f32),
}
impl Sandbox for Example {
type Message = Message;
fn new() -> Self {
Self {
radius: [50.0; 4],
border_width: 0.0,
}
}
fn title(&self) -> String {
String::from("Custom widget - Iced")
}
fn update(&mut self, message: Message) {
let [tl, tr, br, bl] = self.radius;
match message {
Message::RadiusTopLeftChanged(radius) => {
self.radius = [radius, tr, br, bl];
}
Message::RadiusTopRightChanged(radius) => {
self.radius = [tl, radius, br, bl];
}
Message::RadiusBottomRightChanged(radius) => {
self.radius = [tl, tr, radius, bl];
}
Message::RadiusBottomLeftChanged(radius) => {
self.radius = [tl, tr, br, radius];
}
Message::BorderWidthChanged(width) => {
self.border_width = width;
}
}
}
fn view(&self) -> Element<Message> {
let [tl, tr, br, bl] = self.radius;
let content = column![
quad::CustomQuad::new(200.0, self.radius, self.border_width),
text(format!("Radius: {tl:.2}/{tr:.2}/{br:.2}/{bl:.2}")),
slider(1.0..=100.0, tl, Message::RadiusTopLeftChanged).step(0.01),
slider(1.0..=100.0, tr, Message::RadiusTopRightChanged).step(0.01),
slider(1.0..=100.0, br, Message::RadiusBottomRightChanged)
.step(0.01),
slider(1.0..=100.0, bl, Message::RadiusBottomLeftChanged)
.step(0.01),
slider(1.0..=10.0, self.border_width, Message::BorderWidthChanged)
.step(0.01),
]
.padding(20)
.spacing(20)
.max_width(500)
.align_items(Alignment::Center);
container(content)
.width(Length::Fill)
.height(Length::Fill)
.center_x()
.center_y()
.into()
}
}

View file

@ -61,7 +61,7 @@ mod circle {
renderer.fill_quad(
renderer::Quad {
bounds: layout.bounds(),
border_radius: self.radius,
border_radius: self.radius.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
},

View file

@ -11,22 +11,18 @@ mod rainbow {
// 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::{
layout, Element, Layout, Length, Point, Rectangle, Size, Vector,
Element, Layout, Length, Point, Rectangle, Size, Vector,
};
#[derive(Default)]
#[derive(Debug, Clone, Copy, Default)]
pub struct Rainbow;
impl Rainbow {
pub fn new() -> Self {
Self
}
}
pub fn rainbow() -> Rainbow {
Rainbow
}
@ -63,7 +59,7 @@ mod rainbow {
cursor_position: Point,
_viewport: &Rectangle,
) {
use iced_graphics::triangle::{Mesh2D, Vertex2D};
use iced_graphics::triangle::Mesh2D;
use iced_native::Renderer as _;
let b = layout.bounds();
@ -95,43 +91,43 @@ mod rainbow {
let posn_bl = [0.0, b.height];
let posn_l = [0.0, b.height / 2.0];
let mesh = Primitive::Mesh2D {
let mesh = Primitive::SolidMesh {
size: b.size(),
buffers: Mesh2D {
vertices: vec![
Vertex2D {
ColoredVertex2D {
position: posn_center,
color: [1.0, 1.0, 1.0, 1.0],
},
Vertex2D {
ColoredVertex2D {
position: posn_tl,
color: color_r,
},
Vertex2D {
ColoredVertex2D {
position: posn_t,
color: color_o,
},
Vertex2D {
ColoredVertex2D {
position: posn_tr,
color: color_y,
},
Vertex2D {
ColoredVertex2D {
position: posn_r,
color: color_g,
},
Vertex2D {
ColoredVertex2D {
position: posn_br,
color: color_gb,
},
Vertex2D {
ColoredVertex2D {
position: posn_b,
color: color_b,
},
Vertex2D {
ColoredVertex2D {
position: posn_bl,
color: color_i,
},
Vertex2D {
ColoredVertex2D {
position: posn_l,
color: color_v,
},
@ -166,7 +162,7 @@ mod rainbow {
}
use iced::widget::{column, container, scrollable};
use iced::{Alignment, Element, Length, Sandbox, Settings};
use iced::{Element, Length, Sandbox, Settings};
use rainbow::rainbow;
pub fn main() -> iced::Result {
@ -179,7 +175,7 @@ impl Sandbox for Example {
type Message = ();
fn new() -> Self {
Example
Self
}
fn title(&self) -> String {
@ -202,8 +198,7 @@ impl Sandbox for Example {
]
.padding(20)
.spacing(20)
.max_width(500)
.align_items(Alignment::Start);
.max_width(500);
let scrollable =
scrollable(container(content).width(Length::Fill).center_x());

10
examples/lazy/Cargo.toml Normal file
View file

@ -0,0 +1,10 @@
[package]
name = "lazy"
version = "0.1.0"
authors = ["Nick Senger <dev@nsenger.com>"]
edition = "2021"
publish = false
[dependencies]
iced = { path = "../..", features = ["debug"] }
iced_lazy = { path = "../../lazy" }

139
examples/lazy/src/main.rs Normal file
View file

@ -0,0 +1,139 @@
use iced::theme;
use iced::widget::{
button, column, horizontal_space, row, scrollable, text, text_input,
};
use iced::{Element, Length, Sandbox, Settings};
use iced_lazy::lazy;
use std::collections::HashSet;
pub fn main() -> iced::Result {
App::run(Settings::default())
}
struct App {
options: HashSet<String>,
input: String,
order: Order,
}
impl Default for App {
fn default() -> Self {
Self {
options: ["Foo", "Bar", "Baz", "Qux", "Corge", "Waldo", "Fred"]
.into_iter()
.map(ToString::to_string)
.collect(),
input: Default::default(),
order: Order::Ascending,
}
}
}
#[derive(Debug, Clone)]
enum Message {
InputChanged(String),
ToggleOrder,
DeleteOption(String),
AddOption(String),
}
impl Sandbox for App {
type Message = Message;
fn new() -> Self {
Self::default()
}
fn title(&self) -> String {
String::from("Cached - Iced")
}
fn update(&mut self, message: Message) {
match message {
Message::InputChanged(input) => {
self.input = input;
}
Message::ToggleOrder => {
self.order = match self.order {
Order::Ascending => Order::Descending,
Order::Descending => Order::Ascending,
}
}
Message::AddOption(option) => {
self.options.insert(option);
self.input.clear();
}
Message::DeleteOption(option) => {
self.options.remove(&option);
}
}
}
fn view(&self) -> Element<Message> {
let options = lazy((&self.order, self.options.len()), || {
let mut options: Vec<_> = self.options.iter().collect();
options.sort_by(|a, b| match self.order {
Order::Ascending => a.to_lowercase().cmp(&b.to_lowercase()),
Order::Descending => b.to_lowercase().cmp(&a.to_lowercase()),
});
column(
options
.into_iter()
.map(|option| {
row![
text(option),
horizontal_space(Length::Fill),
button("Delete")
.on_press(Message::DeleteOption(
option.to_string(),
),)
.style(theme::Button::Destructive)
]
.into()
})
.collect(),
)
.spacing(10)
});
column![
scrollable(options).height(Length::Fill),
row![
text_input(
"Add a new option",
&self.input,
Message::InputChanged,
)
.on_submit(Message::AddOption(self.input.clone())),
button(text(format!("Toggle Order ({})", self.order)))
.on_press(Message::ToggleOrder)
]
.spacing(10)
]
.spacing(20)
.padding(20)
.into()
}
}
#[derive(Debug, Hash)]
enum Order {
Ascending,
Descending,
}
impl std::fmt::Display for Order {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Ascending => "Ascending",
Self::Descending => "Descending",
}
)
}
}

10
examples/modal/Cargo.toml Normal file
View file

@ -0,0 +1,10 @@
[package]
name = "modal"
version = "0.1.0"
authors = ["tarkah <admin@tarkah.dev>"]
edition = "2021"
publish = false
[dependencies]
iced = { path = "../..", features = [] }
iced_native = { path = "../../native" }

475
examples/modal/src/main.rs Normal file
View file

@ -0,0 +1,475 @@
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,
};
use self::modal::Modal;
pub fn main() -> iced::Result {
App::run(Settings::default())
}
#[derive(Default)]
struct App {
show_modal: bool,
email: String,
password: String,
}
#[derive(Debug, Clone)]
enum Message {
ShowModal,
HideModal,
Email(String),
Password(String),
Submit,
Event(Event),
}
impl Application for App {
type Executor = executor::Default;
type Message = Message;
type Theme = iced::Theme;
type Flags = ();
fn new(_flags: ()) -> (Self, Command<Message>) {
(App::default(), Command::none())
}
fn title(&self) -> String {
String::from("Modal - Iced")
}
fn subscription(&self) -> Subscription<Self::Message> {
subscription::events().map(Message::Event)
}
fn update(&mut self, message: Message) -> Command<Message> {
match message {
Message::ShowModal => {
self.show_modal = true;
widget::focus_next()
}
Message::HideModal => {
self.hide_modal();
Command::none()
}
Message::Email(email) => {
self.email = email;
Command::none()
}
Message::Password(password) => {
self.password = password;
Command::none()
}
Message::Submit => {
if !self.email.is_empty() && !self.password.is_empty() {
self.hide_modal();
}
Command::none()
}
Message::Event(event) => match event {
Event::Keyboard(keyboard::Event::KeyPressed {
key_code: keyboard::KeyCode::Tab,
modifiers,
}) => {
if modifiers.shift() {
widget::focus_previous()
} else {
widget::focus_next()
}
}
Event::Keyboard(keyboard::Event::KeyPressed {
key_code: keyboard::KeyCode::Escape,
..
}) => {
self.hide_modal();
Command::none()
}
_ => Command::none(),
},
}
}
fn view(&self) -> Element<Message> {
let content = container(
column![
row![
text("Top Left"),
horizontal_space(Length::Fill),
text("Top Right")
]
.align_items(Alignment::Start)
.height(Length::Fill),
container(
button(text("Show Modal")).on_press(Message::ShowModal)
)
.center_x()
.center_y()
.width(Length::Fill)
.height(Length::Fill),
row![
text("Bottom Left"),
horizontal_space(Length::Fill),
text("Bottom Right")
]
.align_items(Alignment::End)
.height(Length::Fill),
]
.height(Length::Fill),
)
.padding(10)
.width(Length::Fill)
.height(Length::Fill);
if self.show_modal {
let modal = container(
column![
text("Sign Up").size(24),
column![
column![
text("Email").size(12),
text_input(
"abc@123.com",
&self.email,
Message::Email
)
.on_submit(Message::Submit)
.padding(5),
]
.spacing(5),
column![
text("Password").size(12),
text_input("", &self.password, Message::Password)
.on_submit(Message::Submit)
.password()
.padding(5),
]
.spacing(5),
button(text("Submit")).on_press(Message::HideModal),
]
.spacing(10)
]
.spacing(20),
)
.width(Length::Units(300))
.padding(10)
.style(theme::Container::Box);
Modal::new(content, modal)
.on_blur(Message::HideModal)
.into()
} else {
content.into()
}
}
}
impl App {
fn hide_modal(&mut self) {
self.show_modal = false;
self.email.clear();
self.password.clear();
}
}
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,
};
/// A widget that centers a modal element over some base element
pub struct Modal<'a, Message, Renderer> {
base: Element<'a, Message, Renderer>,
modal: Element<'a, Message, Renderer>,
on_blur: Option<Message>,
}
impl<'a, Message, Renderer> Modal<'a, Message, Renderer> {
/// Returns a new [`Modal`]
pub fn new(
base: impl Into<Element<'a, Message, Renderer>>,
modal: impl Into<Element<'a, Message, Renderer>>,
) -> Self {
Self {
base: base.into(),
modal: modal.into(),
on_blur: None,
}
}
/// Sets the message that will be produces when the background
/// of the [`Modal`] is pressed
pub fn on_blur(self, on_blur: Message) -> Self {
Self {
on_blur: Some(on_blur),
..self
}
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Modal<'a, Message, Renderer>
where
Renderer: iced_native::Renderer,
Message: Clone,
{
fn children(&self) -> Vec<Tree> {
vec![Tree::new(&self.base), Tree::new(&self.modal)]
}
fn diff(&self, tree: &mut Tree) {
tree.diff_children(&[&self.base, &self.modal]);
}
fn width(&self) -> Length {
self.base.as_widget().width()
}
fn height(&self) -> Length {
self.base.as_widget().height()
}
fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
self.base.as_widget().layout(renderer, limits)
}
fn on_event(
&mut self,
state: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
self.base.as_widget_mut().on_event(
&mut state.children[0],
event,
layout,
cursor_position,
renderer,
clipboard,
shell,
)
}
fn draw(
&self,
state: &Tree,
renderer: &mut Renderer,
theme: &<Renderer as iced_native::Renderer>::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) {
self.base.as_widget().draw(
&state.children[0],
renderer,
theme,
style,
layout,
cursor_position,
viewport,
);
}
fn overlay<'b>(
&'b mut self,
state: &'b mut Tree,
layout: Layout<'_>,
_renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
Some(overlay::Element::new(
layout.position(),
Box::new(Overlay {
content: &mut self.modal,
tree: &mut state.children[1],
size: layout.bounds().size(),
on_blur: self.on_blur.clone(),
}),
))
}
fn mouse_interaction(
&self,
state: &Tree,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.base.as_widget().mouse_interaction(
&state.children[0],
layout,
cursor_position,
viewport,
renderer,
)
}
fn operate(
&self,
state: &mut Tree,
layout: Layout<'_>,
operation: &mut dyn widget::Operation<Message>,
) {
self.base.as_widget().operate(
&mut state.children[0],
layout,
operation,
);
}
}
struct Overlay<'a, 'b, Message, Renderer> {
content: &'b mut Element<'a, Message, Renderer>,
tree: &'b mut Tree,
size: Size,
on_blur: Option<Message>,
}
impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, Renderer>
for Overlay<'a, 'b, Message, Renderer>
where
Renderer: iced_native::Renderer,
Message: Clone,
{
fn layout(
&self,
renderer: &Renderer,
_bounds: Size,
position: Point,
) -> layout::Node {
let limits = layout::Limits::new(Size::ZERO, self.size)
.width(Length::Fill)
.height(Length::Fill);
let mut child = self.content.as_widget().layout(renderer, &limits);
child.align(Alignment::Center, Alignment::Center, limits.max());
let mut node = layout::Node::with_children(self.size, vec![child]);
node.move_to(position);
node
}
fn on_event(
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
let content_bounds = layout.children().next().unwrap().bounds();
if let Some(message) = self.on_blur.as_ref() {
if let Event::Mouse(mouse::Event::ButtonPressed(
mouse::Button::Left,
)) = &event
{
if !content_bounds.contains(cursor_position) {
shell.publish(message.clone());
return event::Status::Captured;
}
}
}
self.content.as_widget_mut().on_event(
self.tree,
event,
layout.children().next().unwrap(),
cursor_position,
renderer,
clipboard,
shell,
)
}
fn draw(
&self,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
) {
renderer.fill_quad(
renderer::Quad {
bounds: layout.bounds(),
border_radius: renderer::BorderRadius::from(0.0),
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
Color {
a: 0.80,
..Color::BLACK
},
);
self.content.as_widget().draw(
self.tree,
renderer,
theme,
style,
layout.children().next().unwrap(),
cursor_position,
&layout.bounds(),
);
}
fn operate(
&mut self,
layout: Layout<'_>,
operation: &mut dyn widget::Operation<Message>,
) {
self.content.as_widget().operate(
self.tree,
layout.children().next().unwrap(),
operation,
);
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.content.as_widget().mouse_interaction(
self.tree,
layout.children().next().unwrap(),
cursor_position,
viewport,
renderer,
)
}
}
impl<'a, Message, Renderer> From<Modal<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Renderer: 'a + iced_native::Renderer,
Message: 'a + Clone,
{
fn from(modal: Modal<'a, Message, Renderer>) -> Self {
Element::new(modal)
}
}
}

View file

@ -0,0 +1,11 @@
[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

@ -0,0 +1,142 @@
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

@ -2,7 +2,8 @@
//! a circle around each fingertip. This only works on touch-enabled
//! computers like Microsoft Surface.
use iced::widget::canvas::event;
use iced::widget::canvas::{self, Canvas, Cursor, Geometry, Stroke};
use iced::widget::canvas::stroke::{self, Stroke};
use iced::widget::canvas::{self, Canvas, Cursor, Geometry};
use iced::{
executor, touch, window, Application, Color, Command, Element, Length,
Point, Rectangle, Settings, Subscription, Theme,
@ -186,7 +187,7 @@ impl canvas::Program<Message> for State {
frame.stroke(
&path,
Stroke {
color: Color::BLACK,
style: stroke::Style::Solid(Color::BLACK),
width: 3.0,
..Stroke::default()
},

View file

@ -29,6 +29,8 @@ enum Message {
Dragged(pane_grid::DragEvent),
Resized(pane_grid::ResizeEvent),
TogglePin(pane_grid::Pane),
Maximize(pane_grid::Pane),
Restore,
Close(pane_grid::Pane),
CloseFocused,
}
@ -114,6 +116,10 @@ impl Application for Example {
*is_pinned = !*is_pinned;
}
}
Message::Maximize(pane) => self.panes.maximize(&pane),
Message::Restore => {
self.panes.restore();
}
Message::Close(pane) => {
if let Some((_, sibling)) = self.panes.close(&pane) {
self.focus = Some(sibling);
@ -157,7 +163,7 @@ impl Application for Example {
let focus = self.focus;
let total_panes = self.panes.len();
let pane_grid = PaneGrid::new(&self.panes, |id, pane| {
let pane_grid = PaneGrid::new(&self.panes, |id, pane, is_maximized| {
let is_focused = focus == Some(id);
let pin_button = button(
@ -178,7 +184,12 @@ impl Application for Example {
.spacing(5);
let title_bar = pane_grid::TitleBar::new(title)
.controls(view_controls(id, total_panes, pane.is_pinned))
.controls(view_controls(
id,
total_panes,
pane.is_pinned,
is_maximized,
))
.padding(10)
.style(if is_focused {
style::title_bar_focused
@ -314,16 +325,35 @@ fn view_controls<'a>(
pane: pane_grid::Pane,
total_panes: usize,
is_pinned: bool,
is_maximized: bool,
) -> Element<'a, Message> {
let mut button = button(text("Close").size(14))
let mut row = row![].spacing(5);
if total_panes > 1 {
let toggle = {
let (content, message) = if is_maximized {
("Restore", Message::Restore)
} else {
("Maximize", Message::Maximize(pane))
};
button(text(content).size(14))
.style(theme::Button::Secondary)
.padding(3)
.on_press(message)
};
row = row.push(toggle);
}
let mut close = button(text("Close").size(14))
.style(theme::Button::Destructive)
.padding(3);
if total_panes > 1 && !is_pinned {
button = button.on_press(Message::Close(pane));
close = close.on_press(Message::Close(pane));
}
button.into()
row.push(close).into()
}
mod style {

View file

@ -14,9 +14,15 @@ struct ScrollableDemo {
variants: Vec<Variant>,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum ThemeType {
Light,
Dark,
}
#[derive(Debug, Clone)]
enum Message {
ThemeChanged(Theme),
ThemeChanged(ThemeType),
ScrollToTop(usize),
ScrollToBottom(usize),
Scrolled(usize, f32),
@ -45,7 +51,10 @@ impl Application for ScrollableDemo {
fn update(&mut self, message: Message) -> Command<Message> {
match message {
Message::ThemeChanged(theme) => {
self.theme = theme;
self.theme = match theme {
ThemeType::Light => Theme::Light,
ThemeType::Dark => Theme::Dark,
};
Command::none()
}
@ -78,17 +87,15 @@ impl Application for ScrollableDemo {
}
fn view(&self) -> Element<Message> {
let ScrollableDemo {
theme, variants, ..
} = self;
let ScrollableDemo { variants, .. } = self;
let choose_theme = [Theme::Light, Theme::Dark].iter().fold(
let choose_theme = [ThemeType::Light, ThemeType::Dark].iter().fold(
column!["Choose a theme:"].spacing(10),
|column, option| {
column.push(radio(
format!("{:?}", option),
*option,
Some(*theme),
Some(*option),
Message::ThemeChanged,
))
},
@ -198,7 +205,7 @@ impl Application for ScrollableDemo {
}
fn theme(&self) -> Theme {
self.theme
self.theme.clone()
}
}

View file

@ -11,7 +11,9 @@ use iced::executor;
use iced::theme::{self, Theme};
use iced::time;
use iced::widget::canvas;
use iced::widget::canvas::{Cursor, Path, Stroke};
use iced::widget::canvas::gradient::{self, Gradient};
use iced::widget::canvas::stroke::{self, Stroke};
use iced::widget::canvas::{Cursor, Path};
use iced::window;
use iced::{
Application, Color, Command, Element, Length, Point, Rectangle, Settings,
@ -37,9 +39,9 @@ enum Message {
}
impl Application for SolarSystem {
type Executor = executor::Default;
type Message = Message;
type Theme = Theme;
type Executor = executor::Default;
type Flags = ();
fn new(_flags: ()) -> (Self, Command<Message>) {
@ -65,10 +67,6 @@ impl Application for SolarSystem {
Command::none()
}
fn subscription(&self) -> Subscription<Message> {
time::every(std::time::Duration::from_millis(10)).map(Message::Tick)
}
fn view(&self) -> Element<Message> {
canvas(&self.state)
.width(Length::Fill)
@ -81,10 +79,18 @@ impl Application for SolarSystem {
}
fn style(&self) -> theme::Application {
theme::Application::Custom(|_theme| application::Appearance {
background_color: Color::BLACK,
text_color: Color::WHITE,
})
fn dark_background(_theme: &Theme) -> application::Appearance {
application::Appearance {
background_color: Color::BLACK,
text_color: Color::WHITE,
}
}
theme::Application::from(dark_background as fn(&Theme) -> _)
}
fn subscription(&self) -> Subscription<Message> {
time::every(time::Duration::from_millis(10)).map(Message::Tick)
}
}
@ -178,8 +184,10 @@ impl<Message> canvas::Program<Message> for State {
frame.stroke(
&orbit,
Stroke {
style: stroke::Style::Solid(Color::from_rgba8(
0, 153, 255, 0.1,
)),
width: 1.0,
color: Color::from_rgba8(0, 153, 255, 0.1),
line_dash: canvas::LineDash {
offset: 0,
segments: &[3.0, 6.0],
@ -198,15 +206,18 @@ impl<Message> canvas::Program<Message> for State {
frame.translate(Vector::new(Self::ORBIT_RADIUS, 0.0));
let earth = Path::circle(Point::ORIGIN, Self::EARTH_RADIUS);
let shadow = Path::rectangle(
Point::new(0.0, -Self::EARTH_RADIUS),
Size::new(
Self::EARTH_RADIUS * 4.0,
Self::EARTH_RADIUS * 2.0,
),
);
frame.fill(&earth, Color::from_rgb8(0x6B, 0x93, 0xD6));
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");
frame.fill(&earth, earth_fill);
frame.with_save(|frame| {
frame.rotate(rotation * 10.0);
@ -215,14 +226,6 @@ impl<Message> canvas::Program<Message> for State {
let moon = Path::circle(Point::ORIGIN, Self::MOON_RADIUS);
frame.fill(&moon, Color::WHITE);
});
frame.fill(
&shadow,
Color {
a: 0.7,
..Color::BLACK
},
);
});
});

View file

@ -1,9 +1,10 @@
use iced::theme::{self, Theme};
use iced::widget::{
button, checkbox, column, container, horizontal_rule, progress_bar, radio,
row, scrollable, slider, text, text_input, toggler, vertical_rule,
vertical_space,
};
use iced::{Alignment, Element, Length, Sandbox, Settings, Theme};
use iced::{Alignment, Color, Element, Length, Sandbox, Settings};
pub fn main() -> iced::Result {
Styling::run(Settings::default())
@ -18,9 +19,16 @@ struct Styling {
toggler_value: bool,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum ThemeType {
Light,
Dark,
Custom,
}
#[derive(Debug, Clone)]
enum Message {
ThemeChanged(Theme),
ThemeChanged(ThemeType),
InputChanged(String),
ButtonPressed,
SliderChanged(f32),
@ -41,7 +49,19 @@ impl Sandbox for Styling {
fn update(&mut self, message: Message) {
match message {
Message::ThemeChanged(theme) => self.theme = theme,
Message::ThemeChanged(theme) => {
self.theme = match theme {
ThemeType::Light => Theme::Light,
ThemeType::Dark => Theme::Dark,
ThemeType::Custom => Theme::custom(theme::Palette {
background: Color::from_rgb(1.0, 0.9, 1.0),
text: Color::BLACK,
primary: Color::from_rgb(0.5, 0.5, 0.0),
success: Color::from_rgb(0.0, 1.0, 0.0),
danger: Color::from_rgb(1.0, 0.0, 0.0),
}),
}
}
Message::InputChanged(value) => self.input_value = value,
Message::ButtonPressed => {}
Message::SliderChanged(value) => self.slider_value = value,
@ -51,17 +71,24 @@ impl Sandbox for Styling {
}
fn view(&self) -> Element<Message> {
let choose_theme = [Theme::Light, Theme::Dark].iter().fold(
column![text("Choose a theme:")].spacing(10),
|column, theme| {
column.push(radio(
format!("{:?}", theme),
*theme,
Some(self.theme),
Message::ThemeChanged,
))
},
);
let choose_theme =
[ThemeType::Light, ThemeType::Dark, ThemeType::Custom]
.iter()
.fold(
column![text("Choose a theme:")].spacing(10),
|column, theme| {
column.push(radio(
format!("{:?}", theme),
*theme,
Some(match self.theme {
Theme::Light => ThemeType::Light,
Theme::Dark => ThemeType::Dark,
Theme::Custom { .. } => ThemeType::Custom,
}),
Message::ThemeChanged,
))
},
);
let text_input = text_input(
"Type something...",
@ -132,6 +159,6 @@ impl Sandbox for Styling {
}
fn theme(&self) -> Theme {
self.theme
self.theme.clone()
}
}

View file

@ -1,39 +1,76 @@
use iced::widget::{container, svg};
use iced::{Element, Length, Sandbox, Settings};
use iced::theme;
use iced::widget::{checkbox, column, container, svg};
use iced::{color, Element, Length, Sandbox, Settings};
pub fn main() -> iced::Result {
Tiger::run(Settings::default())
}
struct Tiger;
#[derive(Debug, Default)]
struct Tiger {
apply_color_filter: bool,
}
#[derive(Debug, Clone, Copy)]
pub enum Message {
ToggleColorFilter(bool),
}
impl Sandbox for Tiger {
type Message = ();
type Message = Message;
fn new() -> Self {
Tiger
Tiger::default()
}
fn title(&self) -> String {
String::from("SVG - Iced")
}
fn update(&mut self, _message: ()) {}
fn update(&mut self, message: Self::Message) {
match message {
Message::ToggleColorFilter(apply_color_filter) => {
self.apply_color_filter = apply_color_filter;
}
}
}
fn view(&self) -> Element<()> {
let svg = svg(svg::Handle::from_path(format!(
fn view(&self) -> Element<Self::Message> {
let handle = svg::Handle::from_path(format!(
"{}/resources/tiger.svg",
env!("CARGO_MANIFEST_DIR")
)))
.width(Length::Fill)
.height(Length::Fill);
));
container(svg)
let svg = svg(handle).width(Length::Fill).height(Length::Fill).style(
if self.apply_color_filter {
theme::Svg::custom_fn(|_theme| svg::Appearance {
color: Some(color!(0x0000ff)),
})
} else {
theme::Svg::Default
},
);
let apply_color_filter = checkbox(
"Apply a color filter",
self.apply_color_filter,
Message::ToggleColorFilter,
);
container(
column![
svg,
container(apply_color_filter).width(Length::Fill).center_x()
]
.spacing(20)
.width(Length::Fill)
.height(Length::Fill)
.padding(20)
.center_x()
.center_y()
.into()
.height(Length::Fill),
)
.width(Length::Fill)
.height(Length::Fill)
.padding(20)
.center_x()
.center_y()
.into()
}
}

View file

@ -131,7 +131,11 @@ impl Application for Todos {
task.update(task_message);
if should_focus {
text_input::focus(Task::text_input_id(i))
let id = Task::text_input_id(i);
Command::batch(vec![
text_input::focus(id.clone()),
text_input::select_all(id),
])
} else {
Command::none()
}