WIP

Nathan Sobo created

Change summary

crates/gpui/playground/ui/src/node.rs | 568 ++++++++++++++++++++--------
1 file changed, 396 insertions(+), 172 deletions(-)

Detailed changes

crates/gpui/playground/ui/src/node.rs 🔗

@@ -146,183 +146,412 @@ impl<V: View> Node<V> {
         self
     }
 
+    fn id_as_str(&self) -> &str {
+        self.id.as_deref().unwrap_or("<anonymous>")
+    }
+
+    fn layout_children(&mut self, constraint: SizeConstraint) -> Vector2F {
+        todo!()
+    }
+
     fn layout_xy(
         &mut self,
-        axis: Axis2d,
-        max_size: Vector2F,
+        primary_axis: Axis2d,
+        constraint: SizeConstraint,
         rem_pixels: f32,
         layout: &mut NodeLayout,
         view: &mut V,
         cx: &mut LayoutContext<V>,
     ) -> Vector2F {
-        eprintln!(
-            "{}: max_size = {:?}",
-            self.id.as_deref().unwrap_or(""),
-            max_size
-        );
-
-        let mut remaining_flex: f32 = 0.;
-        layout.margins = self.style.margins.fixed_pixels(rem_pixels);
-        remaining_flex += self.style.margins.flex().get(axis);
         layout.padding = self.style.padding.fixed_pixels(rem_pixels);
-        remaining_flex += self.style.padding.flex().get(axis);
+        layout.margins = self.style.margins.fixed_pixels(rem_pixels);
+        let fixed_padding_size = layout.padding.size();
+        let fixed_margin_size = layout.margins.size();
+        let borders_size = &self.style.borders.size();
+        let flex_2f = self.style.flex();
+        let cross_axis = primary_axis.rotate();
+        let mut child_constraint = SizeConstraint::default();
+
+        for axis in [Axis2d::X, Axis2d::Y] {
+            let length = self.style.size.get(axis);
+
+            // Before we layout children
+            match length {
+                Length::Fixed(fixed_length) => {
+                    let fixed_length = fixed_length.to_pixels(rem_pixels);
+                    let mut remaining_length = constraint.min.get(axis)
+                        - fixed_margin_size.get(axis)
+                        - borders_size.get(axis)
+                        - fixed_padding_size.get(axis)
+                        - fixed_length;
+
+                    let mut remaining_flex = flex_2f.get(axis);
+
+                    // Distribute remaining length to flexible padding, but only so long as the
+                    // padding does not exceed the fixed length.
+                    let mut padding_flex = self.style.padding.flex().get(axis);
+                    let mut padding_length =
+                        ((padding_flex / remaining_flex) * remaining_length).min(fixed_length);
+                    remaining_flex -= padding_flex;
+                    *layout.padding.start_mut(axis) += self.style.padding.start(axis).flex_pixels(
+                        rem_pixels,
+                        &mut padding_flex,
+                        &mut padding_length,
+                    );
+                    *layout.padding.end_mut(axis) += self.style.padding.end(axis).flex_pixels(
+                        rem_pixels,
+                        &mut padding_flex,
+                        &mut padding_length,
+                    );
 
-        let mut padded_max =
-            max_size - layout.margins.size() - self.style.borders.size() - layout.padding.size();
+                    // Distribute remaining length to flexible margins.
+                    *layout.margins.start_mut(axis) += self.style.margins.start(axis).flex_pixels(
+                        rem_pixels,
+                        &mut remaining_flex,
+                        &mut remaining_length,
+                    );
+                    *layout.margins.end_mut(axis) += self.style.margins.end(axis).flex_pixels(
+                        rem_pixels,
+                        &mut remaining_flex,
+                        &mut remaining_length,
+                    );
 
-        match self.style.size.width {
-            Length::Hug => {}
-            Length::Fixed(width) => {
-                padded_max.set_x(width.to_pixels(rem_pixels));
+                    child_constraint.max.set(axis, remaining_length);
+                    if axis == cross_axis {
+                        child_constraint.min.set(axis, remaining_length);
+                    }
+                }
+                Length::Auto { .. } => {
+                    // If the length is flex, we calculate the content's share first
+                    let mut remaining_flex = flex_2f.get(axis);
+                    let mut remaining_length = constraint.max.get(axis)
+                        - fixed_margin_size.get(axis)
+                        - borders_size.get(axis)
+                        - fixed_padding_size.get(axis);
+
+                    let children_length =
+                        length.flex_pixels(rem_pixels, &mut remaining_flex, &mut remaining_length);
+                    child_constraint.max.set(axis, children_length);
+                    if axis == cross_axis {
+                        child_constraint.min.set(axis, children_length);
+                    }
+                }
+                Length::Hug => {
+                    // Leave the min/max children size at 0 for this dimension,
+                    // so that children can be as small as they want.
+                }
             }
-            Length::Auto { min, max, .. } => padded_max.set_x(
-                padded_max
-                    .x()
-                    .clamp(min.to_pixels(rem_pixels), max.to_pixels(rem_pixels)),
-            ),
-        };
-        match self.style.size.height {
-            Length::Hug => {}
-            Length::Fixed(height) => padded_max.set_y(height.to_pixels(rem_pixels)),
-            Length::Auto { min, max, .. } => padded_max.set_y(
-                padded_max
-                    .y()
-                    .clamp(min.to_pixels(rem_pixels), max.to_pixels(rem_pixels)),
-            ),
-        };
-
-        let mut remaining_length =
-            padded_max.get(axis) - self.style.size.get(axis).fixed_pixels(rem_pixels);
-        dbg!(padded_max, remaining_length);
+        }
 
-        // Pass 1: Total up flex units and layout inflexible children.
-        //
-        // Consume the remaining length as we layout inflexible children, so that any
-        // remaining length can be distributed among flexible children in the next pass.
+        // Layout fixed children using the child constraint determined above.
+        let mut remaining_child_length = child_constraint.max.get(primary_axis);
+        let mut remaining_child_flex = 0.;
+        let mut total_child_length = 0.;
         let mut cross_axis_max: f32 = 0.;
-        let cross_axis = axis.rotate();
-
-        // Fixed children are unconstrained along the primary axis, and constrained to
-        // the padded max size along the cross axis.
-        let child_constraint =
-            SizeConstraint::loose(Vector2F::infinity().set(cross_axis, padded_max.get(cross_axis)));
-
-        eprintln!(
-            "{}: child_max = {:?}, remaining_length: {}",
-            self.id.as_deref().unwrap_or(""),
-            child_constraint.max,
-            remaining_length
-        );
+        child_constraint.min.set(primary_axis, 0.);
+        child_constraint.max.set(primary_axis, 0.);
 
         for child in &mut self.children {
+            // Skip children that are flexible in the primary for this first pass.
             if let Some(child_flex) = child
                 .metadata::<NodeStyle>()
-                .and_then(|style| style.flex(axis))
+                .map(|style| style.flex().get(primary_axis))
             {
-                remaining_flex += child_flex;
-            } else {
-                let child_size = child.layout(child_constraint, view, cx);
-                cross_axis_max = cross_axis_max.max(child_size.get(cross_axis));
-                remaining_length -= child_size.get(axis);
+                if child_flex > 0. {
+                    remaining_child_flex += child_flex;
+                    continue;
+                }
             }
+
+            // The child is fixed along the primary axis, so perform layout.
+            let child_size = child.layout(child_constraint, view, cx);
+            let child_length = child_size.get(primary_axis);
+            remaining_child_length -= child_length;
+            total_child_length += child_length;
+            cross_axis_max = cross_axis_max.max(child_size.get(cross_axis));
         }
 
-        eprintln!(
-            "{}: child_max = {:?}, remaining_length: {}",
-            self.id.as_deref().unwrap_or(""),
-            child_constraint.max,
-            remaining_length
-        );
+        // Now layout all the flexible children.
+        for child in &mut self.children {
+            if let Some(child_flex) = child
+                .metadata::<NodeStyle>()
+                .map(|style| style.flex().get(primary_axis))
+            {
+                if child_flex > 0. {
+                    child_constraint.max.set(
+                        primary_axis,
+                        (child_flex / remaining_child_flex) * remaining_child_length,
+                    );
 
-        // Pass 2: Allocate the remaining space among flexible lengths along the primary axis.
-        if remaining_flex > 0. {
-            dbg!(self.id.as_deref(), remaining_length, remaining_flex);
+                    let child_size = child.layout(child_constraint, view, cx);
+                    let child_length = child_size.get(primary_axis);
+                    total_child_length += child_length;
+                    remaining_child_length -= child_length;
+                    remaining_child_flex -= child_flex;
+                    cross_axis_max = cross_axis_max.max(child_size.get(cross_axis));
+                }
+            }
+        }
 
-            // Add flex pixels from margin and padding.
-            *layout.margins.start_mut(axis) += self.style.margins.start(axis).flex_pixels(
-                rem_pixels,
-                &mut remaining_flex,
-                &mut remaining_length,
-            );
+        let children_size = match primary_axis {
+            Axis2d::X => vec2f(total_child_length, cross_axis_max),
+            Axis2d::Y => vec2f(cross_axis_max, total_child_length),
+        };
 
-            dbg!(self.id.as_deref(), layout.margins.start(axis));
+        for axis in [Axis2d::X, Axis2d::Y] {
+            let length = self.style.size.get(axis);
+
+            // Finish with flexible margins and padding now that children are laid out.
+            match length {
+                Length::Hug => {
+                    // Now that we know the size of our children, we can distribute
+                    // space to flexible padding and margins.
+                    let mut remaining_flex = flex_2f.get(axis);
+                    let mut remaining_length = constraint.max.get(axis)
+                        - fixed_margin_size.get(axis)
+                        - borders_size.get(axis)
+                        - fixed_padding_size.get(axis)
+                        - children_size.get(axis);
+
+                    // Distribute remaining length to flexible padding
+                    *layout.padding.start_mut(axis) += self.style.padding.start(axis).flex_pixels(
+                        rem_pixels,
+                        &mut remaining_flex,
+                        &mut remaining_length,
+                    );
+                    *layout.padding.end_mut(axis) += self.style.padding.end(axis).flex_pixels(
+                        rem_pixels,
+                        &mut remaining_flex,
+                        &mut remaining_length,
+                    );
 
-            *layout.padding.start_mut(axis) += self.style.padding.start(axis).flex_pixels(
-                rem_pixels,
-                &mut remaining_flex,
-                &mut remaining_length,
-            );
+                    // Distribute remaining length to flexible margins.
+                    *layout.margins.start_mut(axis) += self.style.margins.start(axis).flex_pixels(
+                        rem_pixels,
+                        &mut remaining_flex,
+                        &mut remaining_length,
+                    );
+                    *layout.margins.end_mut(axis) += self.style.margins.end(axis).flex_pixels(
+                        rem_pixels,
+                        &mut remaining_flex,
+                        &mut remaining_length,
+                    );
+                }
+                Length::Auto { flex, .. } => {
+                    // If the length is flex, we subtract the fixed margins, padding, and
+                    // children length along the current dimension, then distribute the
+                    // remaining length among margins and padding.
+                    let mut remaining_flex = flex_2f.get(axis) - flex;
+                    let mut remaining_length = constraint.max.get(axis)
+                        - fixed_margin_size.get(axis)
+                        - borders_size.get(axis)
+                        - fixed_padding_size.get(axis)
+                        - children_size.get(axis);
+
+                    // Distribute remaining length to flexible padding
+                    *layout.padding.start_mut(axis) += self.style.padding.start(axis).flex_pixels(
+                        rem_pixels,
+                        &mut remaining_flex,
+                        &mut remaining_length,
+                    );
+                    *layout.padding.end_mut(axis) += self.style.padding.end(axis).flex_pixels(
+                        rem_pixels,
+                        &mut remaining_flex,
+                        &mut remaining_length,
+                    );
 
-            // Lay out the flexible children
-            let mut child_max = padded_max;
-            for child in &mut self.children {
-                if let Some(child_flex) = child
-                    .metadata::<NodeStyle>()
-                    .and_then(|style| style.flex(axis))
-                {
-                    child_max.set(axis, child_flex / remaining_flex * remaining_length);
-                    let child_size = child.layout(SizeConstraint::loose(child_max), view, cx);
-
-                    remaining_flex -= child_flex;
-                    remaining_length -= child_size.get(axis);
-                    cross_axis_max = child_size.get(cross_axis).max(cross_axis_max);
+                    // Distribute remaining length to flexible margins.
+                    *layout.margins.start_mut(axis) += self.style.margins.start(axis).flex_pixels(
+                        rem_pixels,
+                        &mut remaining_flex,
+                        &mut remaining_length,
+                    );
+                    *layout.margins.end_mut(axis) += self.style.margins.end(axis).flex_pixels(
+                        rem_pixels,
+                        &mut remaining_flex,
+                        &mut remaining_length,
+                    );
+                }
+                Length::Fixed(_) => {
+                    // If we had a fixed length, we've already computed margins and
+                    // padding, so there's nothing to do.
                 }
             }
-
-            // Add flex pixels from margin and padding.
-            *layout.margins.end_mut(axis) += self.style.margins.end(axis).flex_pixels(
-                rem_pixels,
-                &mut remaining_flex,
-                &mut remaining_length,
-            );
-            *layout.padding.end_mut(axis) += self.style.padding.end(axis).flex_pixels(
-                rem_pixels,
-                &mut remaining_flex,
-                &mut remaining_length,
-            );
         }
 
-        let width = match self.style.size.width {
-            Length::Hug => match axis {
-                Axis2d::X => max_size.get(axis) - remaining_length,
-                Axis2d::Y => {
-                    cross_axis_max
-                        + layout.padding.size().get(cross_axis)
-                        + self.style.borders.size().get(cross_axis)
-                        + layout.margins.size().get(cross_axis)
-                }
-            },
-            Length::Fixed(width) => width.to_pixels(rem_pixels),
-            Length::Auto { min, max, .. } => max_size
-                .x()
-                .clamp(min.to_pixels(rem_pixels), max.to_pixels(rem_pixels)),
-        };
-
-        let height = match self.style.size.height {
-            Length::Hug => match axis {
-                Axis2d::Y => max_size.get(axis) - remaining_length,
-                Axis2d::X => {
-                    cross_axis_max
-                        + layout.padding.size().get(cross_axis)
-                        + self.style.borders.size().get(cross_axis)
-                        + layout.margins.size().get(cross_axis)
-                }
-            },
-            Length::Fixed(height) => height.to_pixels(rem_pixels),
-            Length::Auto { min, max, .. } => max_size
-                .y()
-                .clamp(min.to_pixels(rem_pixels), max.to_pixels(rem_pixels)),
-        };
+        children_size + layout.padding.size() + self.style.borders.size() + layout.margins.size()
+    }
+
+    // If this element is flexible, we need to distribute the available space
+    // between the margin, padding, and content, any of which can be flexible.
+    //
+    // If the node's size is fixed, we distribute the flexible space
+    //
+    // let mut remaining_size = todo!();
+
+    // layout.margins = self.style.margins.fixed_pixels(rem_pixels);
+    // remaining_size -= layout.margins.size();
+    // layout.padding = self.style.padding.fixed_pixels(rem_pixels);
+    // remaining_size -= layout.padding.size();
+    // remaining_size -= self.style.size.fixed_pixels(rem_pixels);
+
+    // //
+    // // The available space
+    // let flex = self.style.flex();
+
+    // let mut padded_max =
+    //     max_size - layout.margins.size() - self.style.borders.size() - layout.padding.size();
+
+    // match self.style.size.width {
+    //     Length::Hug => {}
+    //     Length::Fixed(width) => {
+    //         padded_max.set_x(width.to_pixels(rem_pixels));
+    //     }
+    //     Length::Auto { min, max, .. } => padded_max.set_x(
+    //         padded_max
+    //             .x()
+    //             .clamp(min.to_pixels(rem_pixels), max.to_pixels(rem_pixels)),
+    //     ),
+    // };
+    // match self.style.size.height {
+    //     Length::Hug => {}
+    //     Length::Fixed(height) => padded_max.set_y(height.to_pixels(rem_pixels)),
+    //     Length::Auto { min, max, .. } => padded_max.set_y(
+    //         padded_max
+    //             .y()
+    //             .clamp(min.to_pixels(rem_pixels), max.to_pixels(rem_pixels)),
+    //     ),
+    // };
+
+    // let mut remaining_length =
+    //     padded_max.get(axis) - self.style.size.get(axis).fixed_pixels(rem_pixels);
+    // dbg!(padded_max, remaining_length);
+
+    // // Pass 1: Total up flex units and layout inflexible children.
+    // //
+    // // Consume the remaining length as we layout inflexible children, so that any
+    // // remaining length can be distributed among flexible children in the next pass.
+    // let mut cross_axis_max: f32 = 0.;
+    // let cross_axis = axis.rotate();
+
+    // // Fixed children are unconstrained along the primary axis, and constrained to
+    // // the padded max size along the cross axis.
+    // let child_constraint =
+    //     SizeConstraint::loose(Vector2F::infinity().set(cross_axis, padded_max.get(cross_axis)));
+
+    // eprintln!(
+    //     "{}: child_max = {:?}, remaining_length: {}",
+    //     self.id.as_deref().unwrap_or(""),
+    //     child_constraint.max,
+    //     remaining_length
+    // );
+
+    // for child in &mut self.children {
+    //     if let Some(child_flex) = child
+    //         .metadata::<NodeStyle>()
+    //         .map(|style| style.flex().get(axis))
+    //     {
+    //         remaining_flex += child_flex;
+    //     } else {
+    //         let child_size = child.layout(child_constraint, view, cx);
+    //         cross_axis_max = cross_axis_max.max(child_size.get(cross_axis));
+    //         remaining_length -= child_size.get(axis);
+    //     }
+    // }
 
-        eprintln!(
-            "{}: size = {} {}",
-            self.id.as_deref().unwrap_or(""),
-            width,
-            height
-        );
+    // eprintln!(
+    //     "{}: child_max = {:?}, remaining_length: {}",
+    //     self.id.as_deref().unwrap_or(""),
+    //     child_constraint.max,
+    //     remaining_length
+    // );
+
+    // // Pass 2: Allocate the remaining space among flexible lengths along the primary axis.
+    // if remaining_flex > 0. {
+    //     dbg!(self.id.as_deref(), remaining_length, remaining_flex);
+
+    //     // Add flex pixels from margin and padding.
+    //     *layout.margins.start_mut(axis) += self.style.margins.start(axis).flex_pixels(
+    //         rem_pixels,
+    //         &mut remaining_flex,
+    //         &mut remaining_length,
+    //     );
+
+    //     dbg!(self.id.as_deref(), layout.margins.start(axis));
+
+    //     *layout.padding.start_mut(axis) += self.style.padding.start(axis).flex_pixels(
+    //         rem_pixels,
+    //         &mut remaining_flex,
+    //         &mut remaining_length,
+    //     );
+
+    //     // Lay out the flexible children
+    //     let mut child_max = padded_max;
+    //     for child in &mut self.children {
+    //         if let Some(child_flex) = child.metadata::<NodeStyle>().map(|style| style.flex()) {
+    //             child_max.set(axis, child_flex / remaining_flex * remaining_length);
+    //             let child_size = child.layout(SizeConstraint::loose(child_max), view, cx);
+
+    //             remaining_flex -= child_flex;
+    //             remaining_length -= child_size.get(axis);
+    //             cross_axis_max = child_size.get(cross_axis).max(cross_axis_max);
+    //         }
+    //     }
+
+    //     // Add flex pixels from margin and padding.
+    //     *layout.margins.end_mut(axis) += self.style.margins.end(axis).flex_pixels(
+    //         rem_pixels,
+    //         &mut remaining_flex,
+    //         &mut remaining_length,
+    //     );
+    //     *layout.padding.end_mut(axis) += self.style.padding.end(axis).flex_pixels(
+    //         rem_pixels,
+    //         &mut remaining_flex,
+    //         &mut remaining_length,
+    //     );
+    // }
 
-        vec2f(width, height)
-    }
+    // let width = match self.style.size.width {
+    //     Length::Hug => match axis {
+    //         Axis2d::X => max_size.get(axis) - remaining_length,
+    //         Axis2d::Y => {
+    //             cross_axis_max
+    //                 + layout.padding.size().get(cross_axis)
+    //                 + self.style.borders.size().get(cross_axis)
+    //                 + layout.margins.size().get(cross_axis)
+    //         }
+    //     },
+    //     Length::Fixed(width) => width.to_pixels(rem_pixels),
+    //     Length::Auto { min, max, .. } => max_size
+    //         .x()
+    //         .clamp(min.to_pixels(rem_pixels), max.to_pixels(rem_pixels)),
+    // };
+
+    // let height = match self.style.size.height {
+    //     Length::Hug => match axis {
+    //         Axis2d::Y => max_size.get(axis) - remaining_length,
+    //         Axis2d::X => {
+    //             cross_axis_max
+    //                 + layout.padding.size().get(cross_axis)
+    //                 + self.style.borders.size().get(cross_axis)
+    //                 + layout.margins.size().get(cross_axis)
+    //         }
+    //     },
+    //     Length::Fixed(height) => height.to_pixels(rem_pixels),
+    //     Length::Auto { min, max, .. } => max_size
+    //         .y()
+    //         .clamp(min.to_pixels(rem_pixels), max.to_pixels(rem_pixels)),
+    // };
+
+    // eprintln!(
+    //     "{}: size = {} {}",
+    //     self.id.as_deref().unwrap_or(""),
+    //     width,
+    //     height
+    // );
+
+    // vec2f(width, height)
+    // }
 
     fn paint_children_xy(
         &mut self,
@@ -392,7 +621,7 @@ impl<V: View> Element<V> for Node<V> {
         let mut layout = NodeLayout::default();
 
         let size = if let Some(axis) = self.style.axis.to_2d() {
-            self.layout_xy(axis, constraint.max, cx.rem_pixels(), &mut layout, view, cx)
+            self.layout_xy(axis, constraint, cx.rem_pixels(), &mut layout, view, cx)
         } else {
             todo!()
         };
@@ -615,25 +844,8 @@ pub struct NodeStyle {
 }
 
 impl NodeStyle {
-    fn flex(&self, axis: Axis2d) -> Option<f32> {
-        let mut sum = None;
-        match axis {
-            Axis2d::X => {
-                sum = optional_add(sum, self.margins.left.flex());
-                sum = optional_add(sum, self.padding.left.flex());
-                sum = optional_add(sum, self.size.width.flex());
-                sum = optional_add(sum, self.padding.right.flex());
-                sum = optional_add(sum, self.margins.right.flex());
-            }
-            Axis2d::Y => {
-                sum = optional_add(sum, self.margins.top.flex());
-                sum = optional_add(sum, self.padding.top.flex());
-                sum = optional_add(sum, self.size.height.flex());
-                sum = optional_add(sum, self.padding.bottom.flex());
-                sum = optional_add(sum, self.margins.bottom.flex());
-            }
-        }
-        sum
+    fn flex(&self) -> Vector2F {
+        self.margins.flex() + self.padding.flex() + self.size.flex()
     }
 }
 
@@ -660,14 +872,14 @@ impl<T: Copy> Size<T> {
     }
 }
 
-impl<T: Copy + Add<Output = T>> Size<Option<T>> {
-    fn add_assign_optional(&mut self, rhs: Size<Option<T>>) {
-        self.width = optional_add(self.width, rhs.width);
-        self.height = optional_add(self.height, rhs.height);
+impl Size<Length> {
+    fn fixed_pixels(&self, rem_pixels: f32) -> Size<f32> {
+        Size {
+            width: self.width.fixed_pixels(rem_pixels),
+            height: self.height.fixed_pixels(rem_pixels),
+        }
     }
-}
 
-impl Size<Length> {
     pub fn fixed(&self) -> Size<Rems> {
         Size {
             width: self.width.fixed().unwrap_or_default(),
@@ -1002,7 +1214,7 @@ impl Axis3d {
     }
 }
 
-#[derive(Clone, Copy, Default)]
+#[derive(Clone, Copy, Default, PartialEq, Eq)]
 pub enum Axis2d {
     X,
     #[default]
@@ -1385,6 +1597,8 @@ trait Vector2FExt {
     fn infinity() -> Self;
     fn get(self, axis: Axis2d) -> f32;
     fn set(&mut self, axis: Axis2d, value: f32) -> Self;
+    fn inc_x(&mut self, delta: f32) -> f32;
+    fn increment_y(&mut self, delta: f32) -> f32;
 }
 
 impl Vector2FExt for Vector2F {
@@ -1407,6 +1621,16 @@ impl Vector2FExt for Vector2F {
         }
         *self
     }
+
+    fn inc_x(&mut self, delta: f32) -> f32 {
+        self.set_x(self.x() + delta);
+        self.x()
+    }
+
+    fn increment_y(&mut self, delta: f32) -> f32 {
+        self.set_y(self.y() + delta);
+        self.y()
+    }
 }
 
 trait ElementExt<V: View> {