fission_core/ui/widgets/
icon.rs1use crate::lowering::{LoweringContext, NodeBuilder};
2use crate::ui::traits::Lower;
3use fission_ir::{
4 op::{Color, 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() {
138 None
139 } else {
140 Some(fission_ir::op::Fill::Solid(color))
141 },
142 stroke: self.stroke.clone(),
143 },
144 IconSource::File(f) => {
145 let content = std::fs::read_to_string(f).unwrap_or_default();
146 PaintOp::DrawSvg {
147 content,
148 fill: if self.stroke.is_some() {
149 None
150 } else {
151 Some(fission_ir::op::Fill::Solid(color))
152 },
153 stroke: self.stroke.clone(),
154 }
155 }
156 IconSource::SvgContent(c) => PaintOp::DrawSvg {
157 content: c.clone(),
158 fill: if self.stroke.is_some() {
159 None
160 } else {
161 Some(fission_ir::op::Fill::Solid(color))
162 },
163 stroke: self.stroke.clone(),
164 },
165 };
166
167 let paint_id = NodeBuilder::new(cx.next_node_id(), Op::Paint(paint_op)).build(cx);
168
169 let mut layout = NodeBuilder::new(
170 id,
171 Op::Layout(LayoutOp::Box {
172 width: Some(size),
173 height: Some(size),
174 min_width: None,
175 max_width: None,
176 min_height: None,
177 max_height: None,
178 padding: [0.0; 4],
179 flex_grow: 0.0,
180 flex_shrink: 0.0,
181 aspect_ratio: None,
182 }),
183 );
184 layout.add_child(paint_id);
185 layout.build(cx)
186 }
187}