Merge pull request #2537 from iced-rs/feature/canvas-image-support
`image` and `svg` support for `canvas`
This commit is contained in:
commit
145c3dc8fc
31 changed files with 627 additions and 400 deletions
|
|
@ -7,6 +7,73 @@ use rustc_hash::FxHasher;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
/// A raster image that can be drawn.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct Image<H = Handle> {
|
||||||
|
/// The handle of the image.
|
||||||
|
pub handle: H,
|
||||||
|
|
||||||
|
/// The filter method of the image.
|
||||||
|
pub filter_method: FilterMethod,
|
||||||
|
|
||||||
|
/// The rotation to be applied to the image; on its center.
|
||||||
|
pub rotation: Radians,
|
||||||
|
|
||||||
|
/// The opacity of the image.
|
||||||
|
///
|
||||||
|
/// 0 means transparent. 1 means opaque.
|
||||||
|
pub opacity: f32,
|
||||||
|
|
||||||
|
/// If set to `true`, the image will be snapped to the pixel grid.
|
||||||
|
///
|
||||||
|
/// This can avoid graphical glitches, specially when using
|
||||||
|
/// [`FilterMethod::Nearest`].
|
||||||
|
pub snap: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Image<Handle> {
|
||||||
|
/// Creates a new [`Image`] with the given handle.
|
||||||
|
pub fn new(handle: impl Into<Handle>) -> Self {
|
||||||
|
Self {
|
||||||
|
handle: handle.into(),
|
||||||
|
filter_method: FilterMethod::default(),
|
||||||
|
rotation: Radians(0.0),
|
||||||
|
opacity: 1.0,
|
||||||
|
snap: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the filter method of the [`Image`].
|
||||||
|
pub fn filter_method(mut self, filter_method: FilterMethod) -> Self {
|
||||||
|
self.filter_method = filter_method;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the rotation of the [`Image`].
|
||||||
|
pub fn rotation(mut self, rotation: impl Into<Radians>) -> Self {
|
||||||
|
self.rotation = rotation.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the opacity of the [`Image`].
|
||||||
|
pub fn opacity(mut self, opacity: impl Into<f32>) -> Self {
|
||||||
|
self.opacity = opacity.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets whether the [`Image`] should be snapped to the pixel grid.
|
||||||
|
pub fn snap(mut self, snap: bool) -> Self {
|
||||||
|
self.snap = snap;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Handle> for Image {
|
||||||
|
fn from(handle: &Handle) -> Self {
|
||||||
|
Image::new(handle.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A handle of some image data.
|
/// A handle of some image data.
|
||||||
#[derive(Clone, PartialEq, Eq)]
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
pub enum Handle {
|
pub enum Handle {
|
||||||
|
|
@ -101,6 +168,12 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<&Handle> for Handle {
|
||||||
|
fn from(value: &Handle) -> Self {
|
||||||
|
value.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for Handle {
|
impl std::fmt::Debug for Handle {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -166,14 +239,6 @@ pub trait Renderer: crate::Renderer {
|
||||||
/// Returns the dimensions of an image for the given [`Handle`].
|
/// Returns the dimensions of an image for the given [`Handle`].
|
||||||
fn measure_image(&self, handle: &Self::Handle) -> Size<u32>;
|
fn measure_image(&self, handle: &Self::Handle) -> Size<u32>;
|
||||||
|
|
||||||
/// Draws an image with the given [`Handle`] and inside the provided
|
/// Draws an [`Image`] inside the provided `bounds`.
|
||||||
/// `bounds`.
|
fn draw_image(&mut self, image: Image<Self::Handle>, bounds: Rectangle);
|
||||||
fn draw_image(
|
|
||||||
&mut self,
|
|
||||||
handle: Self::Handle,
|
|
||||||
filter_method: FilterMethod,
|
|
||||||
bounds: Rectangle,
|
|
||||||
rotation: Radians,
|
|
||||||
opacity: f32,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ pub use element::Element;
|
||||||
pub use event::Event;
|
pub use event::Event;
|
||||||
pub use font::Font;
|
pub use font::Font;
|
||||||
pub use gradient::Gradient;
|
pub use gradient::Gradient;
|
||||||
|
pub use image::Image;
|
||||||
pub use layout::Layout;
|
pub use layout::Layout;
|
||||||
pub use length::Length;
|
pub use length::Length;
|
||||||
pub use overlay::Overlay;
|
pub use overlay::Overlay;
|
||||||
|
|
@ -69,6 +70,7 @@ pub use rotation::Rotation;
|
||||||
pub use shadow::Shadow;
|
pub use shadow::Shadow;
|
||||||
pub use shell::Shell;
|
pub use shell::Shell;
|
||||||
pub use size::Size;
|
pub use size::Size;
|
||||||
|
pub use svg::Svg;
|
||||||
pub use text::Text;
|
pub use text::Text;
|
||||||
pub use theme::Theme;
|
pub use theme::Theme;
|
||||||
pub use transformation::Transformation;
|
pub use transformation::Transformation;
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,62 @@ impl Rectangle<f32> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new square [`Rectangle`] with the center at the origin and
|
||||||
|
/// with the given radius.
|
||||||
|
pub fn with_radius(radius: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
x: -radius,
|
||||||
|
y: -radius,
|
||||||
|
width: radius * 2.0,
|
||||||
|
height: radius * 2.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new axis-aligned [`Rectangle`] from the given vertices; returning the
|
||||||
|
/// rotation in [`Radians`] that must be applied to the axis-aligned [`Rectangle`]
|
||||||
|
/// to obtain the desired result.
|
||||||
|
pub fn with_vertices(
|
||||||
|
top_left: Point,
|
||||||
|
top_right: Point,
|
||||||
|
bottom_left: Point,
|
||||||
|
) -> (Rectangle, Radians) {
|
||||||
|
let width = (top_right.x - top_left.x).hypot(top_right.y - top_left.y);
|
||||||
|
|
||||||
|
let height =
|
||||||
|
(bottom_left.x - top_left.x).hypot(bottom_left.y - top_left.y);
|
||||||
|
|
||||||
|
let rotation =
|
||||||
|
(top_right.y - top_left.y).atan2(top_right.x - top_left.x);
|
||||||
|
|
||||||
|
let rotation = if rotation < 0.0 {
|
||||||
|
2.0 * std::f32::consts::PI + rotation
|
||||||
|
} else {
|
||||||
|
rotation
|
||||||
|
};
|
||||||
|
|
||||||
|
let position = {
|
||||||
|
let center = Point::new(
|
||||||
|
(top_right.x + bottom_left.x) / 2.0,
|
||||||
|
(top_right.y + bottom_left.y) / 2.0,
|
||||||
|
);
|
||||||
|
|
||||||
|
let rotation = -rotation - std::f32::consts::PI * 2.0;
|
||||||
|
|
||||||
|
Point::new(
|
||||||
|
center.x + (top_left.x - center.x) * rotation.cos()
|
||||||
|
- (top_left.y - center.y) * rotation.sin(),
|
||||||
|
center.y
|
||||||
|
+ (top_left.x - center.x) * rotation.sin()
|
||||||
|
+ (top_left.y - center.y) * rotation.cos(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
(
|
||||||
|
Rectangle::new(position, Size::new(width, height)),
|
||||||
|
Radians(rotation),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the [`Point`] at the center of the [`Rectangle`].
|
/// Returns the [`Point`] at the center of the [`Rectangle`].
|
||||||
pub fn center(&self) -> Point {
|
pub fn center(&self) -> Point {
|
||||||
Point::new(self.center_x(), self.center_y())
|
Point::new(self.center_x(), self.center_y())
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
use crate::alignment;
|
use crate::alignment;
|
||||||
use crate::image;
|
use crate::image::{self, Image};
|
||||||
use crate::renderer::{self, Renderer};
|
use crate::renderer::{self, Renderer};
|
||||||
use crate::svg;
|
use crate::svg;
|
||||||
use crate::text::{self, Text};
|
use crate::text::{self, Text};
|
||||||
use crate::{
|
use crate::{
|
||||||
Background, Color, Font, Pixels, Point, Radians, Rectangle, Size,
|
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
|
||||||
Transformation,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
impl Renderer for () {
|
impl Renderer for () {
|
||||||
|
|
@ -178,21 +177,13 @@ impl text::Editor for () {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl image::Renderer for () {
|
impl image::Renderer for () {
|
||||||
type Handle = ();
|
type Handle = image::Handle;
|
||||||
|
|
||||||
fn measure_image(&self, _handle: &Self::Handle) -> Size<u32> {
|
fn measure_image(&self, _handle: &Self::Handle) -> Size<u32> {
|
||||||
Size::default()
|
Size::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_image(
|
fn draw_image(&mut self, _image: Image, _bounds: Rectangle) {}
|
||||||
&mut self,
|
|
||||||
_handle: Self::Handle,
|
|
||||||
_filter_method: image::FilterMethod,
|
|
||||||
_bounds: Rectangle,
|
|
||||||
_rotation: Radians,
|
|
||||||
_opacity: f32,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl svg::Renderer for () {
|
impl svg::Renderer for () {
|
||||||
|
|
@ -200,13 +191,5 @@ impl svg::Renderer for () {
|
||||||
Size::default()
|
Size::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_svg(
|
fn draw_svg(&mut self, _svg: svg::Svg, _bounds: Rectangle) {}
|
||||||
&mut self,
|
|
||||||
_handle: svg::Handle,
|
|
||||||
_color: Option<Color>,
|
|
||||||
_bounds: Rectangle,
|
|
||||||
_rotation: Radians,
|
|
||||||
_opacity: f32,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,66 @@ use std::hash::{Hash, Hasher as _};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// A raster image that can be drawn.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct Svg<H = Handle> {
|
||||||
|
/// The handle of the [`Svg`].
|
||||||
|
pub handle: H,
|
||||||
|
|
||||||
|
/// The [`Color`] filter to be applied to the [`Svg`].
|
||||||
|
///
|
||||||
|
/// If some [`Color`] is set, the whole [`Svg`] will be
|
||||||
|
/// painted with it—ignoring any intrinsic colors.
|
||||||
|
///
|
||||||
|
/// This can be useful for coloring icons programmatically
|
||||||
|
/// (e.g. with a theme).
|
||||||
|
pub color: Option<Color>,
|
||||||
|
|
||||||
|
/// The rotation to be applied to the image; on its center.
|
||||||
|
pub rotation: Radians,
|
||||||
|
|
||||||
|
/// The opacity of the [`Svg`].
|
||||||
|
///
|
||||||
|
/// 0 means transparent. 1 means opaque.
|
||||||
|
pub opacity: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Svg<Handle> {
|
||||||
|
/// Creates a new [`Svg`] with the given handle.
|
||||||
|
pub fn new(handle: impl Into<Handle>) -> Self {
|
||||||
|
Self {
|
||||||
|
handle: handle.into(),
|
||||||
|
color: None,
|
||||||
|
rotation: Radians(0.0),
|
||||||
|
opacity: 1.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the [`Color`] filter of the [`Svg`].
|
||||||
|
pub fn color(mut self, color: impl Into<Color>) -> Self {
|
||||||
|
self.color = Some(color.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the rotation of the [`Svg`].
|
||||||
|
pub fn rotation(mut self, rotation: impl Into<Radians>) -> Self {
|
||||||
|
self.rotation = rotation.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the opacity of the [`Svg`].
|
||||||
|
pub fn opacity(mut self, opacity: impl Into<f32>) -> Self {
|
||||||
|
self.opacity = opacity.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Handle> for Svg {
|
||||||
|
fn from(handle: &Handle) -> Self {
|
||||||
|
Svg::new(handle.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A handle of Svg data.
|
/// A handle of Svg data.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct Handle {
|
pub struct Handle {
|
||||||
|
|
@ -95,12 +155,5 @@ pub trait Renderer: crate::Renderer {
|
||||||
fn measure_svg(&self, handle: &Handle) -> Size<u32>;
|
fn measure_svg(&self, handle: &Handle) -> Size<u32>;
|
||||||
|
|
||||||
/// Draws an SVG with the given [`Handle`], an optional [`Color`] filter, and inside the provided `bounds`.
|
/// Draws an SVG with the given [`Handle`], an optional [`Color`] filter, and inside the provided `bounds`.
|
||||||
fn draw_svg(
|
fn draw_svg(&mut self, svg: Svg, bounds: Rectangle);
|
||||||
&mut self,
|
|
||||||
handle: Handle,
|
|
||||||
color: Option<Color>,
|
|
||||||
bounds: Rectangle,
|
|
||||||
rotation: Radians,
|
|
||||||
opacity: f32,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ fn main() -> iced::Result {
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct Example {
|
struct Example {
|
||||||
screenshot: Option<Screenshot>,
|
screenshot: Option<(Screenshot, image::Handle)>,
|
||||||
saved_png_path: Option<Result<String, PngError>>,
|
saved_png_path: Option<Result<String, PngError>>,
|
||||||
png_saving: bool,
|
png_saving: bool,
|
||||||
crop_error: Option<screenshot::CropError>,
|
crop_error: Option<screenshot::CropError>,
|
||||||
|
|
@ -52,10 +52,17 @@ impl Example {
|
||||||
.map(Message::Screenshotted);
|
.map(Message::Screenshotted);
|
||||||
}
|
}
|
||||||
Message::Screenshotted(screenshot) => {
|
Message::Screenshotted(screenshot) => {
|
||||||
self.screenshot = Some(screenshot);
|
self.screenshot = Some((
|
||||||
|
screenshot.clone(),
|
||||||
|
image::Handle::from_rgba(
|
||||||
|
screenshot.size.width,
|
||||||
|
screenshot.size.height,
|
||||||
|
screenshot.bytes,
|
||||||
|
),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
Message::Png => {
|
Message::Png => {
|
||||||
if let Some(screenshot) = &self.screenshot {
|
if let Some((screenshot, _handle)) = &self.screenshot {
|
||||||
self.png_saving = true;
|
self.png_saving = true;
|
||||||
|
|
||||||
return Task::perform(
|
return Task::perform(
|
||||||
|
|
@ -81,7 +88,7 @@ impl Example {
|
||||||
self.height_input_value = new_value;
|
self.height_input_value = new_value;
|
||||||
}
|
}
|
||||||
Message::Crop => {
|
Message::Crop => {
|
||||||
if let Some(screenshot) = &self.screenshot {
|
if let Some((screenshot, _handle)) = &self.screenshot {
|
||||||
let cropped = screenshot.crop(Rectangle::<u32> {
|
let cropped = screenshot.crop(Rectangle::<u32> {
|
||||||
x: self.x_input_value.unwrap_or(0),
|
x: self.x_input_value.unwrap_or(0),
|
||||||
y: self.y_input_value.unwrap_or(0),
|
y: self.y_input_value.unwrap_or(0),
|
||||||
|
|
@ -91,7 +98,14 @@ impl Example {
|
||||||
|
|
||||||
match cropped {
|
match cropped {
|
||||||
Ok(screenshot) => {
|
Ok(screenshot) => {
|
||||||
self.screenshot = Some(screenshot);
|
self.screenshot = Some((
|
||||||
|
screenshot.clone(),
|
||||||
|
image::Handle::from_rgba(
|
||||||
|
screenshot.size.width,
|
||||||
|
screenshot.size.height,
|
||||||
|
screenshot.bytes,
|
||||||
|
),
|
||||||
|
));
|
||||||
self.crop_error = None;
|
self.crop_error = None;
|
||||||
}
|
}
|
||||||
Err(crop_error) => {
|
Err(crop_error) => {
|
||||||
|
|
@ -106,20 +120,16 @@ impl Example {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> Element<'_, Message> {
|
fn view(&self) -> Element<'_, Message> {
|
||||||
let image: Element<Message> = if let Some(screenshot) = &self.screenshot
|
let image: Element<Message> =
|
||||||
{
|
if let Some((_screenshot, handle)) = &self.screenshot {
|
||||||
image(image::Handle::from_rgba(
|
image(handle)
|
||||||
screenshot.size.width,
|
.content_fit(ContentFit::Contain)
|
||||||
screenshot.size.height,
|
.width(Fill)
|
||||||
screenshot.clone(),
|
.height(Fill)
|
||||||
))
|
.into()
|
||||||
.content_fit(ContentFit::Contain)
|
} else {
|
||||||
.width(Fill)
|
text("Press the button to take a screenshot!").into()
|
||||||
.height(Fill)
|
};
|
||||||
.into()
|
|
||||||
} else {
|
|
||||||
text("Press the button to take a screenshot!").into()
|
|
||||||
};
|
|
||||||
|
|
||||||
let image = container(image)
|
let image = container(image)
|
||||||
.center_y(FillPortion(2))
|
.center_y(FillPortion(2))
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced.workspace = true
|
iced.workspace = true
|
||||||
iced.features = ["debug", "canvas", "tokio"]
|
iced.features = ["debug", "canvas", "image", "tokio"]
|
||||||
|
|
||||||
rand = "0.8.3"
|
rand = "0.8.3"
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3"
|
||||||
|
|
|
||||||
BIN
examples/solar_system/assets/earth.png
Normal file
BIN
examples/solar_system/assets/earth.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 90 KiB |
BIN
examples/solar_system/assets/moon.png
Normal file
BIN
examples/solar_system/assets/moon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 103 KiB |
BIN
examples/solar_system/assets/sun.png
Normal file
BIN
examples/solar_system/assets/sun.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
|
|
@ -7,10 +7,9 @@
|
||||||
//!
|
//!
|
||||||
//! [1]: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations#An_animated_solar_system
|
//! [1]: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations#An_animated_solar_system
|
||||||
use iced::mouse;
|
use iced::mouse;
|
||||||
use iced::widget::canvas;
|
|
||||||
use iced::widget::canvas::gradient;
|
|
||||||
use iced::widget::canvas::stroke::{self, Stroke};
|
use iced::widget::canvas::stroke::{self, Stroke};
|
||||||
use iced::widget::canvas::{Geometry, Path};
|
use iced::widget::canvas::{Geometry, Path};
|
||||||
|
use iced::widget::{canvas, image};
|
||||||
use iced::window;
|
use iced::window;
|
||||||
use iced::{
|
use iced::{
|
||||||
Color, Element, Fill, Point, Rectangle, Renderer, Size, Subscription,
|
Color, Element, Fill, Point, Rectangle, Renderer, Size, Subscription,
|
||||||
|
|
@ -66,6 +65,9 @@ impl SolarSystem {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct State {
|
struct State {
|
||||||
|
sun: image::Handle,
|
||||||
|
earth: image::Handle,
|
||||||
|
moon: image::Handle,
|
||||||
space_cache: canvas::Cache,
|
space_cache: canvas::Cache,
|
||||||
system_cache: canvas::Cache,
|
system_cache: canvas::Cache,
|
||||||
start: Instant,
|
start: Instant,
|
||||||
|
|
@ -85,6 +87,15 @@ impl State {
|
||||||
let size = window::Settings::default().size;
|
let size = window::Settings::default().size;
|
||||||
|
|
||||||
State {
|
State {
|
||||||
|
sun: image::Handle::from_bytes(
|
||||||
|
include_bytes!("../assets/sun.png").as_slice(),
|
||||||
|
),
|
||||||
|
earth: image::Handle::from_bytes(
|
||||||
|
include_bytes!("../assets/earth.png").as_slice(),
|
||||||
|
),
|
||||||
|
moon: image::Handle::from_bytes(
|
||||||
|
include_bytes!("../assets/moon.png").as_slice(),
|
||||||
|
),
|
||||||
space_cache: canvas::Cache::default(),
|
space_cache: canvas::Cache::default(),
|
||||||
system_cache: canvas::Cache::default(),
|
system_cache: canvas::Cache::default(),
|
||||||
start: now,
|
start: now,
|
||||||
|
|
@ -132,6 +143,8 @@ impl<Message> canvas::Program<Message> for State {
|
||||||
|
|
||||||
let background =
|
let background =
|
||||||
self.space_cache.draw(renderer, bounds.size(), |frame| {
|
self.space_cache.draw(renderer, bounds.size(), |frame| {
|
||||||
|
frame.fill_rectangle(Point::ORIGIN, frame.size(), Color::BLACK);
|
||||||
|
|
||||||
let stars = Path::new(|path| {
|
let stars = Path::new(|path| {
|
||||||
for (p, size) in &self.stars {
|
for (p, size) in &self.stars {
|
||||||
path.rectangle(*p, Size::new(*size, *size));
|
path.rectangle(*p, Size::new(*size, *size));
|
||||||
|
|
@ -144,17 +157,18 @@ impl<Message> canvas::Program<Message> for State {
|
||||||
|
|
||||||
let system = self.system_cache.draw(renderer, bounds.size(), |frame| {
|
let system = self.system_cache.draw(renderer, bounds.size(), |frame| {
|
||||||
let center = frame.center();
|
let center = frame.center();
|
||||||
|
frame.translate(Vector::new(center.x, center.y));
|
||||||
|
|
||||||
let sun = Path::circle(center, Self::SUN_RADIUS);
|
frame.draw_image(
|
||||||
let orbit = Path::circle(center, Self::ORBIT_RADIUS);
|
Rectangle::with_radius(Self::SUN_RADIUS),
|
||||||
|
&self.sun,
|
||||||
|
);
|
||||||
|
|
||||||
frame.fill(&sun, Color::from_rgb8(0xF9, 0xD7, 0x1C));
|
let orbit = Path::circle(Point::ORIGIN, Self::ORBIT_RADIUS);
|
||||||
frame.stroke(
|
frame.stroke(
|
||||||
&orbit,
|
&orbit,
|
||||||
Stroke {
|
Stroke {
|
||||||
style: stroke::Style::Solid(Color::from_rgba8(
|
style: stroke::Style::Solid(Color::WHITE.scale_alpha(0.1)),
|
||||||
0, 153, 255, 0.1,
|
|
||||||
)),
|
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
line_dash: canvas::LineDash {
|
line_dash: canvas::LineDash {
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
|
@ -168,30 +182,21 @@ impl<Message> canvas::Program<Message> for State {
|
||||||
let rotation = (2.0 * PI / 60.0) * elapsed.as_secs() as f32
|
let rotation = (2.0 * PI / 60.0) * elapsed.as_secs() as f32
|
||||||
+ (2.0 * PI / 60_000.0) * elapsed.subsec_millis() as f32;
|
+ (2.0 * PI / 60_000.0) * elapsed.subsec_millis() as f32;
|
||||||
|
|
||||||
frame.with_save(|frame| {
|
frame.rotate(rotation);
|
||||||
frame.translate(Vector::new(center.x, center.y));
|
frame.translate(Vector::new(Self::ORBIT_RADIUS, 0.0));
|
||||||
frame.rotate(rotation);
|
|
||||||
frame.translate(Vector::new(Self::ORBIT_RADIUS, 0.0));
|
|
||||||
|
|
||||||
let earth = Path::circle(Point::ORIGIN, Self::EARTH_RADIUS);
|
frame.draw_image(
|
||||||
|
Rectangle::with_radius(Self::EARTH_RADIUS),
|
||||||
|
canvas::Image::new(&self.earth).rotation(-rotation * 20.0),
|
||||||
|
);
|
||||||
|
|
||||||
let earth_fill = gradient::Linear::new(
|
frame.rotate(rotation * 10.0);
|
||||||
Point::new(-Self::EARTH_RADIUS, 0.0),
|
frame.translate(Vector::new(0.0, Self::MOON_DISTANCE));
|
||||||
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);
|
frame.draw_image(
|
||||||
|
Rectangle::with_radius(Self::MOON_RADIUS),
|
||||||
frame.with_save(|frame| {
|
&self.moon,
|
||||||
frame.rotate(rotation * 10.0);
|
);
|
||||||
frame.translate(Vector::new(0.0, Self::MOON_DISTANCE));
|
|
||||||
|
|
||||||
let moon = Path::circle(Point::ORIGIN, Self::MOON_RADIUS);
|
|
||||||
frame.fill(&moon, Color::WHITE);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
vec![background, system]
|
vec![background, system]
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ all-features = true
|
||||||
[features]
|
[features]
|
||||||
geometry = ["lyon_path"]
|
geometry = ["lyon_path"]
|
||||||
image = ["dep:image", "kamadak-exif"]
|
image = ["dep:image", "kamadak-exif"]
|
||||||
|
svg = []
|
||||||
web-colors = []
|
web-colors = []
|
||||||
fira-sans = []
|
fira-sans = []
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ pub use stroke::{LineCap, LineDash, LineJoin, Stroke};
|
||||||
pub use style::Style;
|
pub use style::Style;
|
||||||
pub use text::Text;
|
pub use text::Text;
|
||||||
|
|
||||||
|
pub use crate::core::{Image, Svg};
|
||||||
pub use crate::gradient::{self, Gradient};
|
pub use crate::gradient::{self, Gradient};
|
||||||
|
|
||||||
use crate::cache::Cached;
|
use crate::cache::Cached;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
//! Draw and generate geometry.
|
//! Draw and generate geometry.
|
||||||
use crate::core::{Point, Radians, Rectangle, Size, Vector};
|
use crate::core::{Point, Radians, Rectangle, Size, Vector};
|
||||||
use crate::geometry::{self, Fill, Path, Stroke, Text};
|
use crate::geometry::{self, Fill, Image, Path, Stroke, Svg, Text};
|
||||||
|
|
||||||
/// The region of a surface that can be used to draw geometry.
|
/// The region of a surface that can be used to draw geometry.
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
|
|
@ -75,6 +75,18 @@ where
|
||||||
self.raw.fill_text(text);
|
self.raw.fill_text(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Draws the given [`Image`] on the [`Frame`] inside the given bounds.
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
pub fn draw_image(&mut self, bounds: Rectangle, image: impl Into<Image>) {
|
||||||
|
self.raw.draw_image(bounds, image);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draws the given [`Svg`] on the [`Frame`] inside the given bounds.
|
||||||
|
#[cfg(feature = "svg")]
|
||||||
|
pub fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into<Svg>) {
|
||||||
|
self.raw.draw_svg(bounds, svg);
|
||||||
|
}
|
||||||
|
|
||||||
/// Stores the current transform of the [`Frame`] and executes the given
|
/// Stores the current transform of the [`Frame`] and executes the given
|
||||||
/// drawing operations, restoring the transform afterwards.
|
/// drawing operations, restoring the transform afterwards.
|
||||||
///
|
///
|
||||||
|
|
@ -116,8 +128,7 @@ where
|
||||||
let mut frame = self.draft(region);
|
let mut frame = self.draft(region);
|
||||||
|
|
||||||
let result = f(&mut frame);
|
let result = f(&mut frame);
|
||||||
|
self.paste(frame);
|
||||||
self.paste(frame, Point::new(region.x, region.y));
|
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
@ -134,8 +145,8 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draws the contents of the given [`Frame`] with origin at the given [`Point`].
|
/// Draws the contents of the given [`Frame`] with origin at the given [`Point`].
|
||||||
fn paste(&mut self, frame: Self, at: Point) {
|
fn paste(&mut self, frame: Self) {
|
||||||
self.raw.paste(frame.raw, at);
|
self.raw.paste(frame.raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Applies a translation to the current transform of the [`Frame`].
|
/// Applies a translation to the current transform of the [`Frame`].
|
||||||
|
|
@ -186,7 +197,7 @@ pub trait Backend: Sized {
|
||||||
fn scale_nonuniform(&mut self, scale: impl Into<Vector>);
|
fn scale_nonuniform(&mut self, scale: impl Into<Vector>);
|
||||||
|
|
||||||
fn draft(&mut self, clip_bounds: Rectangle) -> Self;
|
fn draft(&mut self, clip_bounds: Rectangle) -> Self;
|
||||||
fn paste(&mut self, frame: Self, at: Point);
|
fn paste(&mut self, frame: Self);
|
||||||
|
|
||||||
fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>);
|
fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>);
|
||||||
|
|
||||||
|
|
@ -199,6 +210,9 @@ pub trait Backend: Sized {
|
||||||
fill: impl Into<Fill>,
|
fill: impl Into<Fill>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
fn draw_image(&mut self, bounds: Rectangle, image: impl Into<Image>);
|
||||||
|
fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into<Svg>);
|
||||||
|
|
||||||
fn into_geometry(self) -> Self::Geometry;
|
fn into_geometry(self) -> Self::Geometry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -231,7 +245,7 @@ impl Backend for () {
|
||||||
fn scale_nonuniform(&mut self, _scale: impl Into<Vector>) {}
|
fn scale_nonuniform(&mut self, _scale: impl Into<Vector>) {}
|
||||||
|
|
||||||
fn draft(&mut self, _clip_bounds: Rectangle) -> Self {}
|
fn draft(&mut self, _clip_bounds: Rectangle) -> Self {}
|
||||||
fn paste(&mut self, _frame: Self, _at: Point) {}
|
fn paste(&mut self, _frame: Self) {}
|
||||||
|
|
||||||
fn stroke<'a>(&mut self, _path: &Path, _stroke: impl Into<Stroke<'a>>) {}
|
fn stroke<'a>(&mut self, _path: &Path, _stroke: impl Into<Stroke<'a>>) {}
|
||||||
|
|
||||||
|
|
@ -245,5 +259,8 @@ impl Backend for () {
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_image(&mut self, _bounds: Rectangle, _image: impl Into<Image>) {}
|
||||||
|
fn draw_svg(&mut self, _bounds: Rectangle, _svg: impl Into<Svg>) {}
|
||||||
|
|
||||||
fn into_geometry(self) -> Self::Geometry {}
|
fn into_geometry(self) -> Self::Geometry {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,57 +2,26 @@
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
pub use ::image as image_rs;
|
pub use ::image as image_rs;
|
||||||
|
|
||||||
use crate::core::{image, svg, Color, Radians, Rectangle};
|
use crate::core::image;
|
||||||
|
use crate::core::svg;
|
||||||
|
use crate::core::Rectangle;
|
||||||
|
|
||||||
/// A raster or vector image.
|
/// A raster or vector image.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Image {
|
pub enum Image {
|
||||||
/// A raster image.
|
/// A raster image.
|
||||||
Raster {
|
Raster(image::Image, Rectangle),
|
||||||
/// The handle of a raster image.
|
|
||||||
handle: image::Handle,
|
|
||||||
|
|
||||||
/// The filter method of a raster image.
|
|
||||||
filter_method: image::FilterMethod,
|
|
||||||
|
|
||||||
/// The bounds of the image.
|
|
||||||
bounds: Rectangle,
|
|
||||||
|
|
||||||
/// The rotation of the image.
|
|
||||||
rotation: Radians,
|
|
||||||
|
|
||||||
/// The opacity of the image.
|
|
||||||
opacity: f32,
|
|
||||||
},
|
|
||||||
/// A vector image.
|
/// A vector image.
|
||||||
Vector {
|
Vector(svg::Svg, Rectangle),
|
||||||
/// The handle of a vector image.
|
|
||||||
handle: svg::Handle,
|
|
||||||
|
|
||||||
/// The [`Color`] filter
|
|
||||||
color: Option<Color>,
|
|
||||||
|
|
||||||
/// The bounds of the image.
|
|
||||||
bounds: Rectangle,
|
|
||||||
|
|
||||||
/// The rotation of the image.
|
|
||||||
rotation: Radians,
|
|
||||||
|
|
||||||
/// The opacity of the image.
|
|
||||||
opacity: f32,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Image {
|
impl Image {
|
||||||
/// Returns the bounds of the [`Image`].
|
/// Returns the bounds of the [`Image`].
|
||||||
pub fn bounds(&self) -> Rectangle {
|
pub fn bounds(&self) -> Rectangle {
|
||||||
match self {
|
match self {
|
||||||
Image::Raster {
|
Image::Raster(image, bounds) => bounds.rotate(image.rotation),
|
||||||
bounds, rotation, ..
|
Image::Vector(svg, bounds) => bounds.rotate(svg.rotation),
|
||||||
}
|
|
||||||
| Image::Vector {
|
|
||||||
bounds, rotation, ..
|
|
||||||
} => bounds.rotate(*rotation),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use crate::core::image;
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
use crate::core::svg;
|
use crate::core::svg;
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
self, Background, Color, Point, Radians, Rectangle, Size, Transformation,
|
self, Background, Color, Image, Point, Rectangle, Size, Svg, Transformation,
|
||||||
};
|
};
|
||||||
use crate::graphics;
|
use crate::graphics;
|
||||||
use crate::graphics::compositor;
|
use crate::graphics::compositor;
|
||||||
|
|
@ -149,25 +149,8 @@ where
|
||||||
delegate!(self, renderer, renderer.measure_image(handle))
|
delegate!(self, renderer, renderer.measure_image(handle))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_image(
|
fn draw_image(&mut self, image: Image<A::Handle>, bounds: Rectangle) {
|
||||||
&mut self,
|
delegate!(self, renderer, renderer.draw_image(image, bounds));
|
||||||
handle: Self::Handle,
|
|
||||||
filter_method: image::FilterMethod,
|
|
||||||
bounds: Rectangle,
|
|
||||||
rotation: Radians,
|
|
||||||
opacity: f32,
|
|
||||||
) {
|
|
||||||
delegate!(
|
|
||||||
self,
|
|
||||||
renderer,
|
|
||||||
renderer.draw_image(
|
|
||||||
handle,
|
|
||||||
filter_method,
|
|
||||||
bounds,
|
|
||||||
rotation,
|
|
||||||
opacity
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -180,19 +163,8 @@ where
|
||||||
delegate!(self, renderer, renderer.measure_svg(handle))
|
delegate!(self, renderer, renderer.measure_svg(handle))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_svg(
|
fn draw_svg(&mut self, svg: Svg, bounds: Rectangle) {
|
||||||
&mut self,
|
delegate!(self, renderer, renderer.draw_svg(svg, bounds));
|
||||||
handle: svg::Handle,
|
|
||||||
color: Option<Color>,
|
|
||||||
bounds: Rectangle,
|
|
||||||
rotation: Radians,
|
|
||||||
opacity: f32,
|
|
||||||
) {
|
|
||||||
delegate!(
|
|
||||||
self,
|
|
||||||
renderer,
|
|
||||||
renderer.draw_svg(handle, color, bounds, rotation, opacity)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -441,9 +413,9 @@ where
|
||||||
#[cfg(feature = "geometry")]
|
#[cfg(feature = "geometry")]
|
||||||
mod geometry {
|
mod geometry {
|
||||||
use super::Renderer;
|
use super::Renderer;
|
||||||
use crate::core::{Point, Radians, Rectangle, Size, Vector};
|
use crate::core::{Point, Radians, Rectangle, Size, Svg, Vector};
|
||||||
use crate::graphics::cache::{self, Cached};
|
use crate::graphics::cache::{self, Cached};
|
||||||
use crate::graphics::geometry::{self, Fill, Path, Stroke, Text};
|
use crate::graphics::geometry::{self, Fill, Image, Path, Stroke, Text};
|
||||||
|
|
||||||
impl<A, B> geometry::Renderer for Renderer<A, B>
|
impl<A, B> geometry::Renderer for Renderer<A, B>
|
||||||
where
|
where
|
||||||
|
|
@ -572,6 +544,14 @@ mod geometry {
|
||||||
delegate!(self, frame, frame.fill_text(text));
|
delegate!(self, frame, frame.fill_text(text));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_image(&mut self, bounds: Rectangle, image: impl Into<Image>) {
|
||||||
|
delegate!(self, frame, frame.draw_image(bounds, image));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into<Svg>) {
|
||||||
|
delegate!(self, frame, frame.draw_svg(bounds, svg));
|
||||||
|
}
|
||||||
|
|
||||||
fn push_transform(&mut self) {
|
fn push_transform(&mut self) {
|
||||||
delegate!(self, frame, frame.push_transform());
|
delegate!(self, frame, frame.push_transform());
|
||||||
}
|
}
|
||||||
|
|
@ -587,13 +567,13 @@ mod geometry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paste(&mut self, frame: Self, at: Point) {
|
fn paste(&mut self, frame: Self) {
|
||||||
match (self, frame) {
|
match (self, frame) {
|
||||||
(Self::Primary(target), Self::Primary(source)) => {
|
(Self::Primary(target), Self::Primary(source)) => {
|
||||||
target.paste(source, at);
|
target.paste(source);
|
||||||
}
|
}
|
||||||
(Self::Secondary(target), Self::Secondary(source)) => {
|
(Self::Secondary(target), Self::Secondary(source)) => {
|
||||||
target.paste(source, at);
|
target.paste(source);
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
image = ["iced_graphics/image"]
|
image = ["iced_graphics/image"]
|
||||||
svg = ["resvg"]
|
svg = ["iced_graphics/svg", "resvg"]
|
||||||
geometry = ["iced_graphics/geometry"]
|
geometry = ["iced_graphics/geometry"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
|
||||||
|
|
@ -550,13 +550,7 @@ impl Engine {
|
||||||
) {
|
) {
|
||||||
match image {
|
match image {
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
Image::Raster {
|
Image::Raster(raster, bounds) => {
|
||||||
handle,
|
|
||||||
filter_method,
|
|
||||||
bounds,
|
|
||||||
rotation,
|
|
||||||
opacity,
|
|
||||||
} => {
|
|
||||||
let physical_bounds = *bounds * _transformation;
|
let physical_bounds = *bounds * _transformation;
|
||||||
|
|
||||||
if !_clip_bounds.intersects(&physical_bounds) {
|
if !_clip_bounds.intersects(&physical_bounds) {
|
||||||
|
|
@ -567,7 +561,7 @@ impl Engine {
|
||||||
.then_some(_clip_mask as &_);
|
.then_some(_clip_mask as &_);
|
||||||
|
|
||||||
let center = physical_bounds.center();
|
let center = physical_bounds.center();
|
||||||
let radians = f32::from(*rotation);
|
let radians = f32::from(raster.rotation);
|
||||||
|
|
||||||
let transform = into_transform(_transformation).post_rotate_at(
|
let transform = into_transform(_transformation).post_rotate_at(
|
||||||
radians.to_degrees(),
|
radians.to_degrees(),
|
||||||
|
|
@ -576,23 +570,17 @@ impl Engine {
|
||||||
);
|
);
|
||||||
|
|
||||||
self.raster_pipeline.draw(
|
self.raster_pipeline.draw(
|
||||||
handle,
|
&raster.handle,
|
||||||
*filter_method,
|
raster.filter_method,
|
||||||
*bounds,
|
*bounds,
|
||||||
*opacity,
|
raster.opacity,
|
||||||
_pixels,
|
_pixels,
|
||||||
transform,
|
transform,
|
||||||
clip_mask,
|
clip_mask,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
#[cfg(feature = "svg")]
|
#[cfg(feature = "svg")]
|
||||||
Image::Vector {
|
Image::Vector(svg, bounds) => {
|
||||||
handle,
|
|
||||||
color,
|
|
||||||
bounds,
|
|
||||||
rotation,
|
|
||||||
opacity,
|
|
||||||
} => {
|
|
||||||
let physical_bounds = *bounds * _transformation;
|
let physical_bounds = *bounds * _transformation;
|
||||||
|
|
||||||
if !_clip_bounds.intersects(&physical_bounds) {
|
if !_clip_bounds.intersects(&physical_bounds) {
|
||||||
|
|
@ -603,7 +591,7 @@ impl Engine {
|
||||||
.then_some(_clip_mask as &_);
|
.then_some(_clip_mask as &_);
|
||||||
|
|
||||||
let center = physical_bounds.center();
|
let center = physical_bounds.center();
|
||||||
let radians = f32::from(*rotation);
|
let radians = f32::from(svg.rotation);
|
||||||
|
|
||||||
let transform = into_transform(_transformation).post_rotate_at(
|
let transform = into_transform(_transformation).post_rotate_at(
|
||||||
radians.to_degrees(),
|
radians.to_degrees(),
|
||||||
|
|
@ -612,10 +600,10 @@ impl Engine {
|
||||||
);
|
);
|
||||||
|
|
||||||
self.vector_pipeline.draw(
|
self.vector_pipeline.draw(
|
||||||
handle,
|
&svg.handle,
|
||||||
*color,
|
svg.color,
|
||||||
physical_bounds,
|
physical_bounds,
|
||||||
*opacity,
|
svg.opacity,
|
||||||
_pixels,
|
_pixels,
|
||||||
transform,
|
transform,
|
||||||
clip_mask,
|
clip_mask,
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use crate::core::text::LineHeight;
|
use crate::core::text::LineHeight;
|
||||||
use crate::core::{Pixels, Point, Radians, Rectangle, Size, Vector};
|
use crate::core::{self, Pixels, Point, Radians, Rectangle, Size, Svg, Vector};
|
||||||
use crate::graphics::cache::{self, Cached};
|
use crate::graphics::cache::{self, Cached};
|
||||||
use crate::graphics::geometry::fill::{self, Fill};
|
use crate::graphics::geometry::fill::{self, Fill};
|
||||||
use crate::graphics::geometry::stroke::{self, Stroke};
|
use crate::graphics::geometry::stroke::{self, Stroke};
|
||||||
use crate::graphics::geometry::{self, Path, Style};
|
use crate::graphics::geometry::{self, Path, Style};
|
||||||
use crate::graphics::{Gradient, Text};
|
use crate::graphics::{self, Gradient, Image, Text};
|
||||||
use crate::Primitive;
|
use crate::Primitive;
|
||||||
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
@ -13,6 +13,7 @@ use std::rc::Rc;
|
||||||
pub enum Geometry {
|
pub enum Geometry {
|
||||||
Live {
|
Live {
|
||||||
text: Vec<Text>,
|
text: Vec<Text>,
|
||||||
|
images: Vec<graphics::Image>,
|
||||||
primitives: Vec<Primitive>,
|
primitives: Vec<Primitive>,
|
||||||
clip_bounds: Rectangle,
|
clip_bounds: Rectangle,
|
||||||
},
|
},
|
||||||
|
|
@ -22,6 +23,7 @@ pub enum Geometry {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Cache {
|
pub struct Cache {
|
||||||
pub text: Rc<[Text]>,
|
pub text: Rc<[Text]>,
|
||||||
|
pub images: Rc<[graphics::Image]>,
|
||||||
pub primitives: Rc<[Primitive]>,
|
pub primitives: Rc<[Primitive]>,
|
||||||
pub clip_bounds: Rectangle,
|
pub clip_bounds: Rectangle,
|
||||||
}
|
}
|
||||||
|
|
@ -37,10 +39,12 @@ impl Cached for Geometry {
|
||||||
match self {
|
match self {
|
||||||
Self::Live {
|
Self::Live {
|
||||||
primitives,
|
primitives,
|
||||||
|
images,
|
||||||
text,
|
text,
|
||||||
clip_bounds,
|
clip_bounds,
|
||||||
} => Cache {
|
} => Cache {
|
||||||
primitives: Rc::from(primitives),
|
primitives: Rc::from(primitives),
|
||||||
|
images: Rc::from(images),
|
||||||
text: Rc::from(text),
|
text: Rc::from(text),
|
||||||
clip_bounds,
|
clip_bounds,
|
||||||
},
|
},
|
||||||
|
|
@ -55,6 +59,7 @@ pub struct Frame {
|
||||||
transform: tiny_skia::Transform,
|
transform: tiny_skia::Transform,
|
||||||
stack: Vec<tiny_skia::Transform>,
|
stack: Vec<tiny_skia::Transform>,
|
||||||
primitives: Vec<Primitive>,
|
primitives: Vec<Primitive>,
|
||||||
|
images: Vec<graphics::Image>,
|
||||||
text: Vec<Text>,
|
text: Vec<Text>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,6 +73,7 @@ impl Frame {
|
||||||
clip_bounds,
|
clip_bounds,
|
||||||
stack: Vec::new(),
|
stack: Vec::new(),
|
||||||
primitives: Vec::new(),
|
primitives: Vec::new(),
|
||||||
|
images: Vec::new(),
|
||||||
text: Vec::new(),
|
text: Vec::new(),
|
||||||
transform: tiny_skia::Transform::from_translate(
|
transform: tiny_skia::Transform::from_translate(
|
||||||
clip_bounds.x,
|
clip_bounds.x,
|
||||||
|
|
@ -238,7 +244,7 @@ impl geometry::frame::Backend for Frame {
|
||||||
Self::with_clip(clip_bounds)
|
Self::with_clip(clip_bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paste(&mut self, frame: Self, _at: Point) {
|
fn paste(&mut self, frame: Self) {
|
||||||
self.primitives.extend(frame.primitives);
|
self.primitives.extend(frame.primitives);
|
||||||
self.text.extend(frame.text);
|
self.text.extend(frame.text);
|
||||||
}
|
}
|
||||||
|
|
@ -269,10 +275,63 @@ impl geometry::frame::Backend for Frame {
|
||||||
fn into_geometry(self) -> Geometry {
|
fn into_geometry(self) -> Geometry {
|
||||||
Geometry::Live {
|
Geometry::Live {
|
||||||
primitives: self.primitives,
|
primitives: self.primitives,
|
||||||
|
images: self.images,
|
||||||
text: self.text,
|
text: self.text,
|
||||||
clip_bounds: self.clip_bounds,
|
clip_bounds: self.clip_bounds,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_image(&mut self, bounds: Rectangle, image: impl Into<core::Image>) {
|
||||||
|
let mut image = image.into();
|
||||||
|
|
||||||
|
let (bounds, external_rotation) =
|
||||||
|
transform_rectangle(bounds, self.transform);
|
||||||
|
|
||||||
|
image.rotation += external_rotation;
|
||||||
|
|
||||||
|
self.images.push(graphics::Image::Raster(image, bounds));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into<Svg>) {
|
||||||
|
let mut svg = svg.into();
|
||||||
|
|
||||||
|
let (bounds, external_rotation) =
|
||||||
|
transform_rectangle(bounds, self.transform);
|
||||||
|
|
||||||
|
svg.rotation += external_rotation;
|
||||||
|
|
||||||
|
self.images.push(Image::Vector(svg, bounds));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform_rectangle(
|
||||||
|
rectangle: Rectangle,
|
||||||
|
transform: tiny_skia::Transform,
|
||||||
|
) -> (Rectangle, Radians) {
|
||||||
|
let mut top_left = tiny_skia::Point {
|
||||||
|
x: rectangle.x,
|
||||||
|
y: rectangle.y,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut top_right = tiny_skia::Point {
|
||||||
|
x: rectangle.x + rectangle.width,
|
||||||
|
y: rectangle.y,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut bottom_left = tiny_skia::Point {
|
||||||
|
x: rectangle.x,
|
||||||
|
y: rectangle.y + rectangle.height,
|
||||||
|
};
|
||||||
|
|
||||||
|
transform.map_point(&mut top_left);
|
||||||
|
transform.map_point(&mut top_right);
|
||||||
|
transform.map_point(&mut bottom_left);
|
||||||
|
|
||||||
|
Rectangle::with_vertices(
|
||||||
|
Point::new(top_left.x, top_left.y),
|
||||||
|
Point::new(top_right.x, top_right.y),
|
||||||
|
Point::new(bottom_left.x, bottom_left.y),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_path(path: &Path) -> Option<tiny_skia::Path> {
|
fn convert_path(path: &Path) -> Option<tiny_skia::Path> {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
|
use crate::core::renderer::Quad;
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
image, renderer::Quad, svg, Background, Color, Point, Radians, Rectangle,
|
self, Background, Color, Point, Rectangle, Svg, Transformation,
|
||||||
Transformation,
|
|
||||||
};
|
};
|
||||||
use crate::graphics::damage;
|
use crate::graphics::damage;
|
||||||
use crate::graphics::layer;
|
use crate::graphics::layer;
|
||||||
|
|
@ -72,7 +72,7 @@ impl Layer {
|
||||||
|
|
||||||
pub fn draw_text(
|
pub fn draw_text(
|
||||||
&mut self,
|
&mut self,
|
||||||
text: crate::core::Text,
|
text: core::Text,
|
||||||
position: Point,
|
position: Point,
|
||||||
color: Color,
|
color: Color,
|
||||||
clip_bounds: Rectangle,
|
clip_bounds: Rectangle,
|
||||||
|
|
@ -115,42 +115,35 @@ impl Layer {
|
||||||
.push(Item::Cached(text, clip_bounds, transformation));
|
.push(Item::Cached(text, clip_bounds, transformation));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_image(
|
pub fn draw_image(&mut self, image: Image, transformation: Transformation) {
|
||||||
|
match image {
|
||||||
|
Image::Raster(raster, bounds) => {
|
||||||
|
self.draw_raster(raster, bounds, transformation);
|
||||||
|
}
|
||||||
|
Image::Vector(svg, bounds) => {
|
||||||
|
self.draw_svg(svg, bounds, transformation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_raster(
|
||||||
&mut self,
|
&mut self,
|
||||||
handle: image::Handle,
|
image: core::Image,
|
||||||
filter_method: image::FilterMethod,
|
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
transformation: Transformation,
|
transformation: Transformation,
|
||||||
rotation: Radians,
|
|
||||||
opacity: f32,
|
|
||||||
) {
|
) {
|
||||||
let image = Image::Raster {
|
let image = Image::Raster(image, bounds * transformation);
|
||||||
handle,
|
|
||||||
filter_method,
|
|
||||||
bounds: bounds * transformation,
|
|
||||||
rotation,
|
|
||||||
opacity,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.images.push(image);
|
self.images.push(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_svg(
|
pub fn draw_svg(
|
||||||
&mut self,
|
&mut self,
|
||||||
handle: svg::Handle,
|
svg: Svg,
|
||||||
color: Option<Color>,
|
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
transformation: Transformation,
|
transformation: Transformation,
|
||||||
rotation: Radians,
|
|
||||||
opacity: f32,
|
|
||||||
) {
|
) {
|
||||||
let svg = Image::Vector {
|
let svg = Image::Vector(svg, bounds * transformation);
|
||||||
handle,
|
|
||||||
color,
|
|
||||||
bounds: bounds * transformation,
|
|
||||||
rotation,
|
|
||||||
opacity,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.images.push(svg);
|
self.images.push(svg);
|
||||||
}
|
}
|
||||||
|
|
@ -293,7 +286,7 @@ impl graphics::Layer for Layer {
|
||||||
|
|
||||||
fn flush(&mut self) {}
|
fn flush(&mut self) {}
|
||||||
|
|
||||||
fn resize(&mut self, bounds: graphics::core::Rectangle) {
|
fn resize(&mut self, bounds: Rectangle) {
|
||||||
self.bounds = bounds;
|
self.bounds = bounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -178,6 +178,16 @@ impl Renderer {
|
||||||
engine::adjust_clip_mask(clip_mask, clip_bounds);
|
engine::adjust_clip_mask(clip_mask, clip_bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for image in &layer.images {
|
||||||
|
self.engine.draw_image(
|
||||||
|
image,
|
||||||
|
Transformation::scale(scale_factor),
|
||||||
|
pixels,
|
||||||
|
clip_mask,
|
||||||
|
clip_bounds,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
for group in &layer.text {
|
for group in &layer.text {
|
||||||
for text in group.as_slice() {
|
for text in group.as_slice() {
|
||||||
self.engine.draw_text(
|
self.engine.draw_text(
|
||||||
|
|
@ -190,16 +200,6 @@ impl Renderer {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for image in &layer.images {
|
|
||||||
self.engine.draw_image(
|
|
||||||
image,
|
|
||||||
Transformation::scale(scale_factor),
|
|
||||||
pixels,
|
|
||||||
clip_mask,
|
|
||||||
clip_bounds,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !overlay.is_empty() {
|
if !overlay.is_empty() {
|
||||||
|
|
@ -330,6 +330,7 @@ impl graphics::geometry::Renderer for Renderer {
|
||||||
match geometry {
|
match geometry {
|
||||||
Geometry::Live {
|
Geometry::Live {
|
||||||
primitives,
|
primitives,
|
||||||
|
images,
|
||||||
text,
|
text,
|
||||||
clip_bounds,
|
clip_bounds,
|
||||||
} => {
|
} => {
|
||||||
|
|
@ -339,6 +340,10 @@ impl graphics::geometry::Renderer for Renderer {
|
||||||
transformation,
|
transformation,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
for image in images {
|
||||||
|
layer.draw_image(image, transformation);
|
||||||
|
}
|
||||||
|
|
||||||
layer.draw_text_group(text, clip_bounds, transformation);
|
layer.draw_text_group(text, clip_bounds, transformation);
|
||||||
}
|
}
|
||||||
Geometry::Cache(cache) => {
|
Geometry::Cache(cache) => {
|
||||||
|
|
@ -348,6 +353,10 @@ impl graphics::geometry::Renderer for Renderer {
|
||||||
transformation,
|
transformation,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
for image in cache.images.iter() {
|
||||||
|
layer.draw_image(image.clone(), transformation);
|
||||||
|
}
|
||||||
|
|
||||||
layer.draw_text_cache(
|
layer.draw_text_cache(
|
||||||
cache.text,
|
cache.text,
|
||||||
cache.clip_bounds,
|
cache.clip_bounds,
|
||||||
|
|
@ -372,23 +381,9 @@ impl core::image::Renderer for Renderer {
|
||||||
self.engine.raster_pipeline.dimensions(handle)
|
self.engine.raster_pipeline.dimensions(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_image(
|
fn draw_image(&mut self, image: core::Image, bounds: Rectangle) {
|
||||||
&mut self,
|
|
||||||
handle: Self::Handle,
|
|
||||||
filter_method: core::image::FilterMethod,
|
|
||||||
bounds: Rectangle,
|
|
||||||
rotation: core::Radians,
|
|
||||||
opacity: f32,
|
|
||||||
) {
|
|
||||||
let (layer, transformation) = self.layers.current_mut();
|
let (layer, transformation) = self.layers.current_mut();
|
||||||
layer.draw_image(
|
layer.draw_raster(image, bounds, transformation);
|
||||||
handle,
|
|
||||||
filter_method,
|
|
||||||
bounds,
|
|
||||||
transformation,
|
|
||||||
rotation,
|
|
||||||
opacity,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -401,23 +396,9 @@ impl core::svg::Renderer for Renderer {
|
||||||
self.engine.vector_pipeline.viewport_dimensions(handle)
|
self.engine.vector_pipeline.viewport_dimensions(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_svg(
|
fn draw_svg(&mut self, svg: core::Svg, bounds: Rectangle) {
|
||||||
&mut self,
|
|
||||||
handle: core::svg::Handle,
|
|
||||||
color: Option<Color>,
|
|
||||||
bounds: Rectangle,
|
|
||||||
rotation: core::Radians,
|
|
||||||
opacity: f32,
|
|
||||||
) {
|
|
||||||
let (layer, transformation) = self.layers.current_mut();
|
let (layer, transformation) = self.layers.current_mut();
|
||||||
layer.draw_svg(
|
layer.draw_svg(svg, bounds, transformation);
|
||||||
handle,
|
|
||||||
color,
|
|
||||||
bounds,
|
|
||||||
transformation,
|
|
||||||
rotation,
|
|
||||||
opacity,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ all-features = true
|
||||||
[features]
|
[features]
|
||||||
geometry = ["iced_graphics/geometry", "lyon"]
|
geometry = ["iced_graphics/geometry", "lyon"]
|
||||||
image = ["iced_graphics/image"]
|
image = ["iced_graphics/image"]
|
||||||
svg = ["resvg/text"]
|
svg = ["iced_graphics/svg", "resvg/text"]
|
||||||
web-colors = ["iced_graphics/web-colors"]
|
web-colors = ["iced_graphics/web-colors"]
|
||||||
webgl = ["wgpu/webgl"]
|
webgl = ["wgpu/webgl"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
//! Build and draw geometry.
|
//! Build and draw geometry.
|
||||||
use crate::core::text::LineHeight;
|
use crate::core::text::LineHeight;
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
Pixels, Point, Radians, Rectangle, Size, Transformation, Vector,
|
self, Pixels, Point, Radians, Rectangle, Size, Svg, Transformation, Vector,
|
||||||
};
|
};
|
||||||
use crate::graphics::cache::{self, Cached};
|
use crate::graphics::cache::{self, Cached};
|
||||||
use crate::graphics::color;
|
use crate::graphics::color;
|
||||||
|
|
@ -11,7 +11,7 @@ use crate::graphics::geometry::{
|
||||||
};
|
};
|
||||||
use crate::graphics::gradient::{self, Gradient};
|
use crate::graphics::gradient::{self, Gradient};
|
||||||
use crate::graphics::mesh::{self, Mesh};
|
use crate::graphics::mesh::{self, Mesh};
|
||||||
use crate::graphics::{self, Text};
|
use crate::graphics::{Image, Text};
|
||||||
use crate::text;
|
use crate::text;
|
||||||
use crate::triangle;
|
use crate::triangle;
|
||||||
|
|
||||||
|
|
@ -19,16 +19,22 @@ use lyon::geom::euclid;
|
||||||
use lyon::tessellation;
|
use lyon::tessellation;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Geometry {
|
pub enum Geometry {
|
||||||
Live { meshes: Vec<Mesh>, text: Vec<Text> },
|
Live {
|
||||||
|
meshes: Vec<Mesh>,
|
||||||
|
images: Vec<Image>,
|
||||||
|
text: Vec<Text>,
|
||||||
|
},
|
||||||
Cached(Cache),
|
Cached(Cache),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Cache {
|
pub struct Cache {
|
||||||
pub meshes: Option<triangle::Cache>,
|
pub meshes: Option<triangle::Cache>,
|
||||||
|
pub images: Option<Arc<[Image]>>,
|
||||||
pub text: Option<text::Cache>,
|
pub text: Option<text::Cache>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,7 +51,17 @@ impl Cached for Geometry {
|
||||||
previous: Option<Self::Cache>,
|
previous: Option<Self::Cache>,
|
||||||
) -> Self::Cache {
|
) -> Self::Cache {
|
||||||
match self {
|
match self {
|
||||||
Self::Live { meshes, text } => {
|
Self::Live {
|
||||||
|
meshes,
|
||||||
|
images,
|
||||||
|
text,
|
||||||
|
} => {
|
||||||
|
let images = if images.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(Arc::from(images))
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(mut previous) = previous {
|
if let Some(mut previous) = previous {
|
||||||
if let Some(cache) = &mut previous.meshes {
|
if let Some(cache) = &mut previous.meshes {
|
||||||
cache.update(meshes);
|
cache.update(meshes);
|
||||||
|
|
@ -59,10 +75,13 @@ impl Cached for Geometry {
|
||||||
previous.text = text::Cache::new(group, text);
|
previous.text = text::Cache::new(group, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
previous.images = images;
|
||||||
|
|
||||||
previous
|
previous
|
||||||
} else {
|
} else {
|
||||||
Cache {
|
Cache {
|
||||||
meshes: triangle::Cache::new(meshes),
|
meshes: triangle::Cache::new(meshes),
|
||||||
|
images,
|
||||||
text: text::Cache::new(group, text),
|
text: text::Cache::new(group, text),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -78,6 +97,7 @@ pub struct Frame {
|
||||||
clip_bounds: Rectangle,
|
clip_bounds: Rectangle,
|
||||||
buffers: BufferStack,
|
buffers: BufferStack,
|
||||||
meshes: Vec<Mesh>,
|
meshes: Vec<Mesh>,
|
||||||
|
images: Vec<Image>,
|
||||||
text: Vec<Text>,
|
text: Vec<Text>,
|
||||||
transforms: Transforms,
|
transforms: Transforms,
|
||||||
fill_tessellator: tessellation::FillTessellator,
|
fill_tessellator: tessellation::FillTessellator,
|
||||||
|
|
@ -96,6 +116,7 @@ impl Frame {
|
||||||
clip_bounds: bounds,
|
clip_bounds: bounds,
|
||||||
buffers: BufferStack::new(),
|
buffers: BufferStack::new(),
|
||||||
meshes: Vec::new(),
|
meshes: Vec::new(),
|
||||||
|
images: Vec::new(),
|
||||||
text: Vec::new(),
|
text: Vec::new(),
|
||||||
transforms: Transforms {
|
transforms: Transforms {
|
||||||
previous: Vec::new(),
|
previous: Vec::new(),
|
||||||
|
|
@ -270,7 +291,7 @@ impl geometry::frame::Backend for Frame {
|
||||||
height: f32::INFINITY,
|
height: f32::INFINITY,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.text.push(graphics::Text::Cached {
|
self.text.push(Text::Cached {
|
||||||
content: text.content,
|
content: text.content,
|
||||||
bounds,
|
bounds,
|
||||||
color: text.color,
|
color: text.color,
|
||||||
|
|
@ -335,10 +356,11 @@ impl geometry::frame::Backend for Frame {
|
||||||
Frame::with_clip(clip_bounds)
|
Frame::with_clip(clip_bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paste(&mut self, frame: Frame, _at: Point) {
|
fn paste(&mut self, frame: Frame) {
|
||||||
self.meshes
|
self.meshes
|
||||||
.extend(frame.buffers.into_meshes(frame.clip_bounds));
|
.extend(frame.buffers.into_meshes(frame.clip_bounds));
|
||||||
|
|
||||||
|
self.images.extend(frame.images);
|
||||||
self.text.extend(frame.text);
|
self.text.extend(frame.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -348,9 +370,32 @@ impl geometry::frame::Backend for Frame {
|
||||||
|
|
||||||
Geometry::Live {
|
Geometry::Live {
|
||||||
meshes: self.meshes,
|
meshes: self.meshes,
|
||||||
|
images: self.images,
|
||||||
text: self.text,
|
text: self.text,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_image(&mut self, bounds: Rectangle, image: impl Into<core::Image>) {
|
||||||
|
let mut image = image.into();
|
||||||
|
|
||||||
|
let (bounds, external_rotation) =
|
||||||
|
self.transforms.current.transform_rectangle(bounds);
|
||||||
|
|
||||||
|
image.rotation += external_rotation;
|
||||||
|
|
||||||
|
self.images.push(Image::Raster(image, bounds));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into<Svg>) {
|
||||||
|
let mut svg = svg.into();
|
||||||
|
|
||||||
|
let (bounds, external_rotation) =
|
||||||
|
self.transforms.current.transform_rectangle(bounds);
|
||||||
|
|
||||||
|
svg.rotation += external_rotation;
|
||||||
|
|
||||||
|
self.images.push(Image::Vector(svg, bounds));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Buffer {
|
enum Buffer {
|
||||||
|
|
@ -518,6 +563,21 @@ impl Transform {
|
||||||
|
|
||||||
gradient
|
gradient
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn transform_rectangle(
|
||||||
|
&self,
|
||||||
|
rectangle: Rectangle,
|
||||||
|
) -> (Rectangle, Radians) {
|
||||||
|
let top_left = self.transform_point(rectangle.position());
|
||||||
|
let top_right = self.transform_point(
|
||||||
|
rectangle.position() + Vector::new(rectangle.width, 0.0),
|
||||||
|
);
|
||||||
|
let bottom_left = self.transform_point(
|
||||||
|
rectangle.position() + Vector::new(0.0, rectangle.height),
|
||||||
|
);
|
||||||
|
|
||||||
|
Rectangle::with_vertices(top_left, top_right, bottom_left)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
struct GradientVertex2DBuilder {
|
struct GradientVertex2DBuilder {
|
||||||
gradient: gradient::Packed,
|
gradient: gradient::Packed,
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,8 @@ impl Pipeline {
|
||||||
6 => Float32x2,
|
6 => Float32x2,
|
||||||
// Layer
|
// Layer
|
||||||
7 => Sint32,
|
7 => Sint32,
|
||||||
|
// Snap
|
||||||
|
8 => Uint32,
|
||||||
),
|
),
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
|
|
@ -212,31 +214,24 @@ impl Pipeline {
|
||||||
transformation: Transformation,
|
transformation: Transformation,
|
||||||
scale: f32,
|
scale: f32,
|
||||||
) {
|
) {
|
||||||
let transformation = transformation * Transformation::scale(scale);
|
|
||||||
|
|
||||||
let nearest_instances: &mut Vec<Instance> = &mut Vec::new();
|
let nearest_instances: &mut Vec<Instance> = &mut Vec::new();
|
||||||
let linear_instances: &mut Vec<Instance> = &mut Vec::new();
|
let linear_instances: &mut Vec<Instance> = &mut Vec::new();
|
||||||
|
|
||||||
for image in images {
|
for image in images {
|
||||||
match &image {
|
match &image {
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
Image::Raster {
|
Image::Raster(image, bounds) => {
|
||||||
handle,
|
|
||||||
filter_method,
|
|
||||||
bounds,
|
|
||||||
rotation,
|
|
||||||
opacity,
|
|
||||||
} => {
|
|
||||||
if let Some(atlas_entry) =
|
if let Some(atlas_entry) =
|
||||||
cache.upload_raster(device, encoder, handle)
|
cache.upload_raster(device, encoder, &image.handle)
|
||||||
{
|
{
|
||||||
add_instances(
|
add_instances(
|
||||||
[bounds.x, bounds.y],
|
[bounds.x, bounds.y],
|
||||||
[bounds.width, bounds.height],
|
[bounds.width, bounds.height],
|
||||||
f32::from(*rotation),
|
f32::from(image.rotation),
|
||||||
*opacity,
|
image.opacity,
|
||||||
|
image.snap,
|
||||||
atlas_entry,
|
atlas_entry,
|
||||||
match filter_method {
|
match image.filter_method {
|
||||||
crate::core::image::FilterMethod::Nearest => {
|
crate::core::image::FilterMethod::Nearest => {
|
||||||
nearest_instances
|
nearest_instances
|
||||||
}
|
}
|
||||||
|
|
@ -251,23 +246,23 @@ impl Pipeline {
|
||||||
Image::Raster { .. } => {}
|
Image::Raster { .. } => {}
|
||||||
|
|
||||||
#[cfg(feature = "svg")]
|
#[cfg(feature = "svg")]
|
||||||
Image::Vector {
|
Image::Vector(svg, bounds) => {
|
||||||
handle,
|
|
||||||
color,
|
|
||||||
bounds,
|
|
||||||
rotation,
|
|
||||||
opacity,
|
|
||||||
} => {
|
|
||||||
let size = [bounds.width, bounds.height];
|
let size = [bounds.width, bounds.height];
|
||||||
|
|
||||||
if let Some(atlas_entry) = cache.upload_vector(
|
if let Some(atlas_entry) = cache.upload_vector(
|
||||||
device, encoder, handle, *color, size, scale,
|
device,
|
||||||
|
encoder,
|
||||||
|
&svg.handle,
|
||||||
|
svg.color,
|
||||||
|
size,
|
||||||
|
scale,
|
||||||
) {
|
) {
|
||||||
add_instances(
|
add_instances(
|
||||||
[bounds.x, bounds.y],
|
[bounds.x, bounds.y],
|
||||||
size,
|
size,
|
||||||
f32::from(*rotation),
|
f32::from(svg.rotation),
|
||||||
*opacity,
|
svg.opacity,
|
||||||
|
true,
|
||||||
atlas_entry,
|
atlas_entry,
|
||||||
nearest_instances,
|
nearest_instances,
|
||||||
);
|
);
|
||||||
|
|
@ -300,6 +295,7 @@ impl Pipeline {
|
||||||
nearest_instances,
|
nearest_instances,
|
||||||
linear_instances,
|
linear_instances,
|
||||||
transformation,
|
transformation,
|
||||||
|
scale,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.prepare_layer += 1;
|
self.prepare_layer += 1;
|
||||||
|
|
@ -375,9 +371,12 @@ impl Layer {
|
||||||
nearest_instances: &[Instance],
|
nearest_instances: &[Instance],
|
||||||
linear_instances: &[Instance],
|
linear_instances: &[Instance],
|
||||||
transformation: Transformation,
|
transformation: Transformation,
|
||||||
|
scale_factor: f32,
|
||||||
) {
|
) {
|
||||||
let uniforms = Uniforms {
|
let uniforms = Uniforms {
|
||||||
transform: transformation.into(),
|
transform: transformation.into(),
|
||||||
|
scale_factor,
|
||||||
|
_padding: [0.0; 3],
|
||||||
};
|
};
|
||||||
|
|
||||||
let bytes = bytemuck::bytes_of(&uniforms);
|
let bytes = bytemuck::bytes_of(&uniforms);
|
||||||
|
|
@ -492,6 +491,7 @@ struct Instance {
|
||||||
_position_in_atlas: [f32; 2],
|
_position_in_atlas: [f32; 2],
|
||||||
_size_in_atlas: [f32; 2],
|
_size_in_atlas: [f32; 2],
|
||||||
_layer: u32,
|
_layer: u32,
|
||||||
|
_snap: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Instance {
|
impl Instance {
|
||||||
|
|
@ -502,6 +502,10 @@ impl Instance {
|
||||||
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||||
struct Uniforms {
|
struct Uniforms {
|
||||||
transform: [f32; 16],
|
transform: [f32; 16],
|
||||||
|
scale_factor: f32,
|
||||||
|
// Uniforms must be aligned to their largest member,
|
||||||
|
// this uses a mat4x4<f32> which aligns to 16, so align to that
|
||||||
|
_padding: [f32; 3],
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_instances(
|
fn add_instances(
|
||||||
|
|
@ -509,6 +513,7 @@ fn add_instances(
|
||||||
image_size: [f32; 2],
|
image_size: [f32; 2],
|
||||||
rotation: f32,
|
rotation: f32,
|
||||||
opacity: f32,
|
opacity: f32,
|
||||||
|
snap: bool,
|
||||||
entry: &atlas::Entry,
|
entry: &atlas::Entry,
|
||||||
instances: &mut Vec<Instance>,
|
instances: &mut Vec<Instance>,
|
||||||
) {
|
) {
|
||||||
|
|
@ -525,6 +530,7 @@ fn add_instances(
|
||||||
image_size,
|
image_size,
|
||||||
rotation,
|
rotation,
|
||||||
opacity,
|
opacity,
|
||||||
|
snap,
|
||||||
allocation,
|
allocation,
|
||||||
instances,
|
instances,
|
||||||
);
|
);
|
||||||
|
|
@ -554,8 +560,8 @@ fn add_instances(
|
||||||
];
|
];
|
||||||
|
|
||||||
add_instance(
|
add_instance(
|
||||||
position, center, size, rotation, opacity, allocation,
|
position, center, size, rotation, opacity, snap,
|
||||||
instances,
|
allocation, instances,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -569,6 +575,7 @@ fn add_instance(
|
||||||
size: [f32; 2],
|
size: [f32; 2],
|
||||||
rotation: f32,
|
rotation: f32,
|
||||||
opacity: f32,
|
opacity: f32,
|
||||||
|
snap: bool,
|
||||||
allocation: &atlas::Allocation,
|
allocation: &atlas::Allocation,
|
||||||
instances: &mut Vec<Instance>,
|
instances: &mut Vec<Instance>,
|
||||||
) {
|
) {
|
||||||
|
|
@ -591,6 +598,7 @@ fn add_instance(
|
||||||
(height as f32 - 1.0) / atlas::SIZE as f32,
|
(height as f32 - 1.0) / atlas::SIZE as f32,
|
||||||
],
|
],
|
||||||
_layer: layer as u32,
|
_layer: layer as u32,
|
||||||
|
_snap: snap as u32,
|
||||||
};
|
};
|
||||||
|
|
||||||
instances.push(instance);
|
instances.push(instance);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
renderer, Background, Color, Point, Radians, Rectangle, Transformation,
|
self, renderer, Background, Color, Point, Rectangle, Svg, Transformation,
|
||||||
};
|
};
|
||||||
use crate::graphics;
|
use crate::graphics;
|
||||||
use crate::graphics::color;
|
use crate::graphics::color;
|
||||||
|
|
@ -20,8 +20,8 @@ pub struct Layer {
|
||||||
pub quads: quad::Batch,
|
pub quads: quad::Batch,
|
||||||
pub triangles: triangle::Batch,
|
pub triangles: triangle::Batch,
|
||||||
pub primitives: primitive::Batch,
|
pub primitives: primitive::Batch,
|
||||||
pub text: text::Batch,
|
|
||||||
pub images: image::Batch,
|
pub images: image::Batch,
|
||||||
|
pub text: text::Batch,
|
||||||
pending_meshes: Vec<Mesh>,
|
pending_meshes: Vec<Mesh>,
|
||||||
pending_text: Vec<Text>,
|
pending_text: Vec<Text>,
|
||||||
}
|
}
|
||||||
|
|
@ -112,42 +112,35 @@ impl Layer {
|
||||||
self.pending_text.push(text);
|
self.pending_text.push(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_image(
|
pub fn draw_image(&mut self, image: Image, transformation: Transformation) {
|
||||||
|
match image {
|
||||||
|
Image::Raster(image, bounds) => {
|
||||||
|
self.draw_raster(image, bounds, transformation);
|
||||||
|
}
|
||||||
|
Image::Vector(svg, bounds) => {
|
||||||
|
self.draw_svg(svg, bounds, transformation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_raster(
|
||||||
&mut self,
|
&mut self,
|
||||||
handle: crate::core::image::Handle,
|
image: core::Image,
|
||||||
filter_method: crate::core::image::FilterMethod,
|
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
transformation: Transformation,
|
transformation: Transformation,
|
||||||
rotation: Radians,
|
|
||||||
opacity: f32,
|
|
||||||
) {
|
) {
|
||||||
let image = Image::Raster {
|
let image = Image::Raster(image, bounds * transformation);
|
||||||
handle,
|
|
||||||
filter_method,
|
|
||||||
bounds: bounds * transformation,
|
|
||||||
rotation,
|
|
||||||
opacity,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.images.push(image);
|
self.images.push(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_svg(
|
pub fn draw_svg(
|
||||||
&mut self,
|
&mut self,
|
||||||
handle: crate::core::svg::Handle,
|
svg: Svg,
|
||||||
color: Option<Color>,
|
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
transformation: Transformation,
|
transformation: Transformation,
|
||||||
rotation: Radians,
|
|
||||||
opacity: f32,
|
|
||||||
) {
|
) {
|
||||||
let svg = Image::Vector {
|
let svg = Image::Vector(svg, bounds * transformation);
|
||||||
handle,
|
|
||||||
color,
|
|
||||||
bounds: bounds * transformation,
|
|
||||||
rotation,
|
|
||||||
opacity,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.images.push(svg);
|
self.images.push(svg);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
101
wgpu/src/lib.rs
101
wgpu/src/lib.rs
|
|
@ -182,19 +182,6 @@ impl Renderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !layer.text.is_empty() {
|
|
||||||
engine.text_pipeline.prepare(
|
|
||||||
device,
|
|
||||||
queue,
|
|
||||||
&self.text_viewport,
|
|
||||||
encoder,
|
|
||||||
&mut self.text_storage,
|
|
||||||
&layer.text,
|
|
||||||
layer.bounds,
|
|
||||||
Transformation::scale(scale_factor),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(feature = "svg", feature = "image"))]
|
#[cfg(any(feature = "svg", feature = "image"))]
|
||||||
if !layer.images.is_empty() {
|
if !layer.images.is_empty() {
|
||||||
engine.image_pipeline.prepare(
|
engine.image_pipeline.prepare(
|
||||||
|
|
@ -207,6 +194,19 @@ impl Renderer {
|
||||||
scale_factor,
|
scale_factor,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !layer.text.is_empty() {
|
||||||
|
engine.text_pipeline.prepare(
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
&self.text_viewport,
|
||||||
|
encoder,
|
||||||
|
&mut self.text_storage,
|
||||||
|
&layer.text,
|
||||||
|
layer.bounds,
|
||||||
|
Transformation::scale(scale_factor),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -359,17 +359,6 @@ impl Renderer {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !layer.text.is_empty() {
|
|
||||||
text_layer += engine.text_pipeline.render(
|
|
||||||
&self.text_viewport,
|
|
||||||
&self.text_storage,
|
|
||||||
text_layer,
|
|
||||||
&layer.text,
|
|
||||||
scissor_rect,
|
|
||||||
&mut render_pass,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(feature = "svg", feature = "image"))]
|
#[cfg(any(feature = "svg", feature = "image"))]
|
||||||
if !layer.images.is_empty() {
|
if !layer.images.is_empty() {
|
||||||
engine.image_pipeline.render(
|
engine.image_pipeline.render(
|
||||||
|
|
@ -381,6 +370,17 @@ impl Renderer {
|
||||||
|
|
||||||
image_layer += 1;
|
image_layer += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !layer.text.is_empty() {
|
||||||
|
text_layer += engine.text_pipeline.render(
|
||||||
|
&self.text_viewport,
|
||||||
|
&self.text_storage,
|
||||||
|
text_layer,
|
||||||
|
&layer.text,
|
||||||
|
scissor_rect,
|
||||||
|
&mut render_pass,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = ManuallyDrop::into_inner(render_pass);
|
let _ = ManuallyDrop::into_inner(render_pass);
|
||||||
|
|
@ -527,23 +527,9 @@ impl core::image::Renderer for Renderer {
|
||||||
self.image_cache.borrow_mut().measure_image(handle)
|
self.image_cache.borrow_mut().measure_image(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_image(
|
fn draw_image(&mut self, image: core::Image, bounds: Rectangle) {
|
||||||
&mut self,
|
|
||||||
handle: Self::Handle,
|
|
||||||
filter_method: core::image::FilterMethod,
|
|
||||||
bounds: Rectangle,
|
|
||||||
rotation: core::Radians,
|
|
||||||
opacity: f32,
|
|
||||||
) {
|
|
||||||
let (layer, transformation) = self.layers.current_mut();
|
let (layer, transformation) = self.layers.current_mut();
|
||||||
layer.draw_image(
|
layer.draw_raster(image, bounds, transformation);
|
||||||
handle,
|
|
||||||
filter_method,
|
|
||||||
bounds,
|
|
||||||
transformation,
|
|
||||||
rotation,
|
|
||||||
opacity,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -553,23 +539,9 @@ impl core::svg::Renderer for Renderer {
|
||||||
self.image_cache.borrow_mut().measure_svg(handle)
|
self.image_cache.borrow_mut().measure_svg(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_svg(
|
fn draw_svg(&mut self, svg: core::Svg, bounds: Rectangle) {
|
||||||
&mut self,
|
|
||||||
handle: core::svg::Handle,
|
|
||||||
color_filter: Option<Color>,
|
|
||||||
bounds: Rectangle,
|
|
||||||
rotation: core::Radians,
|
|
||||||
opacity: f32,
|
|
||||||
) {
|
|
||||||
let (layer, transformation) = self.layers.current_mut();
|
let (layer, transformation) = self.layers.current_mut();
|
||||||
layer.draw_svg(
|
layer.draw_svg(svg, bounds, transformation);
|
||||||
handle,
|
|
||||||
color_filter,
|
|
||||||
bounds,
|
|
||||||
transformation,
|
|
||||||
rotation,
|
|
||||||
opacity,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -593,8 +565,17 @@ impl graphics::geometry::Renderer for Renderer {
|
||||||
let (layer, transformation) = self.layers.current_mut();
|
let (layer, transformation) = self.layers.current_mut();
|
||||||
|
|
||||||
match geometry {
|
match geometry {
|
||||||
Geometry::Live { meshes, text } => {
|
Geometry::Live {
|
||||||
|
meshes,
|
||||||
|
images,
|
||||||
|
text,
|
||||||
|
} => {
|
||||||
layer.draw_mesh_group(meshes, transformation);
|
layer.draw_mesh_group(meshes, transformation);
|
||||||
|
|
||||||
|
for image in images {
|
||||||
|
layer.draw_image(image, transformation);
|
||||||
|
}
|
||||||
|
|
||||||
layer.draw_text_group(text, transformation);
|
layer.draw_text_group(text, transformation);
|
||||||
}
|
}
|
||||||
Geometry::Cached(cache) => {
|
Geometry::Cached(cache) => {
|
||||||
|
|
@ -602,6 +583,12 @@ impl graphics::geometry::Renderer for Renderer {
|
||||||
layer.draw_mesh_cache(meshes, transformation);
|
layer.draw_mesh_cache(meshes, transformation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(images) = cache.images {
|
||||||
|
for image in images.iter().cloned() {
|
||||||
|
layer.draw_image(image, transformation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(text) = cache.text {
|
if let Some(text) = cache.text {
|
||||||
layer.draw_text_cache(text, transformation);
|
layer.draw_text_cache(text, transformation);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
struct Globals {
|
struct Globals {
|
||||||
transform: mat4x4<f32>,
|
transform: mat4x4<f32>,
|
||||||
|
scale_factor: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@group(0) @binding(0) var<uniform> globals: Globals;
|
@group(0) @binding(0) var<uniform> globals: Globals;
|
||||||
|
|
@ -16,6 +17,7 @@ struct VertexInput {
|
||||||
@location(5) atlas_pos: vec2<f32>,
|
@location(5) atlas_pos: vec2<f32>,
|
||||||
@location(6) atlas_scale: vec2<f32>,
|
@location(6) atlas_scale: vec2<f32>,
|
||||||
@location(7) layer: i32,
|
@location(7) layer: i32,
|
||||||
|
@location(8) snap: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct VertexOutput {
|
struct VertexOutput {
|
||||||
|
|
@ -38,7 +40,7 @@ fn vs_main(input: VertexInput) -> VertexOutput {
|
||||||
out.opacity = input.opacity;
|
out.opacity = input.opacity;
|
||||||
|
|
||||||
// Calculate the vertex position and move the center to the origin
|
// Calculate the vertex position and move the center to the origin
|
||||||
v_pos = round(input.pos) + v_pos * input.scale - input.center;
|
v_pos = input.pos + v_pos * input.scale - input.center;
|
||||||
|
|
||||||
// Apply the rotation around the center of the image
|
// Apply the rotation around the center of the image
|
||||||
let cos_rot = cos(input.rotation);
|
let cos_rot = cos(input.rotation);
|
||||||
|
|
@ -51,7 +53,13 @@ fn vs_main(input: VertexInput) -> VertexOutput {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Calculate the final position of the vertex
|
// Calculate the final position of the vertex
|
||||||
out.position = globals.transform * (vec4<f32>(input.center, 0.0, 0.0) + rotate * vec4<f32>(v_pos, 0.0, 1.0));
|
out.position = vec4(vec2(globals.scale_factor), 1.0, 1.0) * (vec4<f32>(input.center, 0.0, 0.0) + rotate * vec4<f32>(v_pos, 0.0, 1.0));
|
||||||
|
|
||||||
|
if bool(input.snap) {
|
||||||
|
out.position = round(out.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
out.position = globals.transform * out.position;
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ pub use program::Program;
|
||||||
|
|
||||||
pub use crate::graphics::cache::Group;
|
pub use crate::graphics::cache::Group;
|
||||||
pub use crate::graphics::geometry::{
|
pub use crate::graphics::geometry::{
|
||||||
fill, gradient, path, stroke, Fill, Gradient, LineCap, LineDash, LineJoin,
|
fill, gradient, path, stroke, Fill, Gradient, Image, LineCap, LineDash,
|
||||||
Path, Stroke, Style, Text,
|
LineJoin, Path, Stroke, Style, Text,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::core;
|
use crate::core;
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ pub struct Image<Handle> {
|
||||||
|
|
||||||
impl<Handle> Image<Handle> {
|
impl<Handle> Image<Handle> {
|
||||||
/// Creates a new [`Image`] with the given path.
|
/// Creates a new [`Image`] with the given path.
|
||||||
pub fn new<T: Into<Handle>>(handle: T) -> Self {
|
pub fn new(handle: impl Into<Handle>) -> Self {
|
||||||
Image {
|
Image {
|
||||||
handle: handle.into(),
|
handle: handle.into(),
|
||||||
width: Length::Shrink,
|
width: Length::Shrink,
|
||||||
|
|
@ -181,11 +181,14 @@ pub fn draw<Renderer, Handle>(
|
||||||
|
|
||||||
let render = |renderer: &mut Renderer| {
|
let render = |renderer: &mut Renderer| {
|
||||||
renderer.draw_image(
|
renderer.draw_image(
|
||||||
handle.clone(),
|
image::Image {
|
||||||
filter_method,
|
handle: handle.clone(),
|
||||||
|
filter_method,
|
||||||
|
rotation: rotation.radians(),
|
||||||
|
opacity,
|
||||||
|
snap: true,
|
||||||
|
},
|
||||||
drawing_bounds,
|
drawing_bounds,
|
||||||
rotation.radians(),
|
|
||||||
opacity,
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ use crate::core::mouse;
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
use crate::core::widget::tree::{self, Tree};
|
use crate::core::widget::tree::{self, Tree};
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
Clipboard, ContentFit, Element, Layout, Length, Pixels, Point, Radians,
|
Clipboard, ContentFit, Element, Image, Layout, Length, Pixels, Point,
|
||||||
Rectangle, Shell, Size, Vector, Widget,
|
Radians, Rectangle, Shell, Size, Vector, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A frame that displays an image with the ability to zoom in/out and pan.
|
/// A frame that displays an image with the ability to zoom in/out and pan.
|
||||||
|
|
@ -349,11 +349,14 @@ where
|
||||||
let render = |renderer: &mut Renderer| {
|
let render = |renderer: &mut Renderer| {
|
||||||
renderer.with_translation(translation, |renderer| {
|
renderer.with_translation(translation, |renderer| {
|
||||||
renderer.draw_image(
|
renderer.draw_image(
|
||||||
self.handle.clone(),
|
Image {
|
||||||
self.filter_method,
|
handle: self.handle.clone(),
|
||||||
|
filter_method: self.filter_method,
|
||||||
|
rotation: Radians(0.0),
|
||||||
|
opacity: 1.0,
|
||||||
|
snap: true,
|
||||||
|
},
|
||||||
drawing_bounds,
|
drawing_bounds,
|
||||||
Radians(0.0),
|
|
||||||
1.0,
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -211,11 +211,13 @@ where
|
||||||
|
|
||||||
let render = |renderer: &mut Renderer| {
|
let render = |renderer: &mut Renderer| {
|
||||||
renderer.draw_svg(
|
renderer.draw_svg(
|
||||||
self.handle.clone(),
|
svg::Svg {
|
||||||
style.color,
|
handle: self.handle.clone(),
|
||||||
|
color: style.color,
|
||||||
|
rotation: self.rotation.radians(),
|
||||||
|
opacity: self.opacity,
|
||||||
|
},
|
||||||
drawing_bounds,
|
drawing_bounds,
|
||||||
self.rotation.radians(),
|
|
||||||
self.opacity,
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue