Adds linear gradient support to 2D meshes in the canvas widget.
This commit is contained in:
parent
97f385e093
commit
00a8a16712
40 changed files with 2041 additions and 653 deletions
|
|
@ -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, Cache, Canvas, Cursor, Geometry, Path, Stroke, StrokeStyle,
|
||||
};
|
||||
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: StrokeStyle::Solid(palette.text),
|
||||
width: 10.0,
|
||||
..Stroke::default()
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
use iced::executor;
|
||||
use iced::widget::canvas::{Cache, Cursor, Geometry, LineCap, Path, Stroke};
|
||||
use iced::widget::canvas::{
|
||||
Cache, Cursor, Geometry, LineCap, Path, Stroke, StrokeStyle,
|
||||
};
|
||||
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: StrokeStyle::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: StrokeStyle::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());
|
||||
})
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
//! This example showcases a simple native custom widget that renders using
|
||||
//! arbitrary low-level geometry.
|
||||
//!
|
||||
//TODO need to update this now that vertex data doesn't contain color data
|
||||
mod rainbow {
|
||||
use iced::Color;
|
||||
// For now, to implement a custom native widget you will need to add
|
||||
// `iced_native` and `iced_wgpu` to your dependencies.
|
||||
//
|
||||
|
|
@ -12,6 +15,7 @@ mod rainbow {
|
|||
// implemented by `iced_wgpu` and other renderers.
|
||||
use iced_graphics::renderer::{self, Renderer};
|
||||
use iced_graphics::{Backend, Primitive};
|
||||
use iced_graphics::shader::Shader;
|
||||
|
||||
use iced_native::widget::{self, Widget};
|
||||
use iced_native::{
|
||||
|
|
@ -63,20 +67,20 @@ mod rainbow {
|
|||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
use iced_graphics::triangle::{Mesh2D, Vertex2D};
|
||||
use iced_graphics::triangle::{Mesh2D, Shader, Vertex2D};
|
||||
use iced_native::Renderer as _;
|
||||
|
||||
let b = layout.bounds();
|
||||
|
||||
// R O Y G B I V
|
||||
let color_r = [1.0, 0.0, 0.0, 1.0];
|
||||
let color_o = [1.0, 0.5, 0.0, 1.0];
|
||||
let color_y = [1.0, 1.0, 0.0, 1.0];
|
||||
let color_g = [0.0, 1.0, 0.0, 1.0];
|
||||
let color_gb = [0.0, 1.0, 0.5, 1.0];
|
||||
let color_b = [0.0, 0.2, 1.0, 1.0];
|
||||
let color_i = [0.5, 0.0, 1.0, 1.0];
|
||||
let color_v = [0.75, 0.0, 0.5, 1.0];
|
||||
// let color_r = [1.0, 0.0, 0.0, 1.0];
|
||||
// let color_o = [1.0, 0.5, 0.0, 1.0];
|
||||
// let color_y = [1.0, 1.0, 0.0, 1.0];
|
||||
// let color_g = [0.0, 1.0, 0.0, 1.0];
|
||||
// let color_gb = [0.0, 1.0, 0.5, 1.0];
|
||||
// let color_b = [0.0, 0.2, 1.0, 1.0];
|
||||
// let color_i = [0.5, 0.0, 1.0, 1.0];
|
||||
// let color_v = [0.75, 0.0, 0.5, 1.0];
|
||||
|
||||
let posn_center = {
|
||||
if b.contains(cursor_position) {
|
||||
|
|
@ -101,39 +105,39 @@ mod rainbow {
|
|||
vertices: vec![
|
||||
Vertex2D {
|
||||
position: posn_center,
|
||||
color: [1.0, 1.0, 1.0, 1.0],
|
||||
// color: [1.0, 1.0, 1.0, 1.0],
|
||||
},
|
||||
Vertex2D {
|
||||
position: posn_tl,
|
||||
color: color_r,
|
||||
// color: color_r,
|
||||
},
|
||||
Vertex2D {
|
||||
position: posn_t,
|
||||
color: color_o,
|
||||
// color: color_o,
|
||||
},
|
||||
Vertex2D {
|
||||
position: posn_tr,
|
||||
color: color_y,
|
||||
// color: color_y,
|
||||
},
|
||||
Vertex2D {
|
||||
position: posn_r,
|
||||
color: color_g,
|
||||
// color: color_g,
|
||||
},
|
||||
Vertex2D {
|
||||
position: posn_br,
|
||||
color: color_gb,
|
||||
// color: color_gb,
|
||||
},
|
||||
Vertex2D {
|
||||
position: posn_b,
|
||||
color: color_b,
|
||||
// color: color_b,
|
||||
},
|
||||
Vertex2D {
|
||||
position: posn_bl,
|
||||
color: color_i,
|
||||
// color: color_i,
|
||||
},
|
||||
Vertex2D {
|
||||
position: posn_l,
|
||||
color: color_v,
|
||||
// color: color_v,
|
||||
},
|
||||
],
|
||||
indices: vec![
|
||||
|
|
@ -147,6 +151,7 @@ mod rainbow {
|
|||
0, 8, 1, // L
|
||||
],
|
||||
},
|
||||
shader: Shader::Solid(Color::BLACK),
|
||||
};
|
||||
|
||||
renderer.with_translation(Vector::new(b.x, b.y), |renderer| {
|
||||
|
|
|
|||
10
examples/modern_art/Cargo.toml
Normal file
10
examples/modern_art/Cargo.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
[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"
|
||||
141
examples/modern_art/src/main.rs
Normal file
141
examples/modern_art/src/main.rs
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
use rand::{Rng, thread_rng};
|
||||
use crate::canvas::{Cursor, FillStyle, Geometry, Gradient};
|
||||
use iced::widget::canvas::{Cache, Fill, Frame};
|
||||
use iced::widget::{canvas, Canvas};
|
||||
use iced::Settings;
|
||||
use iced::{
|
||||
executor, Application, Color, Command, Element, Length, Point, Rectangle,
|
||||
Renderer, Size, Theme,
|
||||
};
|
||||
|
||||
fn main() -> iced::Result {
|
||||
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: Self::Message) -> Command<Self::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 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, bottom_right: Point| -> Gradient {
|
||||
let mut builder = Gradient::linear(top_left, bottom_right);
|
||||
let stops = thread_rng().gen_range(1..64u32);
|
||||
|
||||
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,
|
||||
Fill {
|
||||
style: FillStyle::Solid(random_color()),
|
||||
.. Default::default()
|
||||
}
|
||||
);
|
||||
} else {
|
||||
frame.fill_rectangle(
|
||||
top_left,
|
||||
size,
|
||||
Fill {
|
||||
style: FillStyle::Gradient(&gradient(
|
||||
top_left,
|
||||
Point::new(top_left.x + size.width, top_left.y + size.height)
|
||||
)),
|
||||
.. Default::default()
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
solid
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ use iced::{
|
|||
};
|
||||
|
||||
use std::time::Instant;
|
||||
use crate::canvas::StrokeStyle;
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
SolarSystem::run(Settings {
|
||||
|
|
@ -37,9 +38,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 +66,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)
|
||||
|
|
@ -86,6 +83,10 @@ impl Application for SolarSystem {
|
|||
text_color: Color::WHITE,
|
||||
})
|
||||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
time::every(std::time::Duration::from_millis(10)).map(Message::Tick)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -178,8 +179,8 @@ impl<Message> canvas::Program<Message> for State {
|
|||
frame.stroke(
|
||||
&orbit,
|
||||
Stroke {
|
||||
style: StrokeStyle::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],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue