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}