Merge branch 'master' into non-uniform-border-radius-for-quads
This commit is contained in:
commit
4029a1cdaa
147 changed files with 4828 additions and 2184 deletions
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "iced_graphics"
|
||||
version = "0.3.1"
|
||||
version = "0.4.0"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2021"
|
||||
description = "A bunch of backend-agnostic types that can be leveraged to build a renderer for Iced"
|
||||
|
|
@ -11,28 +11,44 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"]
|
|||
categories = ["gui"]
|
||||
|
||||
[features]
|
||||
svg = ["resvg", "usvg", "tiny-skia"]
|
||||
image = ["png", "jpeg", "jpeg_rayon", "gif", "webp", "bmp"]
|
||||
png = ["image_rs/png"]
|
||||
jpeg = ["image_rs/jpeg"]
|
||||
jpeg_rayon = ["image_rs/jpeg_rayon"]
|
||||
gif = ["image_rs/gif"]
|
||||
webp = ["image_rs/webp"]
|
||||
pnm = ["image_rs/pnm"]
|
||||
ico = ["image_rs/ico"]
|
||||
bmp = ["image_rs/bmp"]
|
||||
hdr = ["image_rs/hdr"]
|
||||
dds = ["image_rs/dds"]
|
||||
farbfeld = ["image_rs/farbfeld"]
|
||||
canvas = ["lyon"]
|
||||
qr_code = ["qrcode", "canvas"]
|
||||
font-source = ["font-kit"]
|
||||
font-fallback = []
|
||||
font-icons = []
|
||||
opengl = []
|
||||
image_rs = ["kamadak-exif"]
|
||||
|
||||
[dependencies]
|
||||
glam = "0.21.3"
|
||||
log = "0.4"
|
||||
raw-window-handle = "0.5"
|
||||
thiserror = "1.0"
|
||||
bitflags = "1.2"
|
||||
|
||||
[dependencies.bytemuck]
|
||||
version = "1.4"
|
||||
features = ["derive"]
|
||||
|
||||
[dependencies.iced_native]
|
||||
version = "0.5"
|
||||
version = "0.6"
|
||||
path = "../native"
|
||||
|
||||
[dependencies.iced_style]
|
||||
version = "0.4"
|
||||
version = "0.5"
|
||||
path = "../style"
|
||||
|
||||
[dependencies.lyon]
|
||||
|
|
@ -48,6 +64,28 @@ default-features = false
|
|||
version = "0.10"
|
||||
optional = true
|
||||
|
||||
[dependencies.image_rs]
|
||||
version = "0.24"
|
||||
package = "image"
|
||||
default-features = false
|
||||
optional = true
|
||||
|
||||
[dependencies.resvg]
|
||||
version = "0.18"
|
||||
optional = true
|
||||
|
||||
[dependencies.usvg]
|
||||
version = "0.18"
|
||||
optional = true
|
||||
|
||||
[dependencies.tiny-skia]
|
||||
version = "0.6"
|
||||
optional = true
|
||||
|
||||
[dependencies.kamadak-exif]
|
||||
version = "0.5"
|
||||
optional = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
all-features = true
|
||||
|
|
|
|||
|
|
@ -66,11 +66,11 @@ pub trait Text {
|
|||
/// A graphics backend that supports image rendering.
|
||||
pub trait Image {
|
||||
/// Returns the dimensions of the provided image.
|
||||
fn dimensions(&self, handle: &image::Handle) -> (u32, u32);
|
||||
fn dimensions(&self, handle: &image::Handle) -> Size<u32>;
|
||||
}
|
||||
|
||||
/// A graphics backend that supports SVG rendering.
|
||||
pub trait Svg {
|
||||
/// Returns the viewport dimensions of the provided SVG.
|
||||
fn viewport_dimensions(&self, handle: &svg::Handle) -> (u32, u32);
|
||||
fn viewport_dimensions(&self, handle: &svg::Handle) -> Size<u32>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use crate::{Color, Point, Size};
|
|||
/// A fill which transitions colors progressively along a direction, either linearly, radially (TBD),
|
||||
/// or conically (TBD).
|
||||
pub enum Gradient {
|
||||
/// A linear gradient interpolates colors along a direction from its [`start`] to its [`end`]
|
||||
/// A linear gradient interpolates colors along a direction from its `start` to its `end`
|
||||
/// point.
|
||||
Linear(Linear),
|
||||
}
|
||||
|
|
@ -23,10 +23,15 @@ impl Gradient {
|
|||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
/// A point along the gradient vector where the specified [`color`] is unmixed.
|
||||
///
|
||||
/// [`color`]: Self::color
|
||||
pub struct ColorStop {
|
||||
/// Offset along the gradient vector.
|
||||
pub offset: f32,
|
||||
|
||||
/// The color of the gradient at the specified [`offset`].
|
||||
///
|
||||
/// [`offset`]: Self::offset
|
||||
pub color: Color,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@
|
|||
use crate::gradient::{ColorStop, Gradient, Position};
|
||||
use crate::{Color, Point};
|
||||
|
||||
/// A linear gradient that can be used in the style of [`super::Fill`] or [`super::Stroke`].
|
||||
/// A linear gradient that can be used in the style of [`Fill`] or [`Stroke`].
|
||||
///
|
||||
/// [`Fill`]: crate::widget::canvas::Fill
|
||||
/// [`Stroke`]: crate::widget::canvas::Stroke
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Linear {
|
||||
/// The point where the linear gradient begins.
|
||||
|
|
|
|||
10
graphics/src/image.rs
Normal file
10
graphics/src/image.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
//! Render images.
|
||||
#[cfg(feature = "image_rs")]
|
||||
pub mod raster;
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
pub mod vector;
|
||||
|
||||
pub mod storage;
|
||||
|
||||
pub use storage::Storage;
|
||||
242
graphics/src/image/raster.rs
Normal file
242
graphics/src/image/raster.rs
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
//! Raster image loading and caching.
|
||||
use crate::image::Storage;
|
||||
use crate::Size;
|
||||
|
||||
use iced_native::image;
|
||||
|
||||
use bitflags::bitflags;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
/// Entry in cache corresponding to an image handle
|
||||
#[derive(Debug)]
|
||||
pub enum Memory<T: Storage> {
|
||||
/// Image data on host
|
||||
Host(::image_rs::ImageBuffer<::image_rs::Rgba<u8>, Vec<u8>>),
|
||||
/// Storage entry
|
||||
Device(T::Entry),
|
||||
/// Image not found
|
||||
NotFound,
|
||||
/// Invalid image data
|
||||
Invalid,
|
||||
}
|
||||
|
||||
impl<T: Storage> Memory<T> {
|
||||
/// Width and height of image
|
||||
pub fn dimensions(&self) -> Size<u32> {
|
||||
use crate::image::storage::Entry;
|
||||
|
||||
match self {
|
||||
Memory::Host(image) => {
|
||||
let (width, height) = image.dimensions();
|
||||
|
||||
Size::new(width, height)
|
||||
}
|
||||
Memory::Device(entry) => entry.size(),
|
||||
Memory::NotFound => Size::new(1, 1),
|
||||
Memory::Invalid => Size::new(1, 1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Caches image raster data
|
||||
#[derive(Debug)]
|
||||
pub struct Cache<T: Storage> {
|
||||
map: HashMap<u64, Memory<T>>,
|
||||
hits: HashSet<u64>,
|
||||
}
|
||||
|
||||
impl<T: Storage> Cache<T> {
|
||||
/// Load image
|
||||
pub fn load(&mut self, handle: &image::Handle) -> &mut Memory<T> {
|
||||
if self.contains(handle) {
|
||||
return self.get(handle).unwrap();
|
||||
}
|
||||
|
||||
let memory = match handle.data() {
|
||||
image::Data::Path(path) => {
|
||||
if let Ok(image) = image_rs::open(path) {
|
||||
let operation = std::fs::File::open(path)
|
||||
.ok()
|
||||
.map(std::io::BufReader::new)
|
||||
.and_then(|mut reader| {
|
||||
Operation::from_exif(&mut reader).ok()
|
||||
})
|
||||
.unwrap_or_else(Operation::empty);
|
||||
|
||||
Memory::Host(operation.perform(image.to_rgba8()))
|
||||
} else {
|
||||
Memory::NotFound
|
||||
}
|
||||
}
|
||||
image::Data::Bytes(bytes) => {
|
||||
if let Ok(image) = image_rs::load_from_memory(bytes) {
|
||||
let operation =
|
||||
Operation::from_exif(&mut std::io::Cursor::new(bytes))
|
||||
.ok()
|
||||
.unwrap_or_else(Operation::empty);
|
||||
|
||||
Memory::Host(operation.perform(image.to_rgba8()))
|
||||
} else {
|
||||
Memory::Invalid
|
||||
}
|
||||
}
|
||||
image::Data::Rgba {
|
||||
width,
|
||||
height,
|
||||
pixels,
|
||||
} => {
|
||||
if let Some(image) = image_rs::ImageBuffer::from_vec(
|
||||
*width,
|
||||
*height,
|
||||
pixels.to_vec(),
|
||||
) {
|
||||
Memory::Host(image)
|
||||
} else {
|
||||
Memory::Invalid
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.insert(handle, memory);
|
||||
self.get(handle).unwrap()
|
||||
}
|
||||
|
||||
/// Load image and upload raster data
|
||||
pub fn upload(
|
||||
&mut self,
|
||||
handle: &image::Handle,
|
||||
state: &mut T::State<'_>,
|
||||
storage: &mut T,
|
||||
) -> Option<&T::Entry> {
|
||||
let memory = self.load(handle);
|
||||
|
||||
if let Memory::Host(image) = memory {
|
||||
let (width, height) = image.dimensions();
|
||||
|
||||
let entry = storage.upload(width, height, image, state)?;
|
||||
|
||||
*memory = Memory::Device(entry);
|
||||
}
|
||||
|
||||
if let Memory::Device(allocation) = memory {
|
||||
Some(allocation)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Trim cache misses from cache
|
||||
pub fn trim(&mut self, storage: &mut T, state: &mut T::State<'_>) {
|
||||
let hits = &self.hits;
|
||||
|
||||
self.map.retain(|k, memory| {
|
||||
let retain = hits.contains(k);
|
||||
|
||||
if !retain {
|
||||
if let Memory::Device(entry) = memory {
|
||||
storage.remove(entry, state);
|
||||
}
|
||||
}
|
||||
|
||||
retain
|
||||
});
|
||||
|
||||
self.hits.clear();
|
||||
}
|
||||
|
||||
fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory<T>> {
|
||||
let _ = self.hits.insert(handle.id());
|
||||
|
||||
self.map.get_mut(&handle.id())
|
||||
}
|
||||
|
||||
fn insert(&mut self, handle: &image::Handle, memory: Memory<T>) {
|
||||
let _ = self.map.insert(handle.id(), memory);
|
||||
}
|
||||
|
||||
fn contains(&self, handle: &image::Handle) -> bool {
|
||||
self.map.contains_key(&handle.id())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Storage> Default for Cache<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
map: HashMap::new(),
|
||||
hits: HashSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
struct Operation: u8 {
|
||||
const FLIP_HORIZONTALLY = 0b001;
|
||||
const ROTATE_180 = 0b010;
|
||||
const FLIP_DIAGONALLY = 0b100;
|
||||
}
|
||||
}
|
||||
|
||||
impl Operation {
|
||||
// Meaning of the returned value is described e.g. at:
|
||||
// https://magnushoff.com/articles/jpeg-orientation/
|
||||
fn from_exif<R>(reader: &mut R) -> Result<Self, exif::Error>
|
||||
where
|
||||
R: std::io::BufRead + std::io::Seek,
|
||||
{
|
||||
let exif = exif::Reader::new().read_from_container(reader)?;
|
||||
|
||||
Ok(exif
|
||||
.get_field(exif::Tag::Orientation, exif::In::PRIMARY)
|
||||
.and_then(|field| field.value.get_uint(0))
|
||||
.and_then(|value| u8::try_from(value).ok())
|
||||
.and_then(|value| Self::from_bits(value.saturating_sub(1)))
|
||||
.unwrap_or_else(Self::empty))
|
||||
}
|
||||
|
||||
fn perform<P>(
|
||||
self,
|
||||
image: image_rs::ImageBuffer<P, Vec<P::Subpixel>>,
|
||||
) -> image_rs::ImageBuffer<P, Vec<P::Subpixel>>
|
||||
where
|
||||
P: image_rs::Pixel + 'static,
|
||||
{
|
||||
use image_rs::imageops;
|
||||
|
||||
let mut image = if self.contains(Self::FLIP_DIAGONALLY) {
|
||||
flip_diagonally(image)
|
||||
} else {
|
||||
image
|
||||
};
|
||||
|
||||
if self.contains(Self::ROTATE_180) {
|
||||
imageops::rotate180_in_place(&mut image);
|
||||
}
|
||||
|
||||
if self.contains(Self::FLIP_HORIZONTALLY) {
|
||||
imageops::flip_horizontal_in_place(&mut image);
|
||||
}
|
||||
|
||||
image
|
||||
}
|
||||
}
|
||||
|
||||
fn flip_diagonally<I>(
|
||||
image: I,
|
||||
) -> image_rs::ImageBuffer<I::Pixel, Vec<<I::Pixel as image_rs::Pixel>::Subpixel>>
|
||||
where
|
||||
I: image_rs::GenericImage,
|
||||
I::Pixel: 'static,
|
||||
{
|
||||
let (width, height) = image.dimensions();
|
||||
let mut out = image_rs::ImageBuffer::new(height, width);
|
||||
|
||||
for x in 0..width {
|
||||
for y in 0..height {
|
||||
let p = image.get_pixel(x, y);
|
||||
|
||||
out.put_pixel(y, x, p);
|
||||
}
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
31
graphics/src/image/storage.rs
Normal file
31
graphics/src/image/storage.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
//! Store images.
|
||||
use crate::Size;
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Stores cached image data for use in rendering
|
||||
pub trait Storage {
|
||||
/// The type of an [`Entry`] in the [`Storage`].
|
||||
type Entry: Entry;
|
||||
|
||||
/// State provided to upload or remove a [`Self::Entry`].
|
||||
type State<'a>;
|
||||
|
||||
/// Upload the image data of a [`Self::Entry`].
|
||||
fn upload(
|
||||
&mut self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
data: &[u8],
|
||||
state: &mut Self::State<'_>,
|
||||
) -> Option<Self::Entry>;
|
||||
|
||||
/// Romve a [`Self::Entry`] from the [`Storage`].
|
||||
fn remove(&mut self, entry: &Self::Entry, state: &mut Self::State<'_>);
|
||||
}
|
||||
|
||||
/// An entry in some [`Storage`],
|
||||
pub trait Entry: Debug {
|
||||
/// The [`Size`] of the [`Entry`].
|
||||
fn size(&self) -> Size<u32>;
|
||||
}
|
||||
176
graphics/src/image/vector.rs
Normal file
176
graphics/src/image/vector.rs
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
//! Vector image loading and caching
|
||||
use crate::image::Storage;
|
||||
|
||||
use iced_native::svg;
|
||||
use iced_native::Size;
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fs;
|
||||
|
||||
/// Entry in cache corresponding to an svg handle
|
||||
pub enum Svg {
|
||||
/// Parsed svg
|
||||
Loaded(usvg::Tree),
|
||||
/// Svg not found or failed to parse
|
||||
NotFound,
|
||||
}
|
||||
|
||||
impl Svg {
|
||||
/// Viewport width and height
|
||||
pub fn viewport_dimensions(&self) -> Size<u32> {
|
||||
match self {
|
||||
Svg::Loaded(tree) => {
|
||||
let size = tree.svg_node().size;
|
||||
|
||||
Size::new(size.width() as u32, size.height() as u32)
|
||||
}
|
||||
Svg::NotFound => Size::new(1, 1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Caches svg vector and raster data
|
||||
#[derive(Debug)]
|
||||
pub struct Cache<T: Storage> {
|
||||
svgs: HashMap<u64, Svg>,
|
||||
rasterized: HashMap<(u64, u32, u32), T::Entry>,
|
||||
svg_hits: HashSet<u64>,
|
||||
rasterized_hits: HashSet<(u64, u32, u32)>,
|
||||
}
|
||||
|
||||
impl<T: Storage> Cache<T> {
|
||||
/// Load svg
|
||||
pub fn load(&mut self, handle: &svg::Handle) -> &Svg {
|
||||
if self.svgs.contains_key(&handle.id()) {
|
||||
return self.svgs.get(&handle.id()).unwrap();
|
||||
}
|
||||
|
||||
let svg = match handle.data() {
|
||||
svg::Data::Path(path) => {
|
||||
let tree = fs::read_to_string(path).ok().and_then(|contents| {
|
||||
usvg::Tree::from_str(
|
||||
&contents,
|
||||
&usvg::Options::default().to_ref(),
|
||||
)
|
||||
.ok()
|
||||
});
|
||||
|
||||
tree.map(Svg::Loaded).unwrap_or(Svg::NotFound)
|
||||
}
|
||||
svg::Data::Bytes(bytes) => {
|
||||
match usvg::Tree::from_data(
|
||||
bytes,
|
||||
&usvg::Options::default().to_ref(),
|
||||
) {
|
||||
Ok(tree) => Svg::Loaded(tree),
|
||||
Err(_) => Svg::NotFound,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let _ = self.svgs.insert(handle.id(), svg);
|
||||
self.svgs.get(&handle.id()).unwrap()
|
||||
}
|
||||
|
||||
/// Load svg and upload raster data
|
||||
pub fn upload(
|
||||
&mut self,
|
||||
handle: &svg::Handle,
|
||||
[width, height]: [f32; 2],
|
||||
scale: f32,
|
||||
state: &mut T::State<'_>,
|
||||
storage: &mut T,
|
||||
) -> Option<&T::Entry> {
|
||||
let id = handle.id();
|
||||
|
||||
let (width, height) = (
|
||||
(scale * width).ceil() as u32,
|
||||
(scale * height).ceil() as u32,
|
||||
);
|
||||
|
||||
// TODO: Optimize!
|
||||
// We currently rerasterize the SVG when its size changes. This is slow
|
||||
// as heck. A GPU rasterizer like `pathfinder` may perform better.
|
||||
// It would be cool to be able to smooth resize the `svg` example.
|
||||
if self.rasterized.contains_key(&(id, width, height)) {
|
||||
let _ = self.svg_hits.insert(id);
|
||||
let _ = self.rasterized_hits.insert((id, width, height));
|
||||
|
||||
return self.rasterized.get(&(id, width, height));
|
||||
}
|
||||
|
||||
match self.load(handle) {
|
||||
Svg::Loaded(tree) => {
|
||||
if width == 0 || height == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// TODO: Optimize!
|
||||
// We currently rerasterize the SVG when its size changes. This is slow
|
||||
// as heck. A GPU rasterizer like `pathfinder` may perform better.
|
||||
// It would be cool to be able to smooth resize the `svg` example.
|
||||
let mut img = tiny_skia::Pixmap::new(width, height)?;
|
||||
|
||||
resvg::render(
|
||||
tree,
|
||||
if width > height {
|
||||
usvg::FitTo::Width(width)
|
||||
} else {
|
||||
usvg::FitTo::Height(height)
|
||||
},
|
||||
img.as_mut(),
|
||||
)?;
|
||||
|
||||
let allocation =
|
||||
storage.upload(width, height, img.data(), state)?;
|
||||
log::debug!("allocating {} {}x{}", id, width, height);
|
||||
|
||||
let _ = self.svg_hits.insert(id);
|
||||
let _ = self.rasterized_hits.insert((id, width, height));
|
||||
let _ = self.rasterized.insert((id, width, height), allocation);
|
||||
|
||||
self.rasterized.get(&(id, width, height))
|
||||
}
|
||||
Svg::NotFound => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Load svg and upload raster data
|
||||
pub fn trim(&mut self, storage: &mut T, state: &mut T::State<'_>) {
|
||||
let svg_hits = &self.svg_hits;
|
||||
let rasterized_hits = &self.rasterized_hits;
|
||||
|
||||
self.svgs.retain(|k, _| svg_hits.contains(k));
|
||||
self.rasterized.retain(|k, entry| {
|
||||
let retain = rasterized_hits.contains(k);
|
||||
|
||||
if !retain {
|
||||
storage.remove(entry, state);
|
||||
}
|
||||
|
||||
retain
|
||||
});
|
||||
self.svg_hits.clear();
|
||||
self.rasterized_hits.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Storage> Default for Cache<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
svgs: HashMap::new(),
|
||||
rasterized: HashMap::new(),
|
||||
svg_hits: HashSet::new(),
|
||||
rasterized_hits: HashSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Svg {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Svg::Loaded(_) => write!(f, "Svg::Loaded"),
|
||||
Svg::NotFound => write!(f, "Svg::NotFound"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -166,10 +166,27 @@ impl<'a> Layer<'a> {
|
|||
border_color: border_color.into_linear(),
|
||||
});
|
||||
}
|
||||
Primitive::Mesh2D {
|
||||
Primitive::SolidMesh { buffers, size } => {
|
||||
let layer = &mut layers[current_layer];
|
||||
|
||||
let bounds = Rectangle::new(
|
||||
Point::new(translation.x, translation.y),
|
||||
*size,
|
||||
);
|
||||
|
||||
// Only draw visible content
|
||||
if let Some(clip_bounds) = layer.bounds.intersection(&bounds) {
|
||||
layer.meshes.push(Mesh::Solid {
|
||||
origin: Point::new(translation.x, translation.y),
|
||||
buffers,
|
||||
clip_bounds,
|
||||
});
|
||||
}
|
||||
}
|
||||
Primitive::GradientMesh {
|
||||
buffers,
|
||||
size,
|
||||
style,
|
||||
gradient,
|
||||
} => {
|
||||
let layer = &mut layers[current_layer];
|
||||
|
||||
|
|
@ -180,11 +197,11 @@ impl<'a> Layer<'a> {
|
|||
|
||||
// Only draw visible content
|
||||
if let Some(clip_bounds) = layer.bounds.intersection(&bounds) {
|
||||
layer.meshes.push(Mesh {
|
||||
layer.meshes.push(Mesh::Gradient {
|
||||
origin: Point::new(translation.x, translation.y),
|
||||
buffers,
|
||||
clip_bounds,
|
||||
style,
|
||||
gradient,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,93 @@
|
|||
//! A collection of triangle primitives.
|
||||
use crate::triangle;
|
||||
use crate::{Point, Rectangle};
|
||||
use crate::{Gradient, Point, Rectangle};
|
||||
|
||||
/// A mesh of triangles.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Mesh<'a> {
|
||||
/// The origin of the vertices of the [`Mesh`].
|
||||
pub origin: Point,
|
||||
pub enum Mesh<'a> {
|
||||
/// A mesh of triangles with a solid color.
|
||||
Solid {
|
||||
/// The origin of the vertices of the [`Mesh`].
|
||||
origin: Point,
|
||||
|
||||
/// The vertex and index buffers of the [`Mesh`].
|
||||
pub buffers: &'a triangle::Mesh2D,
|
||||
/// The vertex and index buffers of the [`Mesh`].
|
||||
buffers: &'a triangle::Mesh2D<triangle::ColoredVertex2D>,
|
||||
|
||||
/// The clipping bounds of the [`Mesh`].
|
||||
pub clip_bounds: Rectangle<f32>,
|
||||
/// The clipping bounds of the [`Mesh`].
|
||||
clip_bounds: Rectangle<f32>,
|
||||
},
|
||||
/// A mesh of triangles with a gradient color.
|
||||
Gradient {
|
||||
/// The origin of the vertices of the [`Mesh`].
|
||||
origin: Point,
|
||||
|
||||
/// The shader of the [`Mesh`].
|
||||
pub style: &'a triangle::Style,
|
||||
/// The vertex and index buffers of the [`Mesh`].
|
||||
buffers: &'a triangle::Mesh2D<triangle::Vertex2D>,
|
||||
|
||||
/// The clipping bounds of the [`Mesh`].
|
||||
clip_bounds: Rectangle<f32>,
|
||||
|
||||
/// The gradient to apply to the [`Mesh`].
|
||||
gradient: &'a Gradient,
|
||||
},
|
||||
}
|
||||
|
||||
impl Mesh<'_> {
|
||||
/// Returns the origin of the [`Mesh`].
|
||||
pub fn origin(&self) -> Point {
|
||||
match self {
|
||||
Self::Solid { origin, .. } | Self::Gradient { origin, .. } => {
|
||||
*origin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the indices of the [`Mesh`].
|
||||
pub fn indices(&self) -> &[u32] {
|
||||
match self {
|
||||
Self::Solid { buffers, .. } => &buffers.indices,
|
||||
Self::Gradient { buffers, .. } => &buffers.indices,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the clip bounds of the [`Mesh`].
|
||||
pub fn clip_bounds(&self) -> Rectangle<f32> {
|
||||
match self {
|
||||
Self::Solid { clip_bounds, .. }
|
||||
| Self::Gradient { clip_bounds, .. } => *clip_bounds,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of counting the attributes of a set of meshes.
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct AttributeCount {
|
||||
/// The total amount of solid vertices.
|
||||
pub solid_vertices: usize,
|
||||
|
||||
/// The total amount of gradient vertices.
|
||||
pub gradient_vertices: usize,
|
||||
|
||||
/// The total amount of indices.
|
||||
pub indices: usize,
|
||||
}
|
||||
|
||||
/// Returns the number of total vertices & total indices of all [`Mesh`]es.
|
||||
pub fn attribute_count_of<'a>(meshes: &'a [Mesh<'a>]) -> (usize, usize) {
|
||||
pub fn attribute_count_of<'a>(meshes: &'a [Mesh<'a>]) -> AttributeCount {
|
||||
meshes
|
||||
.iter()
|
||||
.map(|Mesh { buffers, .. }| {
|
||||
(buffers.vertices.len(), buffers.indices.len())
|
||||
})
|
||||
.fold((0, 0), |(total_v, total_i), (v, i)| {
|
||||
(total_v + v, total_i + i)
|
||||
.fold(AttributeCount::default(), |mut count, mesh| {
|
||||
match mesh {
|
||||
Mesh::Solid { buffers, .. } => {
|
||||
count.solid_vertices += buffers.vertices.len();
|
||||
count.indices += buffers.indices.len();
|
||||
}
|
||||
Mesh::Gradient { buffers, .. } => {
|
||||
count.gradient_vertices += buffers.vertices.len();
|
||||
count.indices += buffers.indices.len();
|
||||
}
|
||||
}
|
||||
|
||||
count
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ mod viewport;
|
|||
pub mod backend;
|
||||
pub mod font;
|
||||
pub mod gradient;
|
||||
pub mod image;
|
||||
pub mod layer;
|
||||
pub mod overlay;
|
||||
pub mod renderer;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use iced_native::svg;
|
|||
use iced_native::{Background, Color, Font, Rectangle, Size, Vector};
|
||||
|
||||
use crate::alignment;
|
||||
use crate::gradient::Gradient;
|
||||
use crate::triangle;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
|
@ -77,20 +78,32 @@ pub enum Primitive {
|
|||
/// The primitive to translate
|
||||
content: Box<Primitive>,
|
||||
},
|
||||
/// A low-level primitive to render a mesh of triangles.
|
||||
/// A low-level primitive to render a mesh of triangles with a solid color.
|
||||
///
|
||||
/// It can be used to render many kinds of geometry freely.
|
||||
Mesh2D {
|
||||
/// The vertex and index buffers of the mesh
|
||||
buffers: triangle::Mesh2D,
|
||||
SolidMesh {
|
||||
/// The vertices and indices of the mesh.
|
||||
buffers: triangle::Mesh2D<triangle::ColoredVertex2D>,
|
||||
|
||||
/// The size of the drawable region of the mesh.
|
||||
///
|
||||
/// Any geometry that falls out of this region will be clipped.
|
||||
size: Size,
|
||||
},
|
||||
/// A low-level primitive to render a mesh of triangles with a gradient.
|
||||
///
|
||||
/// It can be used to render many kinds of geometry freely.
|
||||
GradientMesh {
|
||||
/// The vertices and indices of the mesh.
|
||||
buffers: triangle::Mesh2D<triangle::Vertex2D>,
|
||||
|
||||
/// The size of the drawable region of the mesh.
|
||||
///
|
||||
/// Any geometry that falls out of this region will be clipped.
|
||||
size: Size,
|
||||
|
||||
/// The shader of the mesh
|
||||
style: triangle::Style,
|
||||
/// The [`Gradient`] to apply to the mesh.
|
||||
gradient: Gradient,
|
||||
},
|
||||
/// A cached primitive.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ where
|
|||
{
|
||||
type Handle = image::Handle;
|
||||
|
||||
fn dimensions(&self, handle: &image::Handle) -> (u32, u32) {
|
||||
fn dimensions(&self, handle: &image::Handle) -> Size<u32> {
|
||||
self.backend().dimensions(handle)
|
||||
}
|
||||
|
||||
|
|
@ -196,7 +196,7 @@ impl<B, T> svg::Renderer for Renderer<B, T>
|
|||
where
|
||||
B: Backend + backend::Svg,
|
||||
{
|
||||
fn dimensions(&self, handle: &svg::Handle) -> (u32, u32) {
|
||||
fn dimensions(&self, handle: &svg::Handle) -> Size<u32> {
|
||||
self.backend().viewport_dimensions(handle)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
//! Draw geometry using meshes of triangles.
|
||||
use crate::{Color, Gradient};
|
||||
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
|
||||
/// A set of [`Vertex2D`] and indices representing a list of triangles.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Mesh2D {
|
||||
pub struct Mesh2D<T> {
|
||||
/// The vertices of the mesh
|
||||
pub vertices: Vec<Vertex2D>,
|
||||
pub vertices: Vec<T>,
|
||||
|
||||
/// The list of vertex indices that defines the triangles of the mesh.
|
||||
///
|
||||
/// Therefore, this list should always have a length that is a multiple of 3.
|
||||
|
|
@ -22,23 +21,13 @@ pub struct Vertex2D {
|
|||
pub position: [f32; 2],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
/// Supported shaders for triangle primitives.
|
||||
pub enum Style {
|
||||
/// Fill a primitive with a solid color.
|
||||
Solid(Color),
|
||||
/// Fill a primitive with an interpolated color.
|
||||
Gradient(Gradient),
|
||||
}
|
||||
/// A two-dimensional vertex with a color.
|
||||
#[derive(Copy, Clone, Debug, Zeroable, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct ColoredVertex2D {
|
||||
/// The vertex position in 2D space.
|
||||
pub position: [f32; 2],
|
||||
|
||||
impl From<Color> for Style {
|
||||
fn from(color: Color) -> Self {
|
||||
Self::Solid(color)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Gradient> for Style {
|
||||
fn from(gradient: Gradient) -> Self {
|
||||
Self::Gradient(gradient)
|
||||
}
|
||||
/// The color of the vertex in __linear__ RGBA.
|
||||
pub color: [f32; 4],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ mod cursor;
|
|||
mod frame;
|
||||
mod geometry;
|
||||
mod program;
|
||||
mod style;
|
||||
mod text;
|
||||
|
||||
pub use crate::gradient::{self, Gradient};
|
||||
|
|
@ -25,6 +26,7 @@ pub use geometry::Geometry;
|
|||
pub use path::Path;
|
||||
pub use program::Program;
|
||||
pub use stroke::{LineCap, LineDash, LineJoin, Stroke};
|
||||
pub use style::Style;
|
||||
pub use text::Text;
|
||||
|
||||
use crate::{Backend, Primitive, Renderer};
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
//! Fill [crate::widget::canvas::Geometry] with a certain style.
|
||||
use crate::{Color, Gradient};
|
||||
|
||||
pub use crate::triangle::Style;
|
||||
pub use crate::widget::canvas::Style;
|
||||
|
||||
/// The style used to fill geometry.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Fill {
|
||||
/// The color or gradient of the fill.
|
||||
///
|
||||
/// By default, it is set to [`FillStyle::Solid`] `BLACK`.
|
||||
/// By default, it is set to [`Style::Solid`] with [`Color::BLACK`].
|
||||
pub style: Style,
|
||||
|
||||
/// The fill rule defines how to determine what is inside and what is
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
use crate::gradient::Gradient;
|
||||
use crate::triangle;
|
||||
use crate::triangle::Vertex2D;
|
||||
use crate::widget::canvas::{path, Fill, Geometry, Path, Stroke, Text};
|
||||
use crate::widget::canvas::{path, Fill, Geometry, Path, Stroke, Style, Text};
|
||||
use crate::Primitive;
|
||||
|
||||
use iced_native::{Point, Rectangle, Size, Vector};
|
||||
|
|
@ -23,8 +22,16 @@ pub struct Frame {
|
|||
stroke_tessellator: tessellation::StrokeTessellator,
|
||||
}
|
||||
|
||||
enum Buffer {
|
||||
Solid(tessellation::VertexBuffers<triangle::ColoredVertex2D, u32>),
|
||||
Gradient(
|
||||
tessellation::VertexBuffers<triangle::Vertex2D, u32>,
|
||||
Gradient,
|
||||
),
|
||||
}
|
||||
|
||||
struct BufferStack {
|
||||
stack: Vec<(tessellation::VertexBuffers<Vertex2D, u32>, triangle::Style)>,
|
||||
stack: Vec<Buffer>,
|
||||
}
|
||||
|
||||
impl BufferStack {
|
||||
|
|
@ -32,22 +39,64 @@ impl BufferStack {
|
|||
Self { stack: Vec::new() }
|
||||
}
|
||||
|
||||
fn get(
|
||||
&mut self,
|
||||
mesh_style: triangle::Style,
|
||||
) -> tessellation::BuffersBuilder<'_, Vertex2D, u32, Vertex2DBuilder> {
|
||||
match self.stack.last_mut() {
|
||||
Some((_, current_style)) if current_style == &mesh_style => {}
|
||||
_ => {
|
||||
self.stack
|
||||
.push((tessellation::VertexBuffers::new(), mesh_style));
|
||||
}
|
||||
};
|
||||
fn get_mut(&mut self, style: &Style) -> &mut Buffer {
|
||||
match style {
|
||||
Style::Solid(_) => match self.stack.last() {
|
||||
Some(Buffer::Solid(_)) => {}
|
||||
_ => {
|
||||
self.stack.push(Buffer::Solid(
|
||||
tessellation::VertexBuffers::new(),
|
||||
));
|
||||
}
|
||||
},
|
||||
Style::Gradient(gradient) => match self.stack.last() {
|
||||
Some(Buffer::Gradient(_, last)) if gradient == last => {}
|
||||
_ => {
|
||||
self.stack.push(Buffer::Gradient(
|
||||
tessellation::VertexBuffers::new(),
|
||||
gradient.clone(),
|
||||
));
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
tessellation::BuffersBuilder::new(
|
||||
&mut self.stack.last_mut().unwrap().0,
|
||||
Vertex2DBuilder,
|
||||
)
|
||||
self.stack.last_mut().unwrap()
|
||||
}
|
||||
|
||||
fn get_fill<'a>(
|
||||
&'a mut self,
|
||||
style: &Style,
|
||||
) -> Box<dyn tessellation::FillGeometryBuilder + 'a> {
|
||||
match (style, self.get_mut(style)) {
|
||||
(Style::Solid(color), Buffer::Solid(buffer)) => {
|
||||
Box::new(tessellation::BuffersBuilder::new(
|
||||
buffer,
|
||||
TriangleVertex2DBuilder(color.into_linear()),
|
||||
))
|
||||
}
|
||||
(Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new(
|
||||
tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder),
|
||||
),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_stroke<'a>(
|
||||
&'a mut self,
|
||||
style: &Style,
|
||||
) -> Box<dyn tessellation::StrokeGeometryBuilder + 'a> {
|
||||
match (style, self.get_mut(style)) {
|
||||
(Style::Solid(color), Buffer::Solid(buffer)) => {
|
||||
Box::new(tessellation::BuffersBuilder::new(
|
||||
buffer,
|
||||
TriangleVertex2DBuilder(color.into_linear()),
|
||||
))
|
||||
}
|
||||
(Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new(
|
||||
tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder),
|
||||
),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -73,11 +122,11 @@ impl Transform {
|
|||
point.y = transformed.y;
|
||||
}
|
||||
|
||||
fn transform_style(&self, style: triangle::Style) -> triangle::Style {
|
||||
fn transform_style(&self, style: Style) -> Style {
|
||||
match style {
|
||||
triangle::Style::Solid(color) => triangle::Style::Solid(color),
|
||||
triangle::Style::Gradient(gradient) => {
|
||||
triangle::Style::Gradient(self.transform_gradient(gradient))
|
||||
Style::Solid(color) => Style::Solid(color),
|
||||
Style::Gradient(gradient) => {
|
||||
Style::Gradient(self.transform_gradient(gradient))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -145,7 +194,7 @@ impl Frame {
|
|||
|
||||
let mut buffer = self
|
||||
.buffers
|
||||
.get(self.transforms.current.transform_style(style));
|
||||
.get_fill(&self.transforms.current.transform_style(style));
|
||||
|
||||
let options =
|
||||
tessellation::FillOptions::default().with_fill_rule(rule.into());
|
||||
|
|
@ -154,7 +203,7 @@ impl Frame {
|
|||
self.fill_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
&mut buffer,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
} else {
|
||||
let path = path.transformed(&self.transforms.current.raw);
|
||||
|
|
@ -162,7 +211,7 @@ impl Frame {
|
|||
self.fill_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
&mut buffer,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
}
|
||||
.expect("Tessellate path.");
|
||||
|
|
@ -180,7 +229,7 @@ impl Frame {
|
|||
|
||||
let mut buffer = self
|
||||
.buffers
|
||||
.get(self.transforms.current.transform_style(style));
|
||||
.get_fill(&self.transforms.current.transform_style(style));
|
||||
|
||||
let top_left =
|
||||
self.transforms.current.raw.transform_point(
|
||||
|
|
@ -199,7 +248,7 @@ impl Frame {
|
|||
.tessellate_rectangle(
|
||||
&lyon::math::Box2D::new(top_left, top_left + size),
|
||||
&options,
|
||||
&mut buffer,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
.expect("Fill rectangle");
|
||||
}
|
||||
|
|
@ -211,7 +260,7 @@ impl Frame {
|
|||
|
||||
let mut buffer = self
|
||||
.buffers
|
||||
.get(self.transforms.current.transform_style(stroke.style));
|
||||
.get_stroke(&self.transforms.current.transform_style(stroke.style));
|
||||
|
||||
let mut options = tessellation::StrokeOptions::default();
|
||||
options.line_width = stroke.width;
|
||||
|
|
@ -229,7 +278,7 @@ impl Frame {
|
|||
self.stroke_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
&mut buffer,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
} else {
|
||||
let path = path.transformed(&self.transforms.current.raw);
|
||||
|
|
@ -237,7 +286,7 @@ impl Frame {
|
|||
self.stroke_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
&mut buffer,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
}
|
||||
.expect("Stroke path");
|
||||
|
|
@ -382,16 +431,31 @@ impl Frame {
|
|||
}
|
||||
|
||||
fn into_primitives(mut self) -> Vec<Primitive> {
|
||||
for (buffer, style) in self.buffers.stack {
|
||||
if !buffer.indices.is_empty() {
|
||||
self.primitives.push(Primitive::Mesh2D {
|
||||
buffers: triangle::Mesh2D {
|
||||
vertices: buffer.vertices,
|
||||
indices: buffer.indices,
|
||||
},
|
||||
size: self.size,
|
||||
style,
|
||||
})
|
||||
for buffer in self.buffers.stack {
|
||||
match buffer {
|
||||
Buffer::Solid(buffer) => {
|
||||
if !buffer.indices.is_empty() {
|
||||
self.primitives.push(Primitive::SolidMesh {
|
||||
buffers: triangle::Mesh2D {
|
||||
vertices: buffer.vertices,
|
||||
indices: buffer.indices,
|
||||
},
|
||||
size: self.size,
|
||||
})
|
||||
}
|
||||
}
|
||||
Buffer::Gradient(buffer, gradient) => {
|
||||
if !buffer.indices.is_empty() {
|
||||
self.primitives.push(Primitive::GradientMesh {
|
||||
buffers: triangle::Mesh2D {
|
||||
vertices: buffer.vertices,
|
||||
indices: buffer.indices,
|
||||
},
|
||||
size: self.size,
|
||||
gradient,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -401,25 +465,66 @@ impl Frame {
|
|||
|
||||
struct Vertex2DBuilder;
|
||||
|
||||
impl tessellation::FillVertexConstructor<Vertex2D> for Vertex2DBuilder {
|
||||
fn new_vertex(&mut self, vertex: tessellation::FillVertex<'_>) -> Vertex2D {
|
||||
impl tessellation::FillVertexConstructor<triangle::Vertex2D>
|
||||
for Vertex2DBuilder
|
||||
{
|
||||
fn new_vertex(
|
||||
&mut self,
|
||||
vertex: tessellation::FillVertex<'_>,
|
||||
) -> triangle::Vertex2D {
|
||||
let position = vertex.position();
|
||||
|
||||
Vertex2D {
|
||||
triangle::Vertex2D {
|
||||
position: [position.x, position.y],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl tessellation::StrokeVertexConstructor<Vertex2D> for Vertex2DBuilder {
|
||||
impl tessellation::StrokeVertexConstructor<triangle::Vertex2D>
|
||||
for Vertex2DBuilder
|
||||
{
|
||||
fn new_vertex(
|
||||
&mut self,
|
||||
vertex: tessellation::StrokeVertex<'_, '_>,
|
||||
) -> Vertex2D {
|
||||
) -> triangle::Vertex2D {
|
||||
let position = vertex.position();
|
||||
|
||||
Vertex2D {
|
||||
triangle::Vertex2D {
|
||||
position: [position.x, position.y],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TriangleVertex2DBuilder([f32; 4]);
|
||||
|
||||
impl tessellation::FillVertexConstructor<triangle::ColoredVertex2D>
|
||||
for TriangleVertex2DBuilder
|
||||
{
|
||||
fn new_vertex(
|
||||
&mut self,
|
||||
vertex: tessellation::FillVertex<'_>,
|
||||
) -> triangle::ColoredVertex2D {
|
||||
let position = vertex.position();
|
||||
|
||||
triangle::ColoredVertex2D {
|
||||
position: [position.x, position.y],
|
||||
color: self.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl tessellation::StrokeVertexConstructor<triangle::ColoredVertex2D>
|
||||
for TriangleVertex2DBuilder
|
||||
{
|
||||
fn new_vertex(
|
||||
&mut self,
|
||||
vertex: tessellation::StrokeVertex<'_, '_>,
|
||||
) -> triangle::ColoredVertex2D {
|
||||
let position = vertex.position();
|
||||
|
||||
triangle::ColoredVertex2D {
|
||||
position: [position.x, position.y],
|
||||
color: self.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
//! Create lines from a [crate::widget::canvas::Path] and assigns them various attributes/styles.
|
||||
pub use crate::triangle::Style;
|
||||
pub use crate::widget::canvas::Style;
|
||||
|
||||
use iced_native::Color;
|
||||
|
||||
|
|
@ -8,7 +8,7 @@ use iced_native::Color;
|
|||
pub struct Stroke<'a> {
|
||||
/// The color or gradient of the stroke.
|
||||
///
|
||||
/// By default, it is set to [`StrokeStyle::Solid`] `BLACK`.
|
||||
/// By default, it is set to a [`Style::Solid`] with [`Color::BLACK`].
|
||||
pub style: Style,
|
||||
/// The distance between the two edges of the stroke.
|
||||
pub width: f32,
|
||||
|
|
|
|||
23
graphics/src/widget/canvas/style.rs
Normal file
23
graphics/src/widget/canvas/style.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
use crate::{Color, Gradient};
|
||||
|
||||
/// The coloring style of some drawing.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Style {
|
||||
/// A solid [`Color`].
|
||||
Solid(Color),
|
||||
|
||||
/// A [`Gradient`] color.
|
||||
Gradient(Gradient),
|
||||
}
|
||||
|
||||
impl From<Color> for Style {
|
||||
fn from(color: Color) -> Self {
|
||||
Self::Solid(color)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Gradient> for Style {
|
||||
fn from(gradient: Gradient) -> Self {
|
||||
Self::Gradient(gradient)
|
||||
}
|
||||
}
|
||||
|
|
@ -40,7 +40,7 @@ pub trait Compositor: Sized {
|
|||
height: u32,
|
||||
);
|
||||
|
||||
/// Returns [`GraphicsInformation`] used by this [`Compositor`].
|
||||
/// Returns [`Information`] used by this [`Compositor`].
|
||||
fn fetch_information(&self) -> Information;
|
||||
|
||||
/// Presents the [`Renderer`] primitives to the next frame of the given [`Surface`].
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ pub trait GLCompositor: Sized {
|
|||
/// Resizes the viewport of the [`GLCompositor`].
|
||||
fn resize_viewport(&mut self, physical_size: Size<u32>);
|
||||
|
||||
/// Returns [`GraphicsInformation`] used by this [`Compositor`].
|
||||
/// Returns [`Information`] used by this [`GLCompositor`].
|
||||
fn fetch_information(&self) -> Information;
|
||||
|
||||
/// Presents the primitives of the [`Renderer`] to the next frame of the
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue