1use teksilo_core::build_context::BuildContext;
17use teksilo_core::styles::{ColorPickerLayout, ColorPickerStyle, ColorPickerStyleConfig};
18use teksilo_core::widget_id::WidgetId;
19use teksilo_tokens::{Color, InputTokens, TargetRole};
20
21use crate::panel::Panel;
22use crate::primitives::{HStack, VStack};
23use teksilo_core::styles::density::{dp, spacing};
24
25pub const CANVAS_WIDTH: f32 = 224.0;
29pub const CANVAS_HEIGHT: f32 = 192.0;
30pub const CANVAS_CORNER_RADIUS: f32 = 4.0;
31
32pub const STRIP_THICKNESS: f32 = 14.0;
34pub const STRIP_LENGTH: f32 = 192.0;
35pub const STRIP_CORNER_RADIUS: f32 = 4.0;
36
37pub const INDICATOR_RADIUS: f32 = 7.0;
39pub const INDICATOR_OUTER_STROKE_WIDTH: f32 = 1.5;
40pub const INDICATOR_INNER_STROKE_WIDTH: f32 = 1.0;
41pub const INDICATOR_OUTER_COLOR: Color = Color::WHITE;
42pub const INDICATOR_INNER_COLOR: Color = Color::new(0.0, 0.0, 0.0, 0.6);
43
44pub const STRIP_THUMB_WIDTH: f32 = 18.0;
46pub const STRIP_THUMB_HEIGHT: f32 = 8.0;
47pub const STRIP_THUMB_CORNER_RADIUS: f32 = 2.0;
48
49pub const PADDING: f32 = 12.0;
51pub const GAP: f32 = 10.0;
52
53pub const SWATCH_SIZE: f32 = 22.0;
55pub const SWATCH_SPACING: f32 = 6.0;
56pub const SWATCH_CORNER_RADIUS: f32 = 4.0;
57pub const SWATCH_SELECTED_STROKE_WIDTH: f32 = 2.0;
58
59pub const CHECKER_CELL: f32 = 6.0;
62pub const CHECKER_COLOR_A: Color = Color::new(0.78, 0.78, 0.78, 1.0);
63pub const CHECKER_COLOR_B: Color = Color::WHITE;
64
65pub const PREVIEW_WIDTH: f32 = 64.0;
68pub const PREVIEW_HEIGHT: f32 = 28.0;
69pub const PREVIEW_CORNER_RADIUS: f32 = 4.0;
70
71pub const SPINNER_FIELD_WIDTH: f32 = 56.0;
73pub const HEX_FIELD_WIDTH: f32 = 96.0;
74
75#[derive(Debug, Clone, Copy, PartialEq)]
82pub struct ColorPickerRecipe {
83 pub canvas_width: f32,
85 pub canvas_height: f32,
86 pub canvas_corner_radius: f32,
87 pub strip_thickness: f32,
89 pub strip_length: f32,
90 pub strip_corner_radius: f32,
91 pub indicator_radius: f32,
93 pub indicator_outer_stroke_width: f32,
94 pub indicator_inner_stroke_width: f32,
95 pub indicator_outer_color: Color,
96 pub indicator_inner_color: Color,
97 pub strip_thumb_width: f32,
99 pub strip_thumb_height: f32,
100 pub strip_thumb_corner_radius: f32,
101 pub padding: f32,
103 pub gap: f32,
104 pub swatch_size: f32,
106 pub swatch_spacing: f32,
107 pub swatch_corner_radius: f32,
108 pub swatch_selected_stroke_width: f32,
109 pub checker_cell: f32,
111 pub checker_color_a: Color,
112 pub checker_color_b: Color,
113 pub preview_width: f32,
115 pub preview_height: f32,
116 pub preview_corner_radius: f32,
117 pub spinner_field_width: f32,
119 pub hex_field_width: f32,
120}
121
122impl ColorPickerRecipe {
123 pub fn for_tokens(tokens: &InputTokens) -> Self {
129 Self {
130 canvas_width: dp(CANVAS_WIDTH, TargetRole::Target, tokens),
131 canvas_height: dp(CANVAS_HEIGHT, TargetRole::Target, tokens),
132 canvas_corner_radius: CANVAS_CORNER_RADIUS,
133 strip_thickness: STRIP_THICKNESS,
134 strip_length: dp(STRIP_LENGTH, TargetRole::Target, tokens),
135 strip_corner_radius: STRIP_CORNER_RADIUS,
136 indicator_radius: INDICATOR_RADIUS,
137 indicator_outer_stroke_width: INDICATOR_OUTER_STROKE_WIDTH,
138 indicator_inner_stroke_width: INDICATOR_INNER_STROKE_WIDTH,
139 indicator_outer_color: INDICATOR_OUTER_COLOR,
140 indicator_inner_color: INDICATOR_INNER_COLOR,
141 strip_thumb_width: STRIP_THUMB_WIDTH,
142 strip_thumb_height: STRIP_THUMB_HEIGHT,
143 strip_thumb_corner_radius: STRIP_THUMB_CORNER_RADIUS,
144 padding: spacing(PADDING, tokens),
145 gap: spacing(GAP, tokens),
146 swatch_size: SWATCH_SIZE,
147 swatch_spacing: spacing(SWATCH_SPACING, tokens),
148 swatch_corner_radius: SWATCH_CORNER_RADIUS,
149 swatch_selected_stroke_width: SWATCH_SELECTED_STROKE_WIDTH,
150 checker_cell: CHECKER_CELL,
151 checker_color_a: CHECKER_COLOR_A,
152 checker_color_b: CHECKER_COLOR_B,
153 preview_width: dp(PREVIEW_WIDTH, TargetRole::Target, tokens),
154 preview_height: dp(PREVIEW_HEIGHT, TargetRole::Target, tokens),
155 preview_corner_radius: PREVIEW_CORNER_RADIUS,
156 spinner_field_width: dp(SPINNER_FIELD_WIDTH, TargetRole::Target, tokens),
157 hex_field_width: dp(HEX_FIELD_WIDTH, TargetRole::Target, tokens),
158 }
159 }
160}
161
162impl Default for ColorPickerRecipe {
163 fn default() -> Self {
164 Self::for_tokens(&InputTokens::default())
165 }
166}
167
168#[derive(Debug, Default, Clone, Copy)]
170pub struct RecipeColorPickerStyle {
171 pub recipe: ColorPickerRecipe,
172}
173
174impl RecipeColorPickerStyle {
175 pub fn new(recipe: ColorPickerRecipe) -> Self {
176 Self { recipe }
177 }
178
179 pub fn for_tokens(tokens: &InputTokens) -> Self {
186 Self {
187 recipe: ColorPickerRecipe::for_tokens(tokens),
188 }
189 }
190}
191
192impl ColorPickerStyle for RecipeColorPickerStyle {
193 fn make_body(&self, cfg: &ColorPickerStyleConfig, ctx: &mut BuildContext) -> WidgetId {
194 let body = match cfg.layout {
195 ColorPickerLayout::Compact => {
196 let mut col = VStack::new().spacing(self.recipe.gap).child(cfg.top_row);
197 if let Some(hex) = cfg.compact_hex {
198 col = col.child(hex);
199 }
200 if let Some(footer) = cfg.footer {
201 col = col.child(footer);
202 }
203 ctx.add(col)
204 }
205 ColorPickerLayout::Standard => {
206 let mut col = VStack::new().spacing(self.recipe.gap).child(cfg.top_row);
207 if let Some(preview) = cfg.preview_row {
208 col = col.child(preview);
209 }
210 if let Some(rgb) = cfg.rgb_row {
211 col = col.child(rgb);
212 }
213 if let Some(hsv) = cfg.hsv_row {
214 col = col.child(hsv);
215 }
216 if let Some(grid) = cfg.swatches {
217 col = col.child(grid);
218 }
219 if let Some(footer) = cfg.footer {
220 col = col.child(footer);
221 }
222 ctx.add(col)
223 }
224 ColorPickerLayout::Wide => {
225 let mut side_col = VStack::new().spacing(self.recipe.gap);
226 if let Some(preview) = cfg.preview_row {
227 side_col = side_col.child(preview);
228 }
229 if let Some(rgb) = cfg.rgb_row {
230 side_col = side_col.child(rgb);
231 }
232 if let Some(hsv) = cfg.hsv_row {
233 side_col = side_col.child(hsv);
234 }
235 let side_col_id = ctx.add(side_col);
236 let main_row_id = ctx.add(
237 HStack::new()
238 .spacing(self.recipe.gap)
239 .child(cfg.top_row)
240 .child(side_col_id),
241 );
242 let mut col = VStack::new().spacing(self.recipe.gap).child(main_row_id);
243 if let Some(grid) = cfg.swatches {
244 col = col.child(grid);
245 }
246 if let Some(footer) = cfg.footer {
247 col = col.child(footer);
248 }
249 ctx.add(col)
250 }
251 };
252
253 ctx.add(
257 Panel::new()
258 .padding(self.recipe.padding)
259 .border_width(1.0)
260 .child(body),
261 )
262 }
263}