use std::{fs, path::Path, sync::Arc}; use crate::{ App, Asset, Bounds, Element, GlobalElementId, Hitbox, InspectorElementId, InteractiveElement, Interactivity, IntoElement, LayoutId, Pixels, Point, Radians, SharedString, Size, StyleRefinement, Styled, TransformationMatrix, Window, point, px, radians, size,};use gpui_util::ResultExt; /// An SVG element.pub struct Svg { interactivity: Interactivity, transformation: Option<Transformation>, path: Option<SharedString>, external_path: Option<SharedString>,} /// Create a new SVG element.#[track_caller]pub fn svg() -> Svg { Svg { interactivity: Interactivity::new(), transformation: None, path: None, external_path: None, }} impl Svg { /// Set the path to the SVG file for this element. pub fn path(mut self, path: impl Into<SharedString>) -> Self { self.path = Some(path.into()); self } /// Set the path to the SVG file for this element. pub fn external_path(mut self, path: impl Into<SharedString>) -> Self { self.external_path = Some(path.into()); self } /// Transform the SVG element with the given transformation. /// Note that this won't effect the hitbox or layout of the element, only the rendering. pub fn with_transformation(mut self, transformation: Transformation) -> Self { self.transformation = Some(transformation); self }} impl Element for Svg { type RequestLayoutState = (); type PrepaintState = Option<Hitbox>; fn id(&self) -> Option<crate::ElementId> { self.interactivity.element_id.clone() } fn source_location(&self) -> Option<&'static std::panic::Location<'static>> { self.interactivity.source_location() } fn request_layout( &mut self, global_id: Option<&GlobalElementId>, inspector_id: Option<&InspectorElementId>, window: &mut Window, cx: &mut App, ) -> (LayoutId, Self::RequestLayoutState) { let layout_id = self.interactivity.request_layout( global_id, inspector_id, window, cx, |style, window, cx| window.request_layout(style, None, cx), ); (layout_id, ()) } fn prepaint( &mut self, global_id: Option<&GlobalElementId>, inspector_id: Option<&InspectorElementId>, bounds: Bounds<Pixels>, _request_layout: &mut Self::RequestLayoutState, window: &mut Window, cx: &mut App, ) -> Option<Hitbox> { self.interactivity.prepaint( global_id, inspector_id, bounds, bounds.size, window, cx, |_, _, hitbox, _, _| hitbox, ) } fn paint( &mut self, global_id: Option<&GlobalElementId>, inspector_id: Option<&InspectorElementId>, bounds: Bounds<Pixels>, _request_layout: &mut Self::RequestLayoutState, hitbox: &mut Option<Hitbox>, window: &mut Window, cx: &mut App, ) where Self: Sized, { self.interactivity.paint( global_id, inspector_id, bounds, hitbox.as_ref(), window, cx, |style, window, cx| { if let Some((path, color)) = self.path.as_ref().zip(style.text.color) { let transformation = self .transformation .as_ref() .map(|transformation| { transformation.into_matrix(bounds.center(), window.scale_factor()) }) .unwrap_or_default(); window .paint_svg(bounds, path.clone(), None, transformation, color, cx) .log_err(); } else if let Some((path, color)) = self.external_path.as_ref().zip(style.text.color) { let Some(bytes) = window .use_asset::<SvgAsset>(path, cx) .and_then(|asset| asset.log_err()) else { return; }; let transformation = self .transformation .as_ref() .map(|transformation| { transformation.into_matrix(bounds.center(), window.scale_factor()) }) .unwrap_or_default(); window .paint_svg( bounds, path.clone(), Some(&bytes), transformation, color, cx, ) .log_err(); } }, ) }} impl IntoElement for Svg { type Element = Self; fn into_element(self) -> Self::Element { self }} impl Styled for Svg { fn style(&mut self) -> &mut StyleRefinement { &mut self.interactivity.base_style }} impl InteractiveElement for Svg { fn interactivity(&mut self) -> &mut Interactivity { &mut self.interactivity }} /// A transformation to apply to an SVG element.#[derive(Clone, Copy, Debug, PartialEq)]pub struct Transformation { scale: Size<f32>, translate: Point<Pixels>, rotate: Radians,} impl Default for Transformation { fn default() -> Self { Self { scale: size(1.0, 1.0), translate: point(px(0.0), px(0.0)), rotate: radians(0.0), } }} impl Transformation { /// Create a new Transformation with the specified scale along each axis. pub fn scale(scale: Size<f32>) -> Self { Self { scale, translate: point(px(0.0), px(0.0)), rotate: radians(0.0), } } /// Create a new Transformation with the specified translation. pub fn translate(translate: Point<Pixels>) -> Self { Self { scale: size(1.0, 1.0), translate, rotate: radians(0.0), } } /// Create a new Transformation with the specified rotation in radians. pub fn rotate(rotate: impl Into<Radians>) -> Self { let rotate = rotate.into(); Self { scale: size(1.0, 1.0), translate: point(px(0.0), px(0.0)), rotate, } } /// Update the scaling factor of this transformation. pub fn with_scaling(mut self, scale: Size<f32>) -> Self { self.scale = scale; self } /// Update the translation value of this transformation. pub fn with_translation(mut self, translate: Point<Pixels>) -> Self { self.translate = translate; self } /// Update the rotation angle of this transformation. pub fn with_rotation(mut self, rotate: impl Into<Radians>) -> Self { self.rotate = rotate.into(); self } fn into_matrix(self, center: Point<Pixels>, scale_factor: f32) -> TransformationMatrix { //Note: if you read this as a sequence of matrix multiplications, start from the bottom TransformationMatrix::unit() .translate(center.scale(scale_factor) + self.translate.scale(scale_factor)) .rotate(self.rotate) .scale(self.scale) .translate(center.scale(-scale_factor)) }} enum SvgAsset {} impl Asset for SvgAsset { type Source = SharedString; type Output = Result<Arc<[u8]>, Arc<std::io::Error>>; fn load( source: Self::Source, _cx: &mut App, ) -> impl Future<Output = Self::Output> + Send + 'static { async move { let bytes = fs::read(Path::new(source.as_ref())).map_err(|e| Arc::new(e))?; let bytes = Arc::from(bytes); Ok(bytes) } }}
use std::{fs, path::Path, sync::Arc};
use crate::{
App, Asset, Bounds, Element, GlobalElementId, Hitbox, InspectorElementId, InteractiveElement,
Interactivity, IntoElement, LayoutId, Pixels, Point, Radians, SharedString, Size,
StyleRefinement, Styled, TransformationMatrix, Window, point, px, radians, size,
};
use gpui_util::ResultExt;
/// An SVG element.
pub struct Svg {
interactivity: Interactivity,
transformation: Option<Transformation>,
path: Option<SharedString>,
external_path: Option<SharedString>,
}
/// Create a new SVG element.
#[track_caller]
pub fn svg() -> Svg {
Svg {
interactivity: Interactivity::new(),
transformation: None,
path: None,
external_path: None,
}
}
impl Svg {
/// Set the path to the SVG file for this element.
pub fn path(mut self, path: impl Into<SharedString>) -> Self {
self.path = Some(path.into());
self
}
/// Set the path to the SVG file for this element.
pub fn external_path(mut self, path: impl Into<SharedString>) -> Self {
self.external_path = Some(path.into());
self
}
/// Transform the SVG element with the given transformation.
/// Note that this won't effect the hitbox or layout of the element, only the rendering.
pub fn with_transformation(mut self, transformation: Transformation) -> Self {
self.transformation = Some(transformation);
self
}
}
impl Element for Svg {
type RequestLayoutState = ();
type PrepaintState = Option<Hitbox>;
fn id(&self) -> Option<crate::ElementId> {
self.interactivity.element_id.clone()
}
fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
self.interactivity.source_location()
}
fn request_layout(
&mut self,
global_id: Option<&GlobalElementId>,
inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
let layout_id = self.interactivity.request_layout(
global_id,
inspector_id,
window,
cx,
|style, window, cx| window.request_layout(style, None, cx),
);
(layout_id, ())
}
fn prepaint(
&mut self,
global_id: Option<&GlobalElementId>,
inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
window: &mut Window,
cx: &mut App,
) -> Option<Hitbox> {
self.interactivity.prepaint(
global_id,
inspector_id,
bounds,
bounds.size,
window,
cx,
|_, _, hitbox, _, _| hitbox,
)
}
fn paint(
&mut self,
global_id: Option<&GlobalElementId>,
inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
hitbox: &mut Option<Hitbox>,
window: &mut Window,
cx: &mut App,
) where
Self: Sized,
{
self.interactivity.paint(
global_id,
inspector_id,
bounds,
hitbox.as_ref(),
window,
cx,
|style, window, cx| {
if let Some((path, color)) = self.path.as_ref().zip(style.text.color) {
let transformation = self
.transformation
.as_ref()
.map(|transformation| {
transformation.into_matrix(bounds.center(), window.scale_factor())
})
.unwrap_or_default();
window
.paint_svg(bounds, path.clone(), None, transformation, color, cx)
.log_err();
} else if let Some((path, color)) =
self.external_path.as_ref().zip(style.text.color)
{
let Some(bytes) = window
.use_asset::<SvgAsset>(path, cx)
.and_then(|asset| asset.log_err())
else {
return;
};
let transformation = self
.transformation
.as_ref()
.map(|transformation| {
transformation.into_matrix(bounds.center(), window.scale_factor())
})
.unwrap_or_default();
window
.paint_svg(
bounds,
path.clone(),
Some(&bytes),
transformation,
color,
cx,
)
.log_err();
}
},
)
}
}
impl IntoElement for Svg {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}
impl Styled for Svg {
fn style(&mut self) -> &mut StyleRefinement {
&mut self.interactivity.base_style
}
}
impl InteractiveElement for Svg {
fn interactivity(&mut self) -> &mut Interactivity {
&mut self.interactivity
}
}
/// A transformation to apply to an SVG element.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Transformation {
scale: Size<f32>,
translate: Point<Pixels>,
rotate: Radians,
}
impl Default for Transformation {
fn default() -> Self {
Self {
scale: size(1.0, 1.0),
translate: point(px(0.0), px(0.0)),
rotate: radians(0.0),
}
}
}
impl Transformation {
/// Create a new Transformation with the specified scale along each axis.
pub fn scale(scale: Size<f32>) -> Self {
Self {
scale,
translate: point(px(0.0), px(0.0)),
rotate: radians(0.0),
}
}
/// Create a new Transformation with the specified translation.
pub fn translate(translate: Point<Pixels>) -> Self {
Self {
scale: size(1.0, 1.0),
translate,
rotate: radians(0.0),
}
}
/// Create a new Transformation with the specified rotation in radians.
pub fn rotate(rotate: impl Into<Radians>) -> Self {
let rotate = rotate.into();
Self {
scale: size(1.0, 1.0),
translate: point(px(0.0), px(0.0)),
rotate,
}
}
/// Update the scaling factor of this transformation.
pub fn with_scaling(mut self, scale: Size<f32>) -> Self {
self.scale = scale;
self
}
/// Update the translation value of this transformation.
pub fn with_translation(mut self, translate: Point<Pixels>) -> Self {
self.translate = translate;
self
}
/// Update the rotation angle of this transformation.
pub fn with_rotation(mut self, rotate: impl Into<Radians>) -> Self {
self.rotate = rotate.into();
self
}
fn into_matrix(self, center: Point<Pixels>, scale_factor: f32) -> TransformationMatrix {
//Note: if you read this as a sequence of matrix multiplications, start from the bottom
TransformationMatrix::unit()
.translate(center.scale(scale_factor) + self.translate.scale(scale_factor))
.rotate(self.rotate)
.scale(self.scale)
.translate(center.scale(-scale_factor))
}
}
enum SvgAsset {}
impl Asset for SvgAsset {
type Source = SharedString;
type Output = Result<Arc<[u8]>, Arc<std::io::Error>>;
fn load(
source: Self::Source,
_cx: &mut App,
) -> impl Future<Output = Self::Output> + Send + 'static {
async move {
let bytes = fs::read(Path::new(source.as_ref())).map_err(|e| Arc::new(e))?;
let bytes = Arc::from(bytes);
Ok(bytes)
}
}
}