Add `map` method to `Component`s (#3210)

Marshall Bowers created

This PR adds a `map` method to the `Component` trait.

`map` is a fully-generalized form of `when`, as `when` can be expressed
in terms of `map`:

```rs
div().map(|this| if condition { then(this) } else { this })
```

This allows us to take advantage of Rust's pattern matching when
building up conditions:

```rs
// Before
div()
    .when(self.current_side == PanelSide::Left, |this| this.border_r())
    .when(self.current_side == PanelSide::Right, |this| {
        this.border_l()
    })
    .when(self.current_side == PanelSide::Bottom, |this| {
        this.border_b().w_full().h(current_size)
    })

// After
div()
    .map(|this| match self.current_side {
        PanelSide::Left => this.border_r(),
        PanelSide::Right => this.border_l(),
        PanelSide::Bottom => this.border_b().w_full().h(current_size),
    })
```

Release Notes:

- N/A

Change summary

crates/gpui2/src/element.rs        | 15 ++++++++++-----
crates/ui2/src/components/panel.rs | 16 +++++++---------
crates/ui2/src/elements/input.rs   | 15 +++++++--------
3 files changed, 24 insertions(+), 22 deletions(-)

Detailed changes

crates/gpui2/src/element.rs 🔗

@@ -198,14 +198,19 @@ impl<V> AnyElement<V> {
 pub trait Component<V> {
     fn render(self) -> AnyElement<V>;
 
-    fn when(mut self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self
+    fn map<U>(self, f: impl FnOnce(Self) -> U) -> U
     where
         Self: Sized,
+        U: Component<V>,
     {
-        if condition {
-            self = then(self);
-        }
-        self
+        f(self)
+    }
+
+    fn when(self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self
+    where
+        Self: Sized,
+    {
+        self.map(|this| if condition { then(this) } else { this })
     }
 }
 

crates/ui2/src/components/panel.rs 🔗

@@ -98,16 +98,14 @@ impl<V: 'static> Panel<V> {
         v_stack()
             .id(self.id.clone())
             .flex_initial()
-            .when(
-                self.current_side == PanelSide::Left || self.current_side == PanelSide::Right,
-                |this| this.h_full().w(current_size),
-            )
-            .when(self.current_side == PanelSide::Left, |this| this.border_r())
-            .when(self.current_side == PanelSide::Right, |this| {
-                this.border_l()
+            .map(|this| match self.current_side {
+                PanelSide::Left | PanelSide::Right => this.h_full().w(current_size),
+                PanelSide::Bottom => this,
             })
-            .when(self.current_side == PanelSide::Bottom, |this| {
-                this.border_b().w_full().h(current_size)
+            .map(|this| match self.current_side {
+                PanelSide::Left => this.border_r(),
+                PanelSide::Right => this.border_l(),
+                PanelSide::Bottom => this.border_b().w_full().h(current_size),
             })
             .bg(cx.theme().colors().surface)
             .border_color(cx.theme().colors().border)

crates/ui2/src/elements/input.rs 🔗

@@ -94,14 +94,13 @@ impl Input {
             .active(|style| style.bg(input_active_bg))
             .flex()
             .items_center()
-            .child(
-                div()
-                    .flex()
-                    .items_center()
-                    .text_sm()
-                    .when(self.value.is_empty(), |this| this.child(placeholder_label))
-                    .when(!self.value.is_empty(), |this| this.child(label)),
-            )
+            .child(div().flex().items_center().text_sm().map(|this| {
+                if self.value.is_empty() {
+                    this.child(placeholder_label)
+                } else {
+                    this.child(label)
+                }
+            }))
     }
 }