Add `w_vw` and `h_vh` to `StyledExt` for setting sizes in viewport units (#3600)

Marshall Bowers created

This PR adds `w_vw` and `h_vh` methods to `StyledExt`.

These methods are the same as their `w` and `h` counterparts, but
operate in viewport units, giving us the equivalent of `vw` and `vh` in
CSS.

You can see them in action in this story:

```
cargo run -p storybook2 -- components/viewport_units
```

Release Notes:

- N/A

Change summary

crates/storybook2/src/stories.rs                |  2 +
crates/storybook2/src/stories/viewport_units.rs | 34 +++++++++++++++++++
crates/storybook2/src/story_selector.rs         |  2 +
crates/ui2/src/styled_ext.rs                    | 14 +++++++
4 files changed, 52 insertions(+)

Detailed changes

crates/storybook2/src/stories.rs 🔗

@@ -5,6 +5,7 @@ mod kitchen_sink;
 mod picker;
 mod scroll;
 mod text;
+mod viewport_units;
 mod z_index;
 
 pub use auto_height_editor::*;
@@ -14,4 +15,5 @@ pub use kitchen_sink::*;
 pub use picker::*;
 pub use scroll::*;
 pub use text::*;
+pub use viewport_units::*;
 pub use z_index::*;

crates/storybook2/src/stories/viewport_units.rs 🔗

@@ -0,0 +1,34 @@
+use gpui::{Div, Render};
+use story::Story;
+
+use ui::prelude::*;
+
+pub struct ViewportUnitsStory;
+
+impl Render for ViewportUnitsStory {
+    type Element = Div;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        Story::container().child(
+            div()
+                .flex()
+                .flex_row()
+                .child(
+                    div()
+                        .w_vw(0.5, cx)
+                        .h_vh(0.8, cx)
+                        .bg(gpui::red())
+                        .text_color(gpui::white())
+                        .child("50vw, 80vh"),
+                )
+                .child(
+                    div()
+                        .w_vw(0.25, cx)
+                        .h_vh(0.33, cx)
+                        .bg(gpui::green())
+                        .text_color(gpui::white())
+                        .child("25vw, 33vh"),
+                ),
+        )
+    }
+}

crates/storybook2/src/story_selector.rs 🔗

@@ -30,6 +30,7 @@ pub enum ComponentStory {
     Scroll,
     Tab,
     Text,
+    ViewportUnits,
     ZIndex,
     Picker,
 }
@@ -55,6 +56,7 @@ impl ComponentStory {
             Self::Scroll => ScrollStory::view(cx).into(),
             Self::Text => TextStory::view(cx).into(),
             Self::Tab => cx.build_view(|_| ui::TabStory).into(),
+            Self::ViewportUnits => cx.build_view(|_| crate::stories::ViewportUnitsStory).into(),
             Self::ZIndex => cx.build_view(|_| ZIndexStory).into(),
             Self::Picker => PickerStory::new(cx).into(),
         }

crates/ui2/src/styled_ext.rs 🔗

@@ -16,6 +16,20 @@ fn elevated<E: Styled>(this: E, cx: &mut WindowContext, index: ElevationIndex) -
 
 /// Extends [`Styled`](gpui::Styled) with Zed specific styling methods.
 pub trait StyledExt: Styled + Sized {
+    /// Sets the width of the element as a percentage of the viewport's width.
+    ///
+    /// `percent` should be a value between `0.0` and `1.0`.
+    fn w_vw(self, percent: f32, cx: &mut WindowContext) -> Self {
+        self.w(cx.viewport_size().width * percent)
+    }
+
+    /// Sets the height of the element as a percentage of the viewport's height.
+    ///
+    /// `percent` should be a value between `0.0` and `1.0`.
+    fn h_vh(self, percent: f32, cx: &mut WindowContext) -> Self {
+        self.h(cx.viewport_size().height * percent)
+    }
+
     /// Horizontally stacks elements.
     ///
     /// Sets `flex()`, `flex_row()`, `items_center()`