Skip to main content

ui/components/
image.rs

1use std::sync::Arc;
2
3use gpui::Transformation;
4use gpui::{App, IntoElement, Rems, RenderOnce, Size, Styled, Window, svg};
5use serde::{Deserialize, Serialize};
6use strum::{EnumIter, EnumString, IntoStaticStr};
7
8use crate::prelude::*;
9use crate::traits::transformable::Transformable;
10
11#[derive(
12    Debug, PartialEq, Eq, Copy, Clone, EnumIter, EnumString, IntoStaticStr, Serialize, Deserialize,
13)]
14#[strum(serialize_all = "snake_case")]
15pub enum VectorName {
16    BusinessStamp,
17    Grid,
18    ProTrialStamp,
19    ProUserStamp,
20    StudentStamp,
21    AppLogo,
22    AppXCopilot,
23}
24
25impl VectorName {
26    /// Returns the path to this vector image.
27    pub fn path(&self) -> Arc<str> {
28        let file_stem: &'static str = self.into();
29        format!("images/{file_stem}.svg").into()
30    }
31}
32
33/// A vector image, such as an SVG.
34///
35/// A [`Vector`] is different from an [`crate::Icon`] in that it is intended
36/// to be displayed at a specific size, or series of sizes, rather
37/// than conforming to the standard size of an icon.
38#[derive(IntoElement, RegisterComponent)]
39pub struct Vector {
40    path: Arc<str>,
41    color: Color,
42    size: Size<Rems>,
43    transformation: Transformation,
44}
45
46impl Vector {
47    /// Creates a new [`Vector`] image with the given [`VectorName`] and size.
48    pub fn new(vector: VectorName, width: Rems, height: Rems) -> Self {
49        Self {
50            path: vector.path(),
51            color: Color::default(),
52            size: Size { width, height },
53            transformation: Transformation::default(),
54        }
55    }
56
57    /// Creates a new [`Vector`] image where the width and height are the same.
58    pub fn square(vector: VectorName, size: Rems) -> Self {
59        Self::new(vector, size, size)
60    }
61
62    /// Sets the vector color.
63    pub fn color(mut self, color: Color) -> Self {
64        self.color = color;
65        self
66    }
67
68    /// Sets the vector size.
69    pub fn size(mut self, size: impl Into<Size<Rems>>) -> Self {
70        let size = size.into();
71        self.size = size;
72        self
73    }
74}
75
76impl Transformable for Vector {
77    fn transform(mut self, transformation: Transformation) -> Self {
78        self.transformation = transformation;
79        self
80    }
81}
82
83impl RenderOnce for Vector {
84    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
85        let width = self.size.width;
86        let height = self.size.height;
87
88        svg()
89            // By default, prevent the SVG from stretching
90            // to fill its container.
91            .flex_none()
92            .w(width)
93            .h(height)
94            .path(self.path)
95            .text_color(self.color.color(cx))
96            .with_transformation(self.transformation)
97    }
98}
99
100impl Component for Vector {
101    fn scope() -> ComponentScope {
102        ComponentScope::Images
103    }
104
105    fn name() -> &'static str {
106        "Vector"
107    }
108
109    fn description() -> Option<&'static str> {
110        Some("A vector image component that can be displayed at specific sizes.")
111    }
112
113    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
114        let size = rems_from_px(60.);
115
116        Some(
117            v_flex()
118                .gap_6()
119                .children(vec![
120                    example_group_with_title(
121                        "Basic Usage",
122                        vec![
123                            single_example(
124                                "Default",
125                                Vector::square(VectorName::AppLogo, size).into_any_element(),
126                            ),
127                            single_example(
128                                "Custom Size",
129                                h_flex()
130                                    .h(rems_from_px(120.))
131                                    .justify_center()
132                                    .child(Vector::new(
133                                        VectorName::AppLogo,
134                                        rems_from_px(120.),
135                                        rems_from_px(200.),
136                                    ))
137                                    .into_any_element(),
138                            ),
139                        ],
140                    ),
141                    example_group_with_title(
142                        "Colored",
143                        vec![
144                            single_example(
145                                "Accent Color",
146                                Vector::square(VectorName::AppLogo, size)
147                                    .color(Color::Accent)
148                                    .into_any_element(),
149                            ),
150                            single_example(
151                                "Error Color",
152                                Vector::square(VectorName::AppLogo, size)
153                                    .color(Color::Error)
154                                    .into_any_element(),
155                            ),
156                        ],
157                    ),
158                    example_group_with_title(
159                        "Different Vectors",
160                        vec![single_example(
161                            "App X Copilot",
162                            Vector::square(VectorName::AppXCopilot, rems_from_px(100.))
163                                .into_any_element(),
164                        )],
165                    ),
166                ])
167                .into_any_element(),
168        )
169    }
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175
176    #[test]
177    fn vector_path() {
178        assert_eq!(VectorName::AppLogo.path().as_ref(), "images/app_logo.svg");
179    }
180}