Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Svg and icon support #111

Merged
merged 11 commits into from
Dec 16, 2019
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ categories = ["gui"]
[features]
# Enables a debug view in native platforms (press F12)
debug = ["iced_winit/debug"]
# Enables support for SVG rendering
svg = ["iced_wgpu/svg"]

[badges]
maintenance = { status = "actively-developed" }
Expand Down
725 changes: 725 additions & 0 deletions examples/resources/tiger.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
54 changes: 54 additions & 0 deletions examples/svg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use iced::{Container, Element, Length, Sandbox, Settings};

pub fn main() {
Tiger::run(Settings::default())
}

#[derive(Default)]
struct Tiger;

impl Sandbox for Tiger {
type Message = ();

fn new() -> Self {
Self::default()
}

fn title(&self) -> String {
String::from("SVG - Iced")
}

fn update(&mut self, _message: ()) {}

fn view(&mut self) -> Element<()> {
#[cfg(feature = "svg")]
let content = {
use iced::{Column, Svg};

Column::new()
.width(Length::Shrink)
.padding(20)
.push(Svg::new(format!(
"{}/examples/resources/tiger.svg",
env!("CARGO_MANIFEST_DIR")
)))
};

#[cfg(not(feature = "svg"))]
let content = {
use iced::{HorizontalAlignment, Text};

Text::new("You need to enable the `svg` feature!")
.width(Length::Shrink)
.horizontal_alignment(HorizontalAlignment::Center)
.size(30)
};

Container::new(content)
.width(Length::Fill)
.height(Length::Fill)
.center_x()
.center_y()
.into()
}
}
3 changes: 3 additions & 0 deletions native/src/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub mod button;
pub mod checkbox;
pub mod column;
pub mod container;
pub mod svg;
pub mod image;
pub mod radio;
pub mod row;
Expand Down Expand Up @@ -51,6 +52,8 @@ pub use scrollable::Scrollable;
#[doc(no_inline)]
pub use slider::Slider;
#[doc(no_inline)]
pub use svg::Svg;
#[doc(no_inline)]
pub use text::Text;
#[doc(no_inline)]
pub use text_input::TextInput;
Expand Down
187 changes: 187 additions & 0 deletions native/src/widget/svg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
//! Display vector graphics in your application.
use crate::{layout, Element, Hasher, Layout, Length, Point, Size, Widget};

use std::{
hash::Hash,
path::{Path, PathBuf},
};

/// A vector graphics image.
///
/// An [`Svg`] image resizes smoothly without losing any quality.
///
/// [`Svg`] images can have a considerable rendering cost when resized,
/// specially when they are complex.
///
/// [`Svg`]: struct.Svg.html
#[derive(Debug, Clone)]
pub struct Svg {
handle: Handle,
width: Length,
height: Length,
}

impl Svg {
/// Creates a new [`Svg`] from the given [`Handle`].
///
/// [`Svg`]: struct.Svg.html
/// [`Handle`]: struct.Handle.html
pub fn new(handle: impl Into<Handle>) -> Self {
Svg {
handle: handle.into(),
width: Length::Fill,
height: Length::Fill,
}
}

/// Sets the width of the [`Svg`].
///
/// [`Svg`]: struct.Svg.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}

/// Sets the height of the [`Svg`].
///
/// [`Svg`]: struct.Svg.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
}

impl<Message, Renderer> Widget<Message, Renderer> for Svg
where
Renderer: self::Renderer,
{
fn width(&self) -> Length {
self.width
}

fn height(&self) -> Length {
self.height
}

fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let (width, height) = renderer.dimensions(&self.handle);

let aspect_ratio = width as f32 / height as f32;

let mut size = limits
.width(self.width)
.height(self.height)
.resolve(Size::new(width as f32, height as f32));

let viewport_aspect_ratio = size.width / size.height;

if viewport_aspect_ratio > aspect_ratio {
size.width = width as f32 * size.height / height as f32;
} else {
size.height = height as f32 * size.width / width as f32;
}

layout::Node::new(size)
}

fn draw(
&self,
renderer: &mut Renderer,
layout: Layout<'_>,
_cursor_position: Point,
) -> Renderer::Output {
renderer.draw(self.handle.clone(), layout)
}

fn hash_layout(&self, state: &mut Hasher) {
self.width.hash(state);
self.height.hash(state);
}
}

/// An [`Svg`] handle.
///
/// [`Svg`]: struct.Svg.html
#[derive(Debug, Clone)]
pub struct Handle {
id: u64,
path: PathBuf,
}

impl Handle {
/// Creates an SVG [`Handle`] pointing to the vector image of the given
/// path.
///
/// [`Handle`]: struct.Handle.html
pub fn from_path<T: Into<PathBuf>>(path: T) -> Handle {
use std::hash::Hasher as _;

let path = path.into();

let mut hasher = Hasher::default();
path.hash(&mut hasher);

Handle {
id: hasher.finish(),
path,
}
}

/// Returns the unique identifier of the [`Handle`].
///
/// [`Handle`]: struct.Handle.html
pub fn id(&self) -> u64 {
self.id
}

/// Returns a reference to the path of the [`Handle`].
///
/// [`Handle`]: enum.Handle.html
pub fn path(&self) -> &Path {
&self.path
}
}

impl From<String> for Handle {
fn from(path: String) -> Handle {
Handle::from_path(path)
}
}

impl From<&str> for Handle {
fn from(path: &str) -> Handle {
Handle::from_path(path)
}
}

/// The renderer of an [`Svg`].
///
/// Your [renderer] will need to implement this trait before being able to use
/// an [`Svg`] in your user interface.
///
/// [`Svg`]: struct.Svg.html
/// [renderer]: ../../renderer/index.html
pub trait Renderer: crate::Renderer {
/// Returns the default dimensions of an [`Svg`] located on the given path.
///
/// [`Svg`]: struct.Svg.html
fn dimensions(&self, handle: &Handle) -> (u32, u32);

/// Draws an [`Svg`].
///
/// [`Svg`]: struct.Svg.html
fn draw(&mut self, handle: Handle, layout: Layout<'_>) -> Self::Output;
}

impl<'a, Message, Renderer> From<Svg> for Element<'a, Message, Renderer>
where
Renderer: self::Renderer,
{
fn from(icon: Svg) -> Element<'a, Message, Renderer> {
Element::new(icon)
}
}
7 changes: 6 additions & 1 deletion src/native.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,17 @@ pub mod widget {
pub use iced_winit::image::{Handle, Image};
}

pub mod svg {
//! Display vector graphics in your user interface.
pub use iced_winit::svg::{Handle, Svg};
}

pub use iced_winit::{Checkbox, Radio, Text};

#[doc(no_inline)]
pub use {
button::Button, image::Image, scrollable::Scrollable, slider::Slider,
text_input::TextInput,
svg::Svg, text_input::TextInput,
};

/// A container that distributes its contents vertically.
Expand Down
4 changes: 4 additions & 0 deletions wgpu/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ description = "A wgpu renderer for Iced"
license = "MIT AND OFL-1.1"
repository = "https://github.com/hecrj/iced"

[features]
svg = ["resvg"]

[dependencies]
iced_native = { version = "0.1.0", path = "../native" }
wgpu = "0.4"
Expand All @@ -17,3 +20,4 @@ image = "0.22"
glam = "0.8"
font-kit = "0.4"
log = "0.4"
resvg = { version = "0.8", features = ["raqote-backend"], optional = true }
Loading