Skip to main content

scal_core/
object_builders.rs

1use glam::{Vec2, Vec3};
2use uuid::Uuid;
3
4use crate::anim_obj::{
5    AnimObj, AnimObjKind, CodeHandle, CodeWindowHandle, StretchMode, Syntax, TextAlign,
6};
7use crate::color::Color;
8use crate::theme::Theme;
9use crate::transform::Transform;
10
11macro_rules! impl_transform_methods {
12    ($builder:ty) => {
13        #[allow(clippy::return_self_not_must_use)]
14        impl $builder {
15            /// Set the 2D position of this object's transform
16            pub const fn pos(mut self, position: Vec2) -> Self {
17                self.transform.position = position.extend(self.transform.position.z);
18                self
19            }
20
21            /// Set the z position of this object's transform. It's used for ordering object drawing.
22            pub const fn z(mut self, z: f32) -> Self {
23                self.transform.position.z = z;
24                self
25            }
26            /// Set the scale of this object's transform
27            pub const fn scale(mut self, scale: Vec2) -> Self {
28                self.transform.scale = scale;
29                self
30            }
31            /// Set the rotation of this object's transform
32            pub const fn rot(mut self, rotation: f32) -> Self {
33                self.transform.rotation = rotation;
34                self
35            }
36        }
37    };
38}
39
40/// Create a new SVG object builder.
41/// ```
42/// svg()
43///     .path("./icon.svg")
44///     .size(Vec2::splat(60.0))
45///     .color(Color::RED)
46///     .build(),
47/// ```
48pub fn svg() -> SvgBuilder {
49    SvgBuilder::default()
50}
51
52#[must_use]
53pub struct SvgBuilder {
54    path: String,
55    size: Vec2,
56    tint: Color,
57    fill: Option<Color>,
58    stroke: Option<Color>,
59    stroke_width: Option<f32>,
60    stretch: StretchMode,
61    transform: Transform,
62}
63
64impl Default for SvgBuilder {
65    fn default() -> Self {
66        Self {
67            path: String::new(),
68            size: Vec2::splat(40.0),
69            tint: Color::WHITE,
70            fill: None,
71            stroke: None,
72            stroke_width: None,
73            stretch: StretchMode::Fit,
74            transform: Transform::new(Vec3::ZERO),
75        }
76    }
77}
78
79#[allow(clippy::return_self_not_must_use)]
80impl SvgBuilder {
81    /// Set the filepath of the SVG
82    pub fn path(mut self, path: impl Into<String>) -> Self {
83        self.path = path.into();
84        self
85    }
86    /// Set the size of the SVG
87    pub const fn size(mut self, size: Vec2) -> Self {
88        self.size = size;
89        self
90    }
91    /// Set the tint color of the SVG
92    pub const fn color(mut self, color: Color) -> Self {
93        self.tint = color;
94        self
95    }
96    /// Set the fill color of the SVG
97    pub const fn fill(mut self, color: Color) -> Self {
98        self.fill = Some(color);
99        self
100    }
101    /// Set the stroke color of the SVG
102    pub const fn stroke(mut self, color: Color) -> Self {
103        self.stroke = Some(color);
104        self
105    }
106    /// Set the stroke width of the SVG
107    pub const fn stroke_width(mut self, width: f32) -> Self {
108        self.stroke_width = Some(width);
109        self
110    }
111    /// Set the stretch mode of the SVG
112    pub const fn stretch(mut self, stretch: StretchMode) -> Self {
113        self.stretch = stretch;
114        self
115    }
116    #[must_use]
117    pub fn build(self) -> AnimObj {
118        let id = self.transform.uuid;
119        AnimObj {
120            id,
121            transform: self.transform,
122            kind: AnimObjKind::Svg {
123                path: self.path,
124                size: self.size,
125                tint: self.tint,
126                fill: self.fill,
127                stroke: self.stroke,
128                stroke_width: self.stroke_width,
129                stretch: self.stretch,
130            },
131        }
132    }
133}
134
135impl_transform_methods!(SvgBuilder);
136
137/// Create a new rectangle object builder.
138/// ```
139/// rectangle()
140///     .size(Vec2::new(200.0, 100.0))
141///     .corner_radius(10.0)
142///     .color(Color::BLUE)
143///     .build(),
144/// ```
145pub fn rectangle() -> RectangleBuilder {
146    RectangleBuilder::default()
147}
148
149#[must_use]
150pub struct RectangleBuilder {
151    size: Vec2,
152    corner_radius: f32,
153    color: Color,
154    transform: Transform,
155}
156
157impl Default for RectangleBuilder {
158    fn default() -> Self {
159        Self {
160            size: Vec2::splat(100.0),
161            corner_radius: 0.0,
162            color: Color::WHITE,
163            transform: Transform::new(Vec3::ZERO),
164        }
165    }
166}
167
168#[allow(clippy::return_self_not_must_use)]
169impl RectangleBuilder {
170    pub const fn size(mut self, size: Vec2) -> Self {
171        self.size = size;
172        self
173    }
174    pub const fn corner_radius(mut self, radius: f32) -> Self {
175        self.corner_radius = radius;
176        self
177    }
178    pub const fn color(mut self, color: Color) -> Self {
179        self.color = color;
180        self
181    }
182    #[must_use]
183    pub const fn build(self) -> AnimObj {
184        let id = self.transform.uuid;
185        AnimObj {
186            id,
187            transform: self.transform,
188            kind: AnimObjKind::Rectangle {
189                size: self.size,
190                corner_radius: self.corner_radius,
191                color: self.color,
192            },
193        }
194    }
195}
196
197impl_transform_methods!(RectangleBuilder);
198
199/// Create a new circle object builder.
200/// ```
201/// circle()
202///     .radius(75.0)
203///     .color(Color::GREEN)
204///     .build(),
205/// ```
206pub fn circle() -> CircleBuilder {
207    CircleBuilder::default()
208}
209
210#[must_use]
211pub struct CircleBuilder {
212    radius: f32,
213    color: Color,
214    transform: Transform,
215}
216
217impl Default for CircleBuilder {
218    fn default() -> Self {
219        Self {
220            radius: 50.0,
221            color: Color::WHITE,
222            transform: Transform::new(Vec3::ZERO),
223        }
224    }
225}
226
227#[allow(clippy::return_self_not_must_use)]
228impl CircleBuilder {
229    /// Set the radius of the circle
230    pub const fn radius(mut self, radius: f32) -> Self {
231        self.radius = radius;
232        self
233    }
234    /// Set the color of the circle
235    pub const fn color(mut self, color: Color) -> Self {
236        self.color = color;
237        self
238    }
239    #[must_use]
240    pub const fn build(self) -> AnimObj {
241        let id = self.transform.uuid;
242        AnimObj {
243            id,
244            transform: self.transform,
245            kind: AnimObjKind::Circle {
246                radius: self.radius,
247                color: self.color,
248            },
249        }
250    }
251}
252
253impl_transform_methods!(CircleBuilder);
254
255/// Create a new code block object builder.
256/// ```
257/// code()
258///     .source("fn main() {\n    println!(\"hello\");\n}")
259///     .syntax(Syntax::Rust)
260///     .line_numbers(true)
261///     .build(),
262/// ```
263pub fn code() -> CodeBuilder {
264    CodeBuilder::default()
265}
266
267#[must_use]
268pub struct CodeBuilder {
269    source_code: String,
270    font_family: String,
271    font_size: f32,
272    syntax: Syntax,
273    theme: Option<Theme>,
274    padding: f32,
275    show_line_numbers: bool,
276    line_number_color: Color,
277    transform: Transform,
278}
279
280impl Default for CodeBuilder {
281    fn default() -> Self {
282        Self {
283            source_code: String::new(),
284            font_family: "sans-serif".to_string(),
285            font_size: 20.0,
286            syntax: Syntax::Rust,
287            theme: None,
288            padding: 20.0,
289            show_line_numbers: false,
290            line_number_color: Color::new(0.5, 0.5, 0.5, 0.6),
291            transform: Transform::new(Vec3::ZERO),
292        }
293    }
294}
295
296#[allow(clippy::return_self_not_must_use)]
297impl CodeBuilder {
298    /// Set the source code of the code block
299    pub fn source(mut self, code: impl Into<String>) -> Self {
300        self.source_code = code.into();
301        self
302    }
303    /// Set the font family of the code block
304    pub fn font_family(mut self, family: impl Into<String>) -> Self {
305        self.font_family = family.into();
306        self
307    }
308    /// Set the font size of the code block
309    pub const fn font_size(mut self, size: f32) -> Self {
310        self.font_size = size;
311        self
312    }
313    /// Set the syntax highlighting language of the code block
314    pub const fn syntax(mut self, syntax: Syntax) -> Self {
315        self.syntax = syntax;
316        self
317    }
318    /// Set the theme of the code block
319    pub fn theme(mut self, theme: Theme) -> Self {
320        self.theme = Some(theme);
321        self
322    }
323    /// Set the padding of the code block
324    pub const fn padding(mut self, padding: f32) -> Self {
325        self.padding = padding;
326        self
327    }
328    /// Toggle line numbers in the code block
329    pub const fn line_numbers(mut self, show: bool) -> Self {
330        self.show_line_numbers = show;
331        self
332    }
333    #[must_use]
334    pub fn build(self) -> CodeHandle {
335        let id = self.transform.uuid;
336        CodeHandle(AnimObj {
337            id,
338            transform: self.transform,
339            kind: AnimObjKind::Code {
340                source_code: self.source_code,
341                font_family: self.font_family,
342                font_size: self.font_size,
343                syntax: self.syntax,
344                theme: self.theme,
345                padding: self.padding,
346                show_line_numbers: self.show_line_numbers,
347                line_number_color: self.line_number_color,
348            },
349        })
350    }
351}
352
353impl_transform_methods!(CodeBuilder);
354
355/// Create a new polygon object builder.
356/// ```
357/// polygon()
358///     .radius(60.0)
359///     .sides(8)
360///     .color(Color::YELLOW)
361///     .build(),
362/// ```
363pub fn polygon() -> PolygonBuilder {
364    PolygonBuilder::default()
365}
366
367#[must_use]
368pub struct PolygonBuilder {
369    radius: f32,
370    sides: u32,
371    color: Color,
372    transform: Transform,
373}
374
375impl Default for PolygonBuilder {
376    fn default() -> Self {
377        Self {
378            radius: 50.0,
379            sides: 6,
380            color: Color::WHITE,
381            transform: Transform::new(Vec3::ZERO),
382        }
383    }
384}
385
386impl PolygonBuilder {
387    /// Set the radius of the polygon
388    pub const fn radius(mut self, radius: f32) -> Self {
389        self.radius = radius;
390        self
391    }
392    /// Set the number of sides of the polygon
393    pub const fn sides(mut self, sides: u32) -> Self {
394        self.sides = sides;
395        self
396    }
397    /// Set the color of the polygon
398    pub const fn color(mut self, color: Color) -> Self {
399        self.color = color;
400        self
401    }
402    #[must_use]
403    pub const fn build(self) -> AnimObj {
404        let id = self.transform.uuid;
405        AnimObj {
406            id,
407            transform: self.transform,
408            kind: AnimObjKind::Polygon {
409                radius: self.radius,
410                sides: self.sides,
411                color: self.color,
412            },
413        }
414    }
415}
416
417impl_transform_methods!(PolygonBuilder);
418
419/// Create a new image object builder.
420/// ```
421/// image()
422///     .path("./photo.png")
423///     .size(Vec2::new(320.0, 240.0))
424///     .build(),
425/// ```
426pub fn image() -> ImageBuilder {
427    ImageBuilder::default()
428}
429
430#[must_use]
431pub struct ImageBuilder {
432    path: String,
433    size: Vec2,
434    color: Color,
435    stretch: StretchMode,
436    transform: Transform,
437}
438
439impl Default for ImageBuilder {
440    fn default() -> Self {
441        Self {
442            path: String::new(),
443            size: Vec2::splat(100.0),
444            color: Color::WHITE,
445            stretch: StretchMode::Fit,
446            transform: Transform::new(Vec3::ZERO),
447        }
448    }
449}
450
451impl ImageBuilder {
452    /// Set the filepath of the image
453    pub fn path(mut self, path: impl Into<String>) -> Self {
454        self.path = path.into();
455        self
456    }
457    /// Set the size of the image
458    pub const fn size(mut self, size: Vec2) -> Self {
459        self.size = size;
460        self
461    }
462    /// Set the color of the image
463    pub const fn color(mut self, color: Color) -> Self {
464        self.color = color;
465        self
466    }
467    /// Set the stretch mode of the image
468    pub const fn stretch(mut self, stretch: StretchMode) -> Self {
469        self.stretch = stretch;
470        self
471    }
472    #[must_use]
473    pub fn build(self) -> AnimObj {
474        let id = self.transform.uuid;
475        AnimObj {
476            id,
477            transform: self.transform,
478            kind: AnimObjKind::Image {
479                path: self.path,
480                size: self.size,
481                color: self.color,
482                stretch: self.stretch,
483            },
484        }
485    }
486}
487
488impl_transform_methods!(ImageBuilder);
489
490/// Create a new text object builder.
491/// ```
492/// text()
493///     .value("Hello, SCAL!")
494///     .font_size(32.0)
495///     .color(Color::WHITE)
496///     .build(),
497/// ```
498pub fn text() -> TextBuilder {
499    TextBuilder::default()
500}
501
502#[must_use]
503pub struct TextBuilder {
504    value: String,
505    font_family: String,
506    alignment: TextAlign,
507    color: Color,
508    font_size: f32,
509    transform: Transform,
510}
511
512impl Default for TextBuilder {
513    fn default() -> Self {
514        Self {
515            value: String::new(),
516            font_family: "sans-serif".to_string(),
517            alignment: TextAlign::Center,
518            color: Color::WHITE,
519            font_size: 24.0,
520            transform: Transform::new(Vec3::ZERO),
521        }
522    }
523}
524
525impl TextBuilder {
526    /// Set the text content
527    pub fn value(mut self, value: impl Into<String>) -> Self {
528        self.value = value.into();
529        self
530    }
531    /// Set the font family of the text
532    pub fn font_family(mut self, family: impl Into<String>) -> Self {
533        self.font_family = family.into();
534        self
535    }
536    /// Set the alignment of the text
537    pub const fn alignment(mut self, align: TextAlign) -> Self {
538        self.alignment = align;
539        self
540    }
541    /// Set the color of the text
542    pub const fn color(mut self, color: Color) -> Self {
543        self.color = color;
544        self
545    }
546    /// Set the font size of the text
547    pub const fn font_size(mut self, size: f32) -> Self {
548        self.font_size = size;
549        self
550    }
551    #[must_use]
552    pub fn build(self) -> AnimObj {
553        let id = self.transform.uuid;
554        AnimObj {
555            id,
556            transform: self.transform,
557            kind: AnimObjKind::Text {
558                value: self.value,
559                font_family: self.font_family,
560                alignment: self.alignment,
561                color: self.color,
562                font_size: self.font_size,
563            },
564        }
565    }
566}
567
568impl_transform_methods!(TextBuilder);
569
570/// Create a new code window object builder.
571/// ```
572/// code_window()
573///     .source("print('hello world')")
574///     .syntax(Syntax::Python)
575///     .title("demo.py")
576///     .build(),
577/// ```
578pub fn code_window() -> CodeWindowBuilder {
579    CodeWindowBuilder::default()
580}
581
582#[must_use]
583pub struct CodeWindowBuilder {
584    source_code: String,
585    font_family: String,
586    font_size: f32,
587    syntax: Syntax,
588    theme: Option<Theme>,
589    title: String,
590    title_font_size: f32,
591    width: f32,
592    height: f32,
593    background_color: Color,
594    show_line_numbers: bool,
595    line_number_color: Color,
596    transform: Transform,
597}
598
599impl Default for CodeWindowBuilder {
600    fn default() -> Self {
601        Self {
602            source_code: String::new(),
603            font_family: "sans-serif".to_string(),
604            font_size: 20.0,
605            syntax: Syntax::Rust,
606            theme: None,
607            title: String::new(),
608            title_font_size: 16.0,
609            width: 800.0,
610            height: 600.0,
611            background_color: Color::new(0.176, 0.176, 0.176, 1.0),
612            show_line_numbers: false,
613            line_number_color: Color::new(0.5, 0.5, 0.5, 0.6),
614            transform: Transform::new(Vec3::ZERO),
615        }
616    }
617}
618
619impl CodeWindowBuilder {
620    /// Set the source code of the code window
621    pub fn source(mut self, code: impl Into<String>) -> Self {
622        self.source_code = code.into();
623        self
624    }
625    /// Set the font family of the code window
626    pub fn font_family(mut self, family: impl Into<String>) -> Self {
627        self.font_family = family.into();
628        self
629    }
630    /// Set the font size of the code window
631    pub const fn font_size(mut self, size: f32) -> Self {
632        self.font_size = size;
633        self
634    }
635    /// Set the syntax highlighting language of the code window
636    pub const fn syntax(mut self, syntax: Syntax) -> Self {
637        self.syntax = syntax;
638        self
639    }
640    /// Set the theme of the code window
641    pub fn theme(mut self, theme: Theme) -> Self {
642        self.theme = Some(theme);
643        self
644    }
645    /// Set the title of the code window
646    pub fn title(mut self, title: impl Into<String>) -> Self {
647        self.title = title.into();
648        self
649    }
650    /// Set the title font size
651    pub const fn title_font_size(mut self, size: f32) -> Self {
652        self.title_font_size = size;
653        self
654    }
655    /// Set the width of the code window
656    pub const fn width(mut self, width: f32) -> Self {
657        self.width = width;
658        self
659    }
660    /// Set the height of the code window
661    pub const fn height(mut self, height: f32) -> Self {
662        self.height = height;
663        self
664    }
665    /// Set the background color of the code window
666    pub const fn background_color(mut self, color: Color) -> Self {
667        self.background_color = color;
668        self
669    }
670    /// Toggle line numbers in the code window
671    pub const fn line_numbers(mut self, show: bool) -> Self {
672        self.show_line_numbers = show;
673        self
674    }
675    #[must_use]
676    pub fn build(self) -> CodeWindowHandle {
677        let id = self.transform.uuid;
678        let code_id = Uuid::new_v5(&id, b"code");
679        let close_btn_id = Uuid::new_v5(&id, b"close");
680        let minimize_btn_id = Uuid::new_v5(&id, b"minimize");
681        let maximize_btn_id = Uuid::new_v5(&id, b"maximize");
682        let title_id = Uuid::new_v5(&id, b"title");
683        let bg_id = Uuid::new_v5(&id, b"bg");
684        let container_id = Uuid::new_v5(&id, b"container");
685        let title_bar_bg_id = Uuid::new_v5(&id, b"title_bg");
686        CodeWindowHandle(AnimObj {
687            id,
688            transform: self.transform,
689            kind: AnimObjKind::CodeWindow {
690                source_code: self.source_code,
691                font_family: self.font_family,
692                font_size: self.font_size,
693                syntax: self.syntax,
694                theme: self.theme,
695                title: self.title,
696                title_font_size: self.title_font_size,
697                width: self.width,
698                height: self.height,
699                background_color: self.background_color,
700                code_id,
701                close_btn_id,
702                minimize_btn_id,
703                maximize_btn_id,
704                title_id,
705                bg_id,
706                container_id,
707                title_bar_bg_id,
708                show_line_numbers: self.show_line_numbers,
709                line_number_color: self.line_number_color,
710            },
711        })
712    }
713}
714
715impl_transform_methods!(CodeWindowBuilder);