Checkpoint

Mikayla created

Change summary

Cargo.lock                              |   1 
crates/editor2/src/element.rs           |   2 
crates/gpui2/src/app/entity_map.rs      |   6 
crates/gpui2/src/elements.rs            |   1 
crates/gpui2/src/elements/list.rs       | 107 +++++++++++++++++++++-----
crates/gpui2/src/geometry.rs            |  41 +++++++--
crates/gpui2/src/styled.rs              |   7 +
crates/gpui2/src/window.rs              |   6 +
crates/picker2/src/picker2.rs           |  78 +++++++++++--------
crates/storybook2/Cargo.toml            |   1 
crates/storybook2/src/stories.rs        |   2 
crates/storybook2/src/stories/picker.rs |  40 ++++++++++
crates/storybook2/src/story_selector.rs |   2 
crates/terminal2/src/mappings/mouse.rs  |   4 
crates/terminal2/src/terminal2.rs       |  11 +-
15 files changed, 232 insertions(+), 77 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -8542,6 +8542,7 @@ dependencies = [
  "gpui2",
  "itertools 0.11.0",
  "log",
+ "picker2",
  "rust-embed",
  "serde",
  "settings2",

crates/editor2/src/element.rs 🔗

@@ -2834,7 +2834,7 @@ impl PositionMap {
         let previous_valid = self.snapshot.clip_point(exact_unclipped, Bias::Left);
         let next_valid = self.snapshot.clip_point(exact_unclipped, Bias::Right);
 
-        let column_overshoot_after_line_end = (x_overshoot_after_line_end / self.em_advance).into();
+        let column_overshoot_after_line_end = (x_overshoot_after_line_end / self.em_advance) as u32;
         *exact_unclipped.column_mut() += column_overshoot_after_line_end;
         PointForPosition {
             previous_valid,

crates/gpui2/src/app/entity_map.rs 🔗

@@ -13,6 +13,7 @@ use std::{
         atomic::{AtomicUsize, Ordering::SeqCst},
         Arc, Weak,
     },
+    thread::panicking,
 };
 
 slotmap::new_key_type! { pub struct EntityId; }
@@ -140,9 +141,8 @@ impl<'a, T: 'static> core::ops::DerefMut for Lease<'a, T> {
 
 impl<'a, T> Drop for Lease<'a, T> {
     fn drop(&mut self) {
-        if self.entity.is_some() {
-            // We don't panic here, because other panics can cause us to drop the lease without ending it cleanly.
-            log::error!("Leases must be ended with EntityMap::end_lease")
+        if self.entity.is_some() && !panicking() {
+            panic!("Leases must be ended with EntityMap::end_lease")
         }
     }
 }

crates/gpui2/src/elements/list.rs 🔗

@@ -2,36 +2,44 @@ use std::ops::Range;
 
 use smallvec::SmallVec;
 
-use crate::{AnyElement, Component, Element, ElementId, StyleRefinement, ViewContext};
+use crate::{
+    point, px, AnyElement, AvailableSpace, BorrowWindow, Bounds, Component, Element, ElementId,
+    LayoutId, Pixels, Size, StyleRefinement, Styled, ViewContext,
+};
 
 // We want to support uniform and non-uniform height
 // We need to make the ID mandatory, to replace the 'state' field
 // Previous implementation measured the first element as early as possible
 
-fn list<'a, Id, V, Iter, C>(
+pub fn list<Id, V, C>(
     id: Id,
-    f: impl 'static + FnOnce(&'a mut V, Range<usize>, &'a mut ViewContext<V>) -> Iter,
+    item_count: usize,
+    f: impl 'static + Fn(&mut V, Range<usize>, &mut ViewContext<V>) -> SmallVec<[C; 64]>,
 ) -> List<V>
 where
     Id: Into<ElementId>,
     V: 'static,
-    Iter: 'a + Iterator<Item = C>,
     C: Component<V>,
 {
     List {
         id: id.into(),
-        render_items: Box::new(|view, visible_range, cx| {
+        style: Default::default(),
+        item_count,
+        render_items: Box::new(move |view, visible_range, cx| {
             f(view, visible_range, cx)
-                .map(|element| element.render())
+                .into_iter()
+                .map(|component| component.render())
                 .collect()
         }),
     }
 }
 
-struct List<V> {
+pub struct List<V> {
     id: ElementId,
+    style: StyleRefinement,
+    item_count: usize,
     render_items: Box<
-        dyn for<'a> FnOnce(
+        dyn for<'a> Fn(
             &'a mut V,
             Range<usize>,
             &'a mut ViewContext<V>,
@@ -39,8 +47,6 @@ struct List<V> {
     >,
 }
 
-impl<V> List<V> {}
-
 // #[derive(Debug)]
 // pub enum ScrollTarget {
 //     Show(usize),
@@ -48,24 +54,30 @@ impl<V> List<V> {}
 // }
 
 #[derive(Default)]
-struct ListState {
+pub struct ListState {
     scroll_top: f32,
-    style: StyleRefinement,
     // todo
     // scroll_to: Option<ScrollTarget>,
 }
+
+impl<V: 'static> Styled for List<V> {
+    fn style(&mut self) -> &mut StyleRefinement {
+        &mut self.style
+    }
+}
+
 impl<V: 'static> Element<V> for List<V> {
     type ElementState = ListState;
 
     fn id(&self) -> Option<crate::ElementId> {
-        Some(self.id)
+        Some(self.id.clone())
     }
 
     fn initialize(
         &mut self,
         _: &mut V,
         element_state: Option<Self::ElementState>,
-        _: &mut crate::ViewContext<V>,
+        _: &mut ViewContext<V>,
     ) -> Self::ElementState {
         let element_state = element_state.unwrap_or_default();
         element_state
@@ -73,11 +85,11 @@ impl<V: 'static> Element<V> for List<V> {
 
     fn layout(
         &mut self,
-        view_state: &mut V,
-        element_state: &mut Self::ElementState,
-        cx: &mut crate::ViewContext<V>,
-    ) -> crate::LayoutId {
-        todo!()
+        _view_state: &mut V,
+        _element_state: &mut Self::ElementState,
+        cx: &mut ViewContext<V>,
+    ) -> LayoutId {
+        cx.request_layout(&self.computed_style(), None)
     }
 
     fn paint(
@@ -85,7 +97,62 @@ impl<V: 'static> Element<V> for List<V> {
         bounds: crate::Bounds<crate::Pixels>,
         view_state: &mut V,
         element_state: &mut Self::ElementState,
-        cx: &mut crate::ViewContext<V>,
+        cx: &mut ViewContext<V>,
     ) {
+        let style = self.computed_style();
+        style.paint(bounds, cx);
+
+        let border = style.border_widths.to_pixels(cx.rem_size());
+        let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
+
+        let padded_bounds = Bounds::from_corners(
+            bounds.origin + point(border.left + padding.left, border.top + padding.top),
+            bounds.lower_right()
+                - point(border.right + padding.right, border.bottom + padding.bottom),
+        );
+
+        if self.item_count > 0 {
+            let item_height = self.measure_item_height(view_state, padded_bounds, cx);
+            let visible_item_count = (padded_bounds.size.height / item_height) as usize;
+            let visible_range = 0..visible_item_count;
+
+            let mut items = (self.render_items)(view_state, visible_range, cx);
+
+            for (ix, item) in items.iter_mut().enumerate() {
+                item.initialize(view_state, cx);
+                item.layout(view_state, cx);
+                let offset = padded_bounds.origin + point(px(0.), item_height * ix);
+                cx.with_element_offset(Some(offset), |cx| item.paint(view_state, cx))
+            }
+        }
+    }
+}
+
+impl<V> List<V> {
+    fn measure_item_height(
+        &self,
+        view_state: &mut V,
+        list_bounds: Bounds<Pixels>,
+        cx: &mut ViewContext<V>,
+    ) -> Pixels {
+        let mut items = (self.render_items)(view_state, 0..1, cx);
+        debug_assert!(items.len() == 1);
+        let mut item_to_measure = items.pop().unwrap();
+        item_to_measure.initialize(view_state, cx);
+        let layout_id = item_to_measure.layout(view_state, cx);
+        cx.compute_layout(
+            layout_id,
+            Size {
+                width: AvailableSpace::Definite(list_bounds.size.width),
+                height: AvailableSpace::MinContent,
+            },
+        );
+        cx.layout_bounds(layout_id).size.height
+    }
+}
+
+impl<V: 'static> Component<V> for List<V> {
+    fn render(self) -> AnyElement<V> {
+        AnyElement::new(self)
     }
 }

crates/gpui2/src/geometry.rs 🔗

@@ -259,6 +259,24 @@ impl From<Size<Pixels>> for Size<GlobalPixels> {
     }
 }
 
+impl From<Size<Pixels>> for Size<DefiniteLength> {
+    fn from(size: Size<Pixels>) -> Self {
+        Size {
+            width: size.width.into(),
+            height: size.height.into(),
+        }
+    }
+}
+
+impl From<Size<Pixels>> for Size<AbsoluteLength> {
+    fn from(size: Size<Pixels>) -> Self {
+        Size {
+            width: size.width.into(),
+            height: size.height.into(),
+        }
+    }
+}
+
 impl Size<Length> {
     pub fn full() -> Self {
         Self {
@@ -541,6 +559,15 @@ impl Edges<DefiniteLength> {
             left: px(0.).into(),
         }
     }
+
+    pub fn to_pixels(&self, parent_size: Size<AbsoluteLength>, rem_size: Pixels) -> Edges<Pixels> {
+        Edges {
+            top: self.top.to_pixels(parent_size.height, rem_size),
+            right: self.right.to_pixels(parent_size.width, rem_size),
+            bottom: self.bottom.to_pixels(parent_size.height, rem_size),
+            left: self.left.to_pixels(parent_size.width, rem_size),
+        }
+    }
 }
 
 impl Edges<AbsoluteLength> {
@@ -672,16 +699,16 @@ impl<T> Copy for Corners<T> where T: Copy + Clone + Default + Debug {}
 pub struct Pixels(pub(crate) f32);
 
 impl std::ops::Div for Pixels {
-    type Output = Self;
+    type Output = f32;
 
     fn div(self, rhs: Self) -> Self::Output {
-        Self(self.0 / rhs.0)
+        self.0 / rhs.0
     }
 }
 
 impl std::ops::DivAssign for Pixels {
     fn div_assign(&mut self, rhs: Self) {
-        self.0 /= rhs.0;
+        *self = Self(self.0 / rhs.0);
     }
 }
 
@@ -732,14 +759,6 @@ impl MulAssign<f32> for Pixels {
 impl Pixels {
     pub const MAX: Pixels = Pixels(f32::MAX);
 
-    pub fn as_usize(&self) -> usize {
-        self.0 as usize
-    }
-
-    pub fn as_isize(&self) -> isize {
-        self.0 as isize
-    }
-
     pub fn floor(&self) -> Self {
         Self(self.0.floor())
     }

crates/gpui2/src/styled.rs 🔗

@@ -1,14 +1,19 @@
 use crate::{
     self as gpui, hsla, point, px, relative, rems, AlignItems, CursorStyle, DefiniteLength,
     Display, Fill, FlexDirection, Hsla, JustifyContent, Length, Position, Rems, SharedString,
-    StyleRefinement, Visibility,
+    Style, StyleRefinement, Visibility,
 };
 use crate::{BoxShadow, TextStyleRefinement};
+use refineable::Refineable;
 use smallvec::smallvec;
 
 pub trait Styled {
     fn style(&mut self) -> &mut StyleRefinement;
 
+    fn computed_style(&mut self) -> Style {
+        Style::default().refined(self.style().clone())
+    }
+
     gpui2_macros::style_helpers!();
 
     /// Sets the size of the element to the full width and height.

crates/gpui2/src/window.rs 🔗

@@ -559,6 +559,12 @@ impl<'a> WindowContext<'a> {
             .request_measured_layout(style, rem_size, measure)
     }
 
+    pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size<AvailableSpace>) {
+        self.window
+            .layout_engine
+            .compute_layout(layout_id, available_space)
+    }
+
     /// Obtain the bounds computed for the given LayoutId relative to the window. This method should not
     /// be invoked until the paint phase begins, and will usually be invoked by GPUI itself automatically
     /// in order to pass your element its `Bounds` automatically.

crates/picker2/src/picker2.rs 🔗

@@ -20,24 +20,28 @@
 
 use std::ops::Range;
 
-use gpui::{div, AppContext, Component, Div, Element, ParentElement, Render, ViewContext};
-
-pub struct Picker<D> {
-    delegate: D,
-    //     query_editor: ViewHandle<Editor>,
-    //     list_state: UniformListState,
-    //     max_size: Vector2F,
-    //     theme: Arc<Mutex<Box<dyn Fn(&theme::Theme) -> theme::Picker>>>,
-    //     confirmed: bool,
-    //     pending_update_matches: Option<Task<Option<()>>>,
-    //     confirm_on_update: Option<bool>,
-    //     has_focus: bool,
-}
+use gpui::{
+    div, list, AppContext, Component, Div, Element, ElementId, ParentElement, Render, ViewContext,
+};
+
+// pub struct Picker<D> {
+//     delegate: D,
+//     query_editor: ViewHandle<Editor>,
+//     list_state: UniformListState,
+//     max_size: Vector2F,
+//     theme: Arc<Mutex<Box<dyn Fn(&theme::Theme) -> theme::Picker>>>,
+//     confirmed: bool,
+//     pending_update_matches: Option<Task<Option<()>>>,
+//     confirm_on_update: Option<bool>,
+//     has_focus: bool,
+// }
 
 pub trait PickerDelegate: Sized + 'static {
-    type ListItem: Element<Picker<Self>>;
+    type ListItem: Component<Self>;
     //     fn placeholder_text(&self) -> Arc<str>;
-    //     fn match_count(&self) -> usize;
+
+    fn match_count(&self, picker_id: ElementId) -> usize;
+
     //     fn selected_index(&self) -> usize;
     //     fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>);
     //     fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()>;
@@ -51,7 +55,8 @@ pub trait PickerDelegate: Sized + 'static {
         active: bool,
         hovered: bool,
         selected: bool,
-        cx: &mut ViewContext<Picker<Self>>,
+        picker_id: ElementId,
+        cx: &mut ViewContext<Self>,
     ) -> Self::ListItem;
 
     //     fn center_selection_after_match_updates(&self) -> bool {
@@ -75,29 +80,35 @@ pub trait PickerDelegate: Sized + 'static {
 //     type Event = PickerEvent;
 // }
 
-impl<D: PickerDelegate> Render for Picker<D> {
-    type Element = Div<Self>;
+#[derive(Component)]
+pub struct Picker<V: PickerDelegate> {
+    id: ElementId,
+    phantom: std::marker::PhantomData<V>,
+}
+
+impl<V: PickerDelegate> Picker<V> {
+    pub fn new(id: impl Into<ElementId>) -> Self {
+        Self {
+            id: id.into(),
+            phantom: std::marker::PhantomData,
+        }
+    }
+}
 
-    fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
-        div().child(list(
+impl<V: 'static + PickerDelegate> Picker<V> {
+    pub fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+        div().id(self.id.clone()).child(list(
             "candidates",
-            |this: &mut Picker<D>, visible_range, cx| {
+            view.match_count(self.id.clone()),
+            move |this: &mut V, visible_range, cx| {
                 visible_range
-                    .into_iter()
-                    .map(|ix| this.delegate.render_match(ix, false, false, false, cx))
+                    .map(|ix| this.render_match(ix, false, false, false, self.id.clone(), cx))
+                    .collect()
             },
         ))
     }
 }
 
-fn list<'a, D: PickerDelegate, F, I>(id: &'static str, f: F) -> Div<Picker<D>>
-where
-    F: FnOnce(&'a mut Picker<D>, Range<usize>, &'a mut ViewContext<Picker<D>>) -> I,
-    I: 'a + Iterator<Item = D::ListItem>,
-{
-    todo!();
-}
-
 // impl<D: PickerDelegate> View for Picker<D> {
 //     fn ui_name() -> &'static str {
 //         "Picker"
@@ -213,7 +224,7 @@ where
 //         cx.add_action(Self::cancel);
 //     }
 
-//     pub fn new(delegate: D, cx: &mut ViewContext<Self>) -> Self {
+// pub fn new(delegate: D, cx: &mut ViewContext<Self>) -> Self {
 //         let theme = Arc::new(Mutex::new(
 //             Box::new(|theme: &theme::Theme| theme.picker.clone())
 //                 as Box<dyn Fn(&theme::Theme) -> theme::Picker>,
@@ -247,7 +258,8 @@ where
 //         };
 //         this.update_matches(String::new(), cx);
 //         this
-//     }
+// Self { delegate }
+// }
 
 //     pub fn with_max_size(mut self, width: f32, height: f32) -> Self {
 //         self.max_size = vec2f(width, height);

crates/storybook2/Cargo.toml 🔗

@@ -27,6 +27,7 @@ theme = { path = "../theme" }
 theme2 = { path = "../theme2" }
 ui = { package = "ui2", path = "../ui2", features = ["stories"] }
 util = { path = "../util" }
+picker = { package = "picker2", path = "../picker2" }
 
 [dev-dependencies]
 gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }

crates/storybook2/src/stories.rs 🔗

@@ -1,6 +1,7 @@
 mod colors;
 mod focus;
 mod kitchen_sink;
+mod picker;
 mod scroll;
 mod text;
 mod z_index;
@@ -8,6 +9,7 @@ mod z_index;
 pub use colors::*;
 pub use focus::*;
 pub use kitchen_sink::*;
+pub use picker::*;
 pub use scroll::*;
 pub use text::*;
 pub use z_index::*;

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

@@ -0,0 +1,40 @@
+use gpui::{div, Div, ParentElement, Render, View, VisualContext, WindowContext};
+use picker::{Picker, PickerDelegate};
+
+pub struct PickerStory {
+    //     picker: View<Picker<PickerStoryDelegate>>,
+}
+
+impl PickerDelegate for PickerStory {
+    type ListItem = Div<Self>;
+
+    fn match_count(&self, picker_id: gpui::ElementId) -> usize {
+        0
+    }
+
+    fn render_match(
+        &self,
+        ix: usize,
+        active: bool,
+        hovered: bool,
+        selected: bool,
+        picker_id: gpui::ElementId,
+        cx: &mut gpui::ViewContext<Self>,
+    ) -> Self::ListItem {
+        todo!()
+    }
+}
+
+impl PickerStory {
+    pub fn new(cx: &mut WindowContext) -> View<Self> {
+        cx.build_view(|cx| PickerStory {})
+    }
+}
+
+impl Render for PickerStory {
+    type Element = Div<Self>;
+
+    fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
+        div().child(Picker::new("picker_story"))
+    }
+}

crates/storybook2/src/story_selector.rs 🔗

@@ -51,6 +51,7 @@ pub enum ComponentStory {
     TrafficLights,
     Workspace,
     ZIndex,
+    Picker,
 }
 
 impl ComponentStory {
@@ -94,6 +95,7 @@ impl ComponentStory {
             Self::TrafficLights => cx.build_view(|_| ui::TrafficLightsStory).into(),
             Self::Workspace => ui::WorkspaceStory::view(cx).into(),
             Self::ZIndex => cx.build_view(|_| ZIndexStory).into(),
+            Self::Picker => PickerStory::new(cx).into(),
         }
     }
 }

crates/terminal2/src/mappings/mouse.rs 🔗

@@ -186,9 +186,9 @@ pub fn mouse_side(
 }
 
 pub fn grid_point(pos: Point<Pixels>, cur_size: TerminalSize, display_offset: usize) -> AlacPoint {
-    let col = GridCol((pos.x / cur_size.cell_width).as_usize());
+    let col = GridCol((cur_size.cell_width / pos.x) as usize);
     let col = min(col, cur_size.last_column());
-    let line = (pos.y / cur_size.line_height).as_isize() as i32;
+    let line = (cur_size.line_height / pos.y) as i32;
     let line = min(line, cur_size.bottommost_line().0);
     AlacPoint::new(GridLine(line - display_offset as i32), col)
 }

crates/terminal2/src/terminal2.rs 🔗

@@ -1121,8 +1121,7 @@ impl Terminal {
                     None => return,
                 };
 
-                let scroll_lines =
-                    (scroll_delta / self.last_content.size.line_height).as_isize() as i32;
+                let scroll_lines = (scroll_delta / self.last_content.size.line_height) as i32;
 
                 self.events
                     .push_back(InternalEvent::Scroll(AlacScroll::Delta(scroll_lines)));
@@ -1280,11 +1279,11 @@ impl Terminal {
             }
             /* Calculate the appropriate scroll lines */
             TouchPhase::Moved => {
-                let old_offset = (self.scroll_px / line_height).as_isize() as i32;
+                let old_offset = (self.scroll_px / line_height) as i32;
 
                 self.scroll_px += e.delta.pixel_delta(line_height).y * scroll_multiplier;
 
-                let new_offset = (self.scroll_px / line_height).as_isize() as i32;
+                let new_offset = (self.scroll_px / line_height) as i32;
 
                 // Whenever we hit the edges, reset our stored scroll to 0
                 // so we can respond to changes in direction quickly
@@ -1396,9 +1395,9 @@ fn all_search_matches<'a, T>(
 }
 
 fn content_index_for_mouse(pos: Point<Pixels>, size: &TerminalSize) -> usize {
-    let col = (pos.x / size.cell_width()).round().as_usize();
+    let col = (pos.x / size.cell_width()).round() as usize;
     let clamped_col = min(col, size.columns() - 1);
-    let row = (pos.y / size.line_height()).round().as_usize();
+    let row = (pos.y / size.line_height()).round() as usize;
     let clamped_row = min(row, size.screen_lines() - 1);
     clamped_row * size.columns() + clamped_col
 }