fission_core/ui/widgets/
icon.rs1use crate::lowering::{LoweringContext, NodeBuilder};
2use crate::ui::traits::Lower;
3use fission_ir::{
4 op::{Color, Fill, LayoutOp, Op, PaintOp, Stroke},
5 NodeId,
6};
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
15pub enum IconSource {
16 Path(String),
18 File(String),
20 SvgContent(String),
22}
23
24impl Default for IconSource {
25 fn default() -> Self {
26 IconSource::Path(String::new())
27 }
28}
29
30impl From<String> for IconSource {
31 fn from(s: String) -> Self {
32 IconSource::Path(s)
33 }
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct Icon {
57 pub id: Option<NodeId>,
59 pub source: IconSource,
61 pub color: Option<Color>,
63 pub size: Option<f32>,
65 pub stroke: Option<Stroke>,
67}
68
69impl Icon {
70 pub fn path(path: impl Into<String>) -> Self {
71 Self {
72 id: None,
73 source: IconSource::Path(path.into()),
74 color: None,
75 size: None,
76 stroke: None,
77 }
78 }
79
80 pub fn file(path: impl Into<String>) -> Self {
81 Self {
82 id: None,
83 source: IconSource::File(path.into()),
84 color: None,
85 size: None,
86 stroke: None,
87 }
88 }
89
90 pub fn svg(content: impl Into<String>) -> Self {
91 Self {
92 id: None,
93 source: IconSource::SvgContent(content.into()),
94 color: None,
95 size: None,
96 stroke: None,
97 }
98 }
99
100 pub fn new(path: impl Into<String>) -> Self {
102 Self::path(path)
103 }
104
105 pub fn size(mut self, s: f32) -> Self {
106 self.size = Some(s);
107 self
108 }
109
110 pub fn color(mut self, c: Color) -> Self {
111 self.color = Some(c);
112 self
113 }
114
115 pub fn stroke(mut self, s: Stroke) -> Self {
116 self.stroke = Some(s);
117 self
118 }
119
120 pub fn into_node(self) -> crate::ui::Node {
121 crate::ui::Node::Icon(self)
122 }
123}
124
125impl Lower for Icon {
126 fn lower(&self, cx: &mut LoweringContext) -> NodeId {
127 let id = self.id.unwrap_or_else(|| cx.next_node_id());
128
129 let tokens = &cx.env.theme.tokens;
130 let color = self.color.unwrap_or(tokens.colors.text_primary);
131 let size = self.size.unwrap_or(24.0);
132
133 let paint_op = match &self.source {
135 IconSource::Path(d) => PaintOp::DrawPath {
136 path: d.clone(),
137 fill: if self.stroke.is_some() { None } else { Some(Fill { color }) },
138 stroke: self.stroke,
139 },
140 IconSource::File(f) => {
141 let content = std::fs::read_to_string(f).unwrap_or_default();
142 PaintOp::DrawSvg {
143 content,
144 fill: if self.stroke.is_some() { None } else { Some(Fill { color }) },
145 stroke: self.stroke,
146 }
147 },
148 IconSource::SvgContent(c) => PaintOp::DrawSvg {
149 content: c.clone(),
150 fill: if self.stroke.is_some() { None } else { Some(Fill { color }) },
151 stroke: self.stroke,
152 },
153 };
154
155 let paint_id = NodeBuilder::new(cx.next_node_id(), Op::Paint(paint_op)).build(cx);
156
157 let mut layout = NodeBuilder::new(
158 id,
159 Op::Layout(LayoutOp::Box {
160 width: Some(size),
161 height: Some(size),
162 min_width: None, max_width: None, min_height: None, max_height: None,
163 padding: [0.0; 4],
164 flex_grow: 0.0,
165 flex_shrink: 0.0,
166 aspect_ratio: None,
167 }),
168 );
169 layout.add_child(paint_id);
170 layout.build(cx)
171 }
172}