Cargo.lock 🔗
@@ -8542,6 +8542,7 @@ dependencies = [
"gpui2",
"itertools 0.11.0",
"log",
+ "picker2",
"rust-embed",
"serde",
"settings2",
Mikayla created
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(-)
@@ -8542,6 +8542,7 @@ dependencies = [
"gpui2",
"itertools 0.11.0",
"log",
+ "picker2",
"rust-embed",
"serde",
"settings2",
@@ -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,
@@ -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")
}
}
}
@@ -6,5 +6,6 @@ mod text;
pub use div::*;
pub use img::*;
+pub use list::*;
pub use svg::*;
pub use text::*;
@@ -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)
}
}
@@ -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())
}
@@ -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.
@@ -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.
@@ -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);
@@ -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"] }
@@ -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::*;
@@ -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"))
+ }
+}
@@ -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(),
}
}
}
@@ -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)
}
@@ -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
}