fission_core/ui/widgets/
icon.rs1use crate::internal::InternalLower;
2use crate::lowering::{InternalIrBuilder, InternalLoweringCx};
3use fission_ir::{
4 op::{Color, LayoutOp, Op, PaintOp, Stroke},
5 WidgetId,
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<WidgetId>,
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
121impl InternalLower for Icon {
122 fn lower(&self, cx: &mut InternalLoweringCx) -> WidgetId {
123 let id = self.id.map(Into::into).unwrap_or_else(|| cx.next_node_id());
124
125 let tokens = &cx.env.theme.tokens;
126 let color = self.color.unwrap_or(tokens.colors.text_primary);
127 let size = self.size.unwrap_or(24.0);
128
129 let paint_op = match &self.source {
131 IconSource::Path(d) => PaintOp::DrawPath {
132 path: d.clone(),
133 fill: if self.stroke.is_some() {
134 None
135 } else {
136 Some(fission_ir::op::Fill::Solid(color))
137 },
138 stroke: self.stroke.clone(),
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() {
145 None
146 } else {
147 Some(fission_ir::op::Fill::Solid(color))
148 },
149 stroke: self.stroke.clone(),
150 }
151 }
152 IconSource::SvgContent(c) => PaintOp::DrawSvg {
153 content: c.clone(),
154 fill: if self.stroke.is_some() {
155 None
156 } else {
157 Some(fission_ir::op::Fill::Solid(color))
158 },
159 stroke: self.stroke.clone(),
160 },
161 };
162
163 let paint_id = InternalIrBuilder::new(cx.next_node_id(), Op::Paint(paint_op)).build(cx);
164
165 let mut layout = InternalIrBuilder::new(
166 id,
167 Op::Layout(LayoutOp::Box {
168 width: Some(size),
169 height: Some(size),
170 min_width: None,
171 max_width: None,
172 min_height: None,
173 max_height: None,
174 padding: [0.0; 4],
175 flex_grow: 0.0,
176 flex_shrink: 0.0,
177 aspect_ratio: None,
178 }),
179 );
180 layout.add_child(paint_id);
181 layout.build(cx)
182 }
183}