elements.rs

  1use gpui::{
  2    color::Color,
  3    geometry::{
  4        rect::RectF,
  5        vector::{vec2f, Vector2F},
  6    },
  7    json::{json, ToJson},
  8    scene,
  9    serde_json::Value,
 10    AnyElement, Element, LayoutContext, Quad, SceneBuilder, SizeConstraint, View, ViewContext,
 11};
 12use std::{any::Any, ops::Range, rc::Rc};
 13
 14pub struct Atom<V: View> {
 15    style: Rc<AtomStyle>,
 16    children: Vec<AnyElement<V>>,
 17}
 18
 19impl<V: View> Atom<V> {
 20    pub fn new(style: impl Into<Rc<AtomStyle>>) -> Self {
 21        Self {
 22            style: style.into(),
 23            children: Vec::new(),
 24        }
 25    }
 26
 27    fn inner_constraint(&self, mut constraint: SizeConstraint) -> SizeConstraint {
 28        // Constrain width
 29        constraint
 30            .max
 31            .set_x(constraint.max.x().min(self.style.width.max()));
 32
 33        // Constrain height
 34        constraint
 35            .max
 36            .set_y(constraint.max.y().min(self.style.height.max()));
 37
 38        // Account for margin, border, and padding
 39        let inset = self.inset_size();
 40        SizeConstraint {
 41            min: (constraint.min - inset).max(Vector2F::zero()),
 42            max: (constraint.max - inset).max(Vector2F::zero()),
 43        }
 44    }
 45
 46    // fn layout_2d_children(
 47    //     &mut self,
 48    //     orientation: Axis2d,
 49    //     constraint: SizeConstraint,
 50    //     view: &mut V,
 51    //     cx: &mut LayoutContext<V>,
 52    // ) -> Vector2F {
 53    //     let mut total_flex: Option<f32> = None;
 54    //     let mut total_size = 0.0;
 55    //     let mut cross_axis_max: f32 = 0.0;
 56
 57    //     // First pass: Layout non-flex children only
 58    //     for child in &mut self.children {
 59    //         if let Some(child_flex) = child.metadata::<AtomStyle>().and_then(|style| style.flex) {
 60    //             *total_flex.get_or_insert(0.) += child_flex;
 61    //         } else {
 62    //             let child_constraint = match orientation {
 63    //                 Axis2d::X => SizeConstraint::new(
 64    //                     vec2f(0.0, constraint.min.y()),
 65    //                     vec2f(INFINITY, constraint.max.y()),
 66    //                 ),
 67    //                 Axis2d::Y => SizeConstraint::new(
 68    //                     vec2f(constraint.min.x(), 0.0),
 69    //                     vec2f(constraint.max.x(), INFINITY),
 70    //                 ),
 71    //             };
 72    //             let child_size = child.layout(child_constraint, view, cx);
 73    //             total_size += match orientation {
 74    //                 Axis3d::Horizontal => {
 75    //                     cross_axis_max = cross_axis_max.max(child_size.y());
 76    //                     child_size.x()
 77    //                 }
 78    //                 Axis3d::Vertical => {
 79    //                     cross_axis_max = cross_axis_max.max(child_size.x());
 80    //                     child_size.y()
 81    //                 }
 82    //             };
 83    //         }
 84    //     }
 85
 86    //     let remaining_space = match orientation {
 87    //         Axis3d::Vertical => constraint.max.y() - total_size,
 88    //         Axis3d::Horizontal => constraint.max.x() - total_size,
 89    //     };
 90
 91    //     // Second pass: Layout flexible children
 92    //     if let Some(total_flex) = total_flex {
 93    //         if total_flex > 0. {
 94    //             let space_per_flex = remaining_space.max(0.) / total_flex;
 95
 96    //             for child in &mut self.children {
 97    //                 if let Some(child_flex) =
 98    //                     child.metadata::<AtomStyle>().and_then(|style| style.flex)
 99    //                 {
100    //                     let child_max = space_per_flex * child_flex;
101    //                     let mut child_constraint = constraint;
102    //                     match orientation {
103    //                         Axis3d::Vertical => {
104    //                             child_constraint.min.set_y(0.0);
105    //                             child_constraint.max.set_y(child_max);
106    //                         }
107    //                         Axis3d::Horizontal => {
108    //                             child_constraint.min.set_x(0.0);
109    //                             child_constraint.max.set_x(child_max);
110    //                         }
111    //                     }
112
113    //                     let child_size = child.layout(child_constraint, view, cx);
114
115    //                     cross_axis_max = match orientation {
116    //                         Axis3d::Vertical => {
117    //                             total_size += child_size.y();
118    //                             cross_axis_max.max(child_size.x())
119    //                         }
120    //                         Axis3d::Horizontal => {
121    //                             total_size += child_size.x();
122    //                             cross_axis_max.max(child_size.y())
123    //                         }
124    //                     };
125    //                 }
126    //             }
127    //         }
128    //     }
129
130    //     let size = match orientation {
131    //         Axis3d::Vertical => vec2f(cross_axis_max, total_size),
132    //         Axis3d::Horizontal => vec2f(total_size, cross_axis_max),
133    //     };
134    //     size
135    // }
136
137    // fn layout_stacked_children(
138    //     &mut self,
139    //     constraint: SizeConstraint,
140    //     view: &mut V,
141    //     cx: &mut LayoutContext<V>,
142    // ) -> Vector2F {
143    //     let mut size = Vector2F::zero();
144
145    //     for child in &mut self.children {
146    //         let child_size = child.layout(constraint, view, cx);
147    //         size.set_x(size.x().max(child_size.x()));
148    //         size.set_y(size.y().max(child_size.y()));
149    //     }
150
151    //     size
152    // }
153
154    fn inset_size(&self) -> Vector2F {
155        self.padding_size() + self.border_size() + self.margin_size()
156    }
157
158    fn margin_size(&self) -> Vector2F {
159        vec2f(
160            self.style.margin.left + self.style.margin.right,
161            self.style.margin.top + self.style.margin.bottom,
162        )
163    }
164
165    fn padding_size(&self) -> Vector2F {
166        vec2f(
167            self.style.padding.left + self.style.padding.right,
168            self.style.padding.top + self.style.padding.bottom,
169        )
170    }
171
172    fn border_size(&self) -> Vector2F {
173        let mut x = 0.0;
174        if self.style.border.left {
175            x += self.style.border.width;
176        }
177        if self.style.border.right {
178            x += self.style.border.width;
179        }
180
181        let mut y = 0.0;
182        if self.style.border.top {
183            y += self.style.border.width;
184        }
185        if self.style.border.bottom {
186            y += self.style.border.width;
187        }
188
189        vec2f(x, y)
190    }
191}
192
193impl<V: View> Element<V> for Atom<V> {
194    type LayoutState = Vector2F; // Content size
195    type PaintState = ();
196
197    fn layout(
198        &mut self,
199        constraint: SizeConstraint,
200        view: &mut V,
201        cx: &mut LayoutContext<V>,
202    ) -> (Vector2F, Self::LayoutState) {
203        let inner_constraint = self.inner_constraint(constraint);
204        // let size_of_children = match self.style.axis {
205        //     // Axis3d::X => self.layout_2d_children(Axis2d::X, constraint, view, cx),
206        //     // Axis3d::Y => self.layout_2d_children(Axis2d::Y, constraint, view, cx),
207        //     // Axis3d::Z => self.layout_stacked_children(constraint, view, cx),
208        // };
209        let size_of_children = inner_constraint.max; // TODO!
210
211        // Add back space for padding, border, and margin.
212        let mut size = size_of_children + self.inset_size();
213
214        // Impose horizontal constraints
215        if constraint.min.x().is_finite() {
216            size.set_x(size.x().max(constraint.min.x()));
217        }
218        if size.x() > constraint.max.x() {
219            size.set_x(constraint.max.x());
220        }
221
222        // Impose vertical constraints
223        if constraint.min.y().is_finite() {
224            size.set_y(size.y().max(constraint.min.y()));
225        }
226        if size.y() > constraint.max.y() {
227            size.set_y(constraint.max.y());
228        }
229
230        (size, size_of_children)
231    }
232
233    fn paint(
234        &mut self,
235        scene: &mut SceneBuilder,
236        bounds: RectF,
237        visible_bounds: RectF,
238        size_of_children: &mut Vector2F,
239        view: &mut V,
240        cx: &mut ViewContext<V>,
241    ) -> Self::PaintState {
242        let margin = &self.style.margin;
243
244        // Account for margins
245        let content_bounds = RectF::from_points(
246            bounds.origin() + vec2f(margin.left, margin.top),
247            bounds.lower_right() - vec2f(margin.right, margin.bottom),
248        );
249
250        // Paint drop shadow
251        for shadow in &self.style.shadows {
252            scene.push_shadow(scene::Shadow {
253                bounds: content_bounds + shadow.offset,
254                corner_radius: self.style.corner_radius,
255                sigma: shadow.blur,
256                color: shadow.color,
257            });
258        }
259
260        // // Paint cursor style
261        // if let Some(hit_bounds) = content_bounds.intersection(visible_bounds) {
262        //     if let Some(style) = self.style.cursor {
263        //         scene.push_cursor_region(CursorRegion {
264        //             bounds: hit_bounds,
265        //             style,
266        //         });
267        //     }
268        // }
269
270        // Render the background and/or the border (if it not an overlay border).
271        match self.style.fill {
272            Fill::Color(fill_color) => {}
273        }
274        if let Fill::Color(fill_color) = self.style.fill {
275            let is_fill_visible = !fill_color.is_fully_transparent();
276            if is_fill_visible || self.style.border.is_visible() {
277                scene.push_quad(Quad {
278                    bounds: content_bounds,
279                    background: is_fill_visible.then_some(fill_color),
280                    border: scene::Border {
281                        width: self.style.border.width,
282                        color: self.style.border.color,
283                        overlay: false,
284                        top: self.style.border.top,
285                        right: self.style.border.right,
286                        bottom: self.style.border.bottom,
287                        left: self.style.border.left,
288                    },
289                    corner_radius: self.style.corner_radius,
290                });
291            }
292        }
293
294        if !self.children.is_empty() {
295            // Account for padding first.
296            let padding = &self.style.padding;
297            let padded_bounds = RectF::from_points(
298                content_bounds.origin() + vec2f(padding.left, padding.top),
299                content_bounds.lower_right() - vec2f(padding.right, padding.top),
300            );
301            let parent_size = padded_bounds.size();
302
303            // Now paint the children accourding to the orientation.
304            let child_aligment = self.style.align;
305            // match self.style.orientation {
306            //     Orientation::Axial(axis) => {
307            //         let mut child_origin = padded_bounds.origin();
308            //         // Align all children together along the primary axis
309            //         match axis {
310            //             Axis3d::Horizontal => align_child(
311            //                 &mut child_origin,
312            //                 parent_size,
313            //                 *size_of_children,
314            //                 child_aligment,
315            //                 true,
316            //                 false,
317            //             ),
318            //             Axis3d::Vertical => align_child(
319            //                 &mut child_origin,
320            //                 parent_size,
321            //                 *size_of_children,
322            //                 child_aligment,
323            //                 false,
324            //                 true,
325            //             ),
326            //         };
327
328            //         for child in &mut self.children {
329            //             // Align each child along the cross axis
330            //             match axis {
331            //                 Axis3d::Horizontal => {
332            //                     child_origin.set_y(padded_bounds.origin_y());
333            //                     align_child(
334            //                         &mut child_origin,
335            //                         parent_size,
336            //                         child.size(),
337            //                         child_aligment,
338            //                         false,
339            //                         true,
340            //                     );
341            //                 }
342            //                 Axis3d::Vertical => {
343            //                     child_origin.set_x(padded_bounds.origin_x());
344            //                     align_child(
345            //                         &mut child_origin,
346            //                         parent_size,
347            //                         child.size(),
348            //                         child_aligment,
349            //                         true,
350            //                         false,
351            //                     );
352            //                 }
353            //             }
354
355            //             child.paint(scene, child_origin, visible_bounds, view, cx);
356
357            //             // Advance along the cross axis by the size of this child
358            //             match axis {
359            //                 Axis3d::Horizontal => {
360            //                     child_origin.set_x(child_origin.x() + child.size().x())
361            //                 }
362            //                 Axis3d::Vertical => {
363            //                     child_origin.set_y(child_origin.x() + child.size().y())
364            //                 }
365            //             }
366            //         }
367            //     }
368            //     Orientation::Stacked => {
369            //         for child in &mut self.children {
370            //             let mut child_origin = padded_bounds.origin();
371            //             align_child(
372            //                 &mut child_origin,
373            //                 parent_size,
374            //                 child.size(),
375            //                 child_aligment,
376            //                 true,
377            //                 true,
378            //             );
379
380            //             scene.paint_layer(None, |scene| {
381            //                 child.paint(scene, child_origin, visible_bounds, view, cx);
382            //             });
383            //         }
384            //     }
385            // }
386        }
387    }
388
389    fn rect_for_text_range(
390        &self,
391        range_utf16: Range<usize>,
392        _: RectF,
393        _: RectF,
394        _: &Self::LayoutState,
395        _: &Self::PaintState,
396        view: &V,
397        cx: &ViewContext<V>,
398    ) -> Option<RectF> {
399        self.children
400            .iter()
401            .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx))
402    }
403
404    fn debug(
405        &self,
406        bounds: RectF,
407        _: &Self::LayoutState,
408        _: &Self::PaintState,
409        view: &V,
410        cx: &ViewContext<V>,
411    ) -> Value {
412        json!({
413            "type": "Cell",
414            "bounds": bounds.to_json(),
415            "children": self.children.iter().map(|child| child.debug(view, cx)).collect::<Vec<Value>>()
416        })
417    }
418
419    fn metadata(&self) -> Option<&dyn Any> {
420        Some(&self.style)
421    }
422}
423
424fn align_child(
425    child_origin: &mut Vector2F,
426    parent_size: Vector2F,
427    child_size: Vector2F,
428    alignment: Vector2F,
429    horizontal: bool,
430    vertical: bool,
431) {
432    let parent_center = parent_size / 2.;
433    let parent_target = parent_center + parent_center * alignment;
434    let child_center = child_size / 2.;
435    let child_target = child_center + child_center * alignment;
436
437    if horizontal {
438        child_origin.set_x(child_origin.x() + parent_target.x() - child_target.x())
439    }
440    if vertical {
441        child_origin.set_y(child_origin.y() + parent_target.y() - child_target.y());
442    }
443}
444
445struct Interactive<Style> {
446    default: Style,
447    hovered: Style,
448    active: Style,
449    disabled: Style,
450}
451
452#[derive(Clone, Default)]
453pub struct AtomStyle {
454    axis: Axis3d,
455    wrap: bool,
456    align: Vector2F,
457    overflow_x: Overflow,
458    overflow_y: Overflow,
459    gap_x: Gap,
460    gap_y: Gap,
461
462    width: Length,
463    height: Length,
464    margin: Edges<f32>,
465    padding: Edges<f32>,
466
467    text_color: Option<Color>,
468    font_size: Option<f32>,
469    font_style: Option<FontStyle>,
470    font_weight: Option<FontWeight>,
471
472    opacity: f32,
473    fill: Fill,
474    border: Border,
475    corner_radius: f32,
476    shadows: Vec<Shadow>,
477}
478
479impl AtomStyle {
480    pub fn width(mut self, width: impl Into<Length>) -> Self {
481        self.width = width.into();
482        self
483    }
484
485    pub fn height(mut self, height: impl Into<Length>) -> Self {
486        self.height = height.into();
487        self
488    }
489
490    pub fn fill(mut self, fill: impl Into<Fill>) -> Self {
491        self.fill = fill.into();
492        self
493    }
494}
495
496#[derive(Clone, Default)]
497struct Edges<T> {
498    top: T,
499    bottom: T,
500    left: T,
501    right: T,
502}
503
504#[derive(Clone, Default)]
505struct CornerRadii {
506    top_left: f32,
507    top_right: f32,
508    bottom_right: f32,
509    bottom_left: f32,
510}
511
512#[derive(Clone)]
513enum Fill {
514    Color(Color),
515}
516
517impl From<Color> for Fill {
518    fn from(value: Color) -> Self {
519        Fill::Color(value)
520    }
521}
522
523impl Default for Fill {
524    fn default() -> Self {
525        Fill::Color(Color::default())
526    }
527}
528
529#[derive(Clone, Default)]
530struct Border {
531    color: Color,
532    width: f32,
533    top: bool,
534    bottom: bool,
535    left: bool,
536    right: bool,
537}
538
539impl Border {
540    fn is_visible(&self) -> bool {
541        self.width > 0.
542            && !self.color.is_fully_transparent()
543            && (self.top || self.bottom || self.left || self.right)
544    }
545}
546
547#[derive(Clone, Copy)]
548enum Length {
549    Fixed(f32),
550    Auto { flex: f32, min: f32, max: f32 },
551}
552
553impl Default for Length {
554    fn default() -> Self {
555        Length::Auto {
556            flex: 1.,
557            min: 0.,
558            max: f32::INFINITY,
559        }
560    }
561}
562
563impl From<f32> for Length {
564    fn from(value: f32) -> Self {
565        Length::Fixed(value)
566    }
567}
568
569impl Length {
570    pub fn max(&self) -> f32 {
571        match self {
572            Length::Fixed(value) => *value,
573            Length::Auto { max, .. } => *max,
574        }
575    }
576}
577
578#[derive(Clone, Copy, Default)]
579enum Axis3d {
580    X,
581    #[default]
582    Y,
583    Z,
584}
585
586impl Axis3d {
587    fn to_2d(self) -> Option<Axis2d> {
588        match self {
589            Axis3d::X => Some(Axis2d::X),
590            Axis3d::Y => Some(Axis2d::Y),
591            Axis3d::Z => None,
592        }
593    }
594}
595
596#[derive(Clone, Copy, Default)]
597enum Axis2d {
598    X,
599    #[default]
600    Y,
601}
602
603#[derive(Clone, Copy, Default)]
604enum Overflow {
605    #[default]
606    Visible,
607    Hidden,
608    Auto,
609}
610
611#[derive(Clone, Copy)]
612enum Gap {
613    Fixed(f32),
614    Around,
615    Between,
616    Even,
617}
618
619impl Default for Gap {
620    fn default() -> Self {
621        Gap::Fixed(0.)
622    }
623}
624
625#[derive(Clone, Copy, Default)]
626struct Shadow {
627    offset: Vector2F,
628    blur: f32,
629    color: Color,
630}
631
632#[derive(Clone, Copy, Default)]
633enum FontStyle {
634    #[default]
635    Normal,
636    Italic,
637    Oblique,
638}
639
640#[derive(Clone, Copy, Default)]
641enum FontWeight {
642    Thin,
643    ExtraLight,
644    Light,
645    #[default]
646    Normal,
647    Medium,
648    Semibold,
649    Bold,
650    ExtraBold,
651    Black,
652}