diff --git a/Cargo.lock b/Cargo.lock index 3a888cfd23..98feaf9f61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8542,6 +8542,7 @@ dependencies = [ "gpui2", "itertools 0.11.0", "log", + "picker2", "rust-embed", "serde", "settings2", diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 6420d1e6cd..4e42124c02 100644 --- a/crates/editor2/src/element.rs +++ b/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, diff --git a/crates/gpui2/src/app/entity_map.rs b/crates/gpui2/src/app/entity_map.rs index e626f8c409..588091c7a0 100644 --- a/crates/gpui2/src/app/entity_map.rs +++ b/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") } } } diff --git a/crates/gpui2/src/elements.rs b/crates/gpui2/src/elements.rs index dc8baf4ca5..3ebe5bf5d1 100644 --- a/crates/gpui2/src/elements.rs +++ b/crates/gpui2/src/elements.rs @@ -6,5 +6,6 @@ mod text; pub use div::*; pub use img::*; +pub use list::*; pub use svg::*; pub use text::*; diff --git a/crates/gpui2/src/elements/list.rs b/crates/gpui2/src/elements/list.rs index 647d59586e..01a64f7bd1 100644 --- a/crates/gpui2/src/elements/list.rs +++ b/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: Id, - f: impl 'static + FnOnce(&'a mut V, Range, &'a mut ViewContext) -> Iter, + item_count: usize, + f: impl 'static + Fn(&mut V, Range, &mut ViewContext) -> SmallVec<[C; 64]>, ) -> List where Id: Into, V: 'static, - Iter: 'a + Iterator, C: Component, { 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 { +pub struct List { id: ElementId, + style: StyleRefinement, + item_count: usize, render_items: Box< - dyn for<'a> FnOnce( + dyn for<'a> Fn( &'a mut V, Range, &'a mut ViewContext, @@ -39,8 +47,6 @@ struct List { >, } -impl List {} - // #[derive(Debug)] // pub enum ScrollTarget { // Show(usize), @@ -48,24 +54,30 @@ impl List {} // } #[derive(Default)] -struct ListState { +pub struct ListState { scroll_top: f32, - style: StyleRefinement, // todo // scroll_to: Option, } + +impl Styled for List { + fn style(&mut self) -> &mut StyleRefinement { + &mut self.style + } +} + impl Element for List { type ElementState = ListState; fn id(&self) -> Option { - Some(self.id) + Some(self.id.clone()) } fn initialize( &mut self, _: &mut V, element_state: Option, - _: &mut crate::ViewContext, + _: &mut ViewContext, ) -> Self::ElementState { let element_state = element_state.unwrap_or_default(); element_state @@ -73,11 +85,11 @@ impl Element for List { fn layout( &mut self, - view_state: &mut V, - element_state: &mut Self::ElementState, - cx: &mut crate::ViewContext, - ) -> crate::LayoutId { - todo!() + _view_state: &mut V, + _element_state: &mut Self::ElementState, + cx: &mut ViewContext, + ) -> LayoutId { + cx.request_layout(&self.computed_style(), None) } fn paint( @@ -85,7 +97,62 @@ impl Element for List { bounds: crate::Bounds, view_state: &mut V, element_state: &mut Self::ElementState, - cx: &mut crate::ViewContext, + cx: &mut ViewContext, ) { + 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 List { + fn measure_item_height( + &self, + view_state: &mut V, + list_bounds: Bounds, + cx: &mut ViewContext, + ) -> 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 Component for List { + fn render(self) -> AnyElement { + AnyElement::new(self) } } diff --git a/crates/gpui2/src/geometry.rs b/crates/gpui2/src/geometry.rs index d6755a5397..a9df756932 100644 --- a/crates/gpui2/src/geometry.rs +++ b/crates/gpui2/src/geometry.rs @@ -259,6 +259,24 @@ impl From> for Size { } } +impl From> for Size { + fn from(size: Size) -> Self { + Size { + width: size.width.into(), + height: size.height.into(), + } + } +} + +impl From> for Size { + fn from(size: Size) -> Self { + Size { + width: size.width.into(), + height: size.height.into(), + } + } +} + impl Size { pub fn full() -> Self { Self { @@ -541,6 +559,15 @@ impl Edges { left: px(0.).into(), } } + + pub fn to_pixels(&self, parent_size: Size, rem_size: Pixels) -> Edges { + 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 { @@ -672,16 +699,16 @@ impl Copy for Corners 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 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()) } diff --git a/crates/gpui2/src/styled.rs b/crates/gpui2/src/styled.rs index a272ab95b1..f45e6a219e 100644 --- a/crates/gpui2/src/styled.rs +++ b/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. diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 9cab40082b..880fc6c6cb 100644 --- a/crates/gpui2/src/window.rs +++ b/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) { + 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. diff --git a/crates/picker2/src/picker2.rs b/crates/picker2/src/picker2.rs index c6c5ee3e41..9b4f8778e2 100644 --- a/crates/picker2/src/picker2.rs +++ b/crates/picker2/src/picker2.rs @@ -20,24 +20,28 @@ use std::ops::Range; -use gpui::{div, AppContext, Component, Div, Element, ParentElement, Render, ViewContext}; +use gpui::{ + div, list, AppContext, Component, Div, Element, ElementId, ParentElement, Render, ViewContext, +}; -pub struct Picker { - delegate: D, - // query_editor: ViewHandle, - // list_state: UniformListState, - // max_size: Vector2F, - // theme: Arc theme::Picker>>>, - // confirmed: bool, - // pending_update_matches: Option>>, - // confirm_on_update: Option, - // has_focus: bool, -} +// pub struct Picker { +// delegate: D, +// query_editor: ViewHandle, +// list_state: UniformListState, +// max_size: Vector2F, +// theme: Arc theme::Picker>>>, +// confirmed: bool, +// pending_update_matches: Option>>, +// confirm_on_update: Option, +// has_focus: bool, +// } pub trait PickerDelegate: Sized + 'static { - type ListItem: Element>; + type ListItem: Component; // fn placeholder_text(&self) -> Arc; - // 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>); // fn update_matches(&mut self, query: String, cx: &mut ViewContext>) -> Task<()>; @@ -51,7 +55,8 @@ pub trait PickerDelegate: Sized + 'static { active: bool, hovered: bool, selected: bool, - cx: &mut ViewContext>, + picker_id: ElementId, + cx: &mut ViewContext, ) -> Self::ListItem; // fn center_selection_after_match_updates(&self) -> bool { @@ -75,27 +80,33 @@ pub trait PickerDelegate: Sized + 'static { // type Event = PickerEvent; // } -impl Render for Picker { - type Element = Div; +#[derive(Component)] +pub struct Picker { + id: ElementId, + phantom: std::marker::PhantomData, +} - fn render(&mut self, cx: &mut gpui::ViewContext) -> Self::Element { - div().child(list( - "candidates", - |this: &mut Picker, visible_range, cx| { - visible_range - .into_iter() - .map(|ix| this.delegate.render_match(ix, false, false, false, cx)) - }, - )) +impl Picker { + pub fn new(id: impl Into) -> Self { + Self { + id: id.into(), + phantom: std::marker::PhantomData, + } } } -fn list<'a, D: PickerDelegate, F, I>(id: &'static str, f: F) -> Div> -where - F: FnOnce(&'a mut Picker, Range, &'a mut ViewContext>) -> I, - I: 'a + Iterator, -{ - todo!(); +impl Picker { + pub fn render(self, view: &mut V, cx: &mut ViewContext) -> impl Component { + div().id(self.id.clone()).child(list( + "candidates", + view.match_count(self.id.clone()), + move |this: &mut V, visible_range, cx| { + visible_range + .map(|ix| this.render_match(ix, false, false, false, self.id.clone(), cx)) + .collect() + }, + )) + } } // impl View for Picker { @@ -213,7 +224,7 @@ where // cx.add_action(Self::cancel); // } -// pub fn new(delegate: D, cx: &mut ViewContext) -> Self { +// pub fn new(delegate: D, cx: &mut ViewContext) -> Self { // let theme = Arc::new(Mutex::new( // Box::new(|theme: &theme::Theme| theme.picker.clone()) // as Box 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); diff --git a/crates/storybook2/Cargo.toml b/crates/storybook2/Cargo.toml index 1f3a0b33cc..0e35a7a66f 100644 --- a/crates/storybook2/Cargo.toml +++ b/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"] } diff --git a/crates/storybook2/src/stories.rs b/crates/storybook2/src/stories.rs index 3d8a332fb9..2620e68d6c 100644 --- a/crates/storybook2/src/stories.rs +++ b/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::*; diff --git a/crates/storybook2/src/stories/picker.rs b/crates/storybook2/src/stories/picker.rs new file mode 100644 index 0000000000..de983e0326 --- /dev/null +++ b/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>, +} + +impl PickerDelegate for PickerStory { + type ListItem = Div; + + 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::ListItem { + todo!() + } +} + +impl PickerStory { + pub fn new(cx: &mut WindowContext) -> View { + cx.build_view(|cx| PickerStory {}) + } +} + +impl Render for PickerStory { + type Element = Div; + + fn render(&mut self, cx: &mut gpui::ViewContext) -> Self::Element { + div().child(Picker::new("picker_story")) + } +} diff --git a/crates/storybook2/src/story_selector.rs b/crates/storybook2/src/story_selector.rs index f59208ccb8..766c0dd51a 100644 --- a/crates/storybook2/src/story_selector.rs +++ b/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(), } } } diff --git a/crates/terminal2/src/mappings/mouse.rs b/crates/terminal2/src/mappings/mouse.rs index eac6ad17ff..edced3156f 100644 --- a/crates/terminal2/src/mappings/mouse.rs +++ b/crates/terminal2/src/mappings/mouse.rs @@ -186,9 +186,9 @@ pub fn mouse_side( } pub fn grid_point(pos: Point, 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) } diff --git a/crates/terminal2/src/terminal2.rs b/crates/terminal2/src/terminal2.rs index ba5c4815f2..3d06b48812 100644 --- a/crates/terminal2/src/terminal2.rs +++ b/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, 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 }