From 3f7dc5951292d2fd717dde46891e7695a07324e5 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Mon, 6 Nov 2023 12:33:20 -0800 Subject: [PATCH] Snapshot for kirill --- Cargo.lock | 17 ++ Cargo.toml | 1 + crates/gpui2/src/elements.rs | 1 + crates/gpui2/src/elements/img.rs | 2 + crates/gpui2/src/elements/list.rs | 91 +++++++ crates/picker2/Cargo.toml | 28 +++ crates/picker2/src/picker2.rs | 400 ++++++++++++++++++++++++++++++ 7 files changed, 540 insertions(+) create mode 100644 crates/gpui2/src/elements/list.rs create mode 100644 crates/picker2/Cargo.toml create mode 100644 crates/picker2/src/picker2.rs diff --git a/Cargo.lock b/Cargo.lock index 6d5fdc67d7..3a888cfd23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6006,6 +6006,23 @@ dependencies = [ "workspace", ] +[[package]] +name = "picker2" +version = "0.1.0" +dependencies = [ + "ctor", + "editor2", + "env_logger 0.9.3", + "gpui2", + "menu2", + "parking_lot 0.11.2", + "serde_json", + "settings2", + "theme2", + "util", + "workspace2", +] + [[package]] name = "pico-args" version = "0.4.2" diff --git a/Cargo.toml b/Cargo.toml index 6245889530..853053f2c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,6 +67,7 @@ members = [ "crates/notifications", "crates/outline", "crates/picker", + "crates/picker2", "crates/plugin", "crates/plugin_macros", "crates/plugin_runtime", diff --git a/crates/gpui2/src/elements.rs b/crates/gpui2/src/elements.rs index 83c27b8a3b..dc8baf4ca5 100644 --- a/crates/gpui2/src/elements.rs +++ b/crates/gpui2/src/elements.rs @@ -1,5 +1,6 @@ mod div; mod img; +mod list; mod svg; mod text; diff --git a/crates/gpui2/src/elements/img.rs b/crates/gpui2/src/elements/img.rs index a35436d74e..637bcd78e6 100644 --- a/crates/gpui2/src/elements/img.rs +++ b/crates/gpui2/src/elements/img.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use crate::{ div, AnyElement, BorrowWindow, Bounds, Component, Div, DivState, Element, ElementFocus, ElementId, ElementInteraction, FocusDisabled, FocusEnabled, FocusListeners, Focusable, diff --git a/crates/gpui2/src/elements/list.rs b/crates/gpui2/src/elements/list.rs new file mode 100644 index 0000000000..647d59586e --- /dev/null +++ b/crates/gpui2/src/elements/list.rs @@ -0,0 +1,91 @@ +use std::ops::Range; + +use smallvec::SmallVec; + +use crate::{AnyElement, Component, Element, ElementId, StyleRefinement, 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>( + id: Id, + f: impl 'static + FnOnce(&'a mut V, Range, &'a mut ViewContext) -> Iter, +) -> List +where + Id: Into, + V: 'static, + Iter: 'a + Iterator, + C: Component, +{ + List { + id: id.into(), + render_items: Box::new(|view, visible_range, cx| { + f(view, visible_range, cx) + .map(|element| element.render()) + .collect() + }), + } +} + +struct List { + id: ElementId, + render_items: Box< + dyn for<'a> FnOnce( + &'a mut V, + Range, + &'a mut ViewContext, + ) -> SmallVec<[AnyElement; 64]>, + >, +} + +impl List {} + +// #[derive(Debug)] +// pub enum ScrollTarget { +// Show(usize), +// Center(usize), +// } + +#[derive(Default)] +struct ListState { + scroll_top: f32, + style: StyleRefinement, + // todo + // scroll_to: Option, +} +impl Element for List { + type ElementState = ListState; + + fn id(&self) -> Option { + Some(self.id) + } + + fn initialize( + &mut self, + _: &mut V, + element_state: Option, + _: &mut crate::ViewContext, + ) -> Self::ElementState { + let element_state = element_state.unwrap_or_default(); + element_state + } + + fn layout( + &mut self, + view_state: &mut V, + element_state: &mut Self::ElementState, + cx: &mut crate::ViewContext, + ) -> crate::LayoutId { + todo!() + } + + fn paint( + &mut self, + bounds: crate::Bounds, + view_state: &mut V, + element_state: &mut Self::ElementState, + cx: &mut crate::ViewContext, + ) { + } +} diff --git a/crates/picker2/Cargo.toml b/crates/picker2/Cargo.toml new file mode 100644 index 0000000000..8d88c25366 --- /dev/null +++ b/crates/picker2/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "picker2" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/picker2.rs" +doctest = false + +[dependencies] +editor = { package = "editor2", path = "../editor2" } +gpui = { package = "gpui2", path = "../gpui2" } +menu = { package = "menu2", path = "../menu2" } +settings = { package = "settings2", path = "../settings2" } +util = { path = "../util" } +theme = { package = "theme2", path = "../theme2" } +workspace = { package = "workspace2", path = "../workspace2" } + +parking_lot.workspace = true + +[dev-dependencies] +editor = { package = "editor2", path = "../editor2", features = ["test-support"] } +gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } +serde_json.workspace = true +workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] } +ctor.workspace = true +env_logger.workspace = true diff --git a/crates/picker2/src/picker2.rs b/crates/picker2/src/picker2.rs new file mode 100644 index 0000000000..c6c5ee3e41 --- /dev/null +++ b/crates/picker2/src/picker2.rs @@ -0,0 +1,400 @@ +// use editor::Editor; +// use gpui::{ +// elements::*, +// geometry::vector::{vec2f, Vector2F}, +// keymap_matcher::KeymapContext, +// platform::{CursorStyle, MouseButton}, +// AnyElement, AnyViewHandle, AppContext, Axis, Entity, MouseState, Task, View, ViewContext, +// ViewHandle, +// }; +// use menu::{Cancel, Confirm, SecondaryConfirm, SelectFirst, SelectLast, SelectNext, SelectPrev}; +// use parking_lot::Mutex; +// use std::{cmp, sync::Arc}; +// use util::ResultExt; +// use workspace::Modal; + +// #[derive(Clone, Copy)] +// pub enum PickerEvent { +// Dismiss, +// } + +use std::ops::Range; + +use gpui::{div, AppContext, Component, Div, Element, 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 trait PickerDelegate: Sized + 'static { + type ListItem: Element>; + // fn placeholder_text(&self) -> Arc; + // fn match_count(&self) -> 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<()>; + // fn confirm(&mut self, secondary: bool, cx: &mut ViewContext>); + // fn dismissed(&mut self, cx: &mut ViewContext>); + + // todo!("rename to render_candidate?") + fn render_match( + &self, + ix: usize, + active: bool, + hovered: bool, + selected: bool, + cx: &mut ViewContext>, + ) -> Self::ListItem; + + // fn center_selection_after_match_updates(&self) -> bool { + // false + // } + // fn render_header( + // &self, + // _cx: &mut ViewContext>, + // ) -> Option>> { + // None + // } + // fn render_footer( + // &self, + // _cx: &mut ViewContext>, + // ) -> Option>> { + // None + // } +} + +// impl Entity for Picker { +// type Event = PickerEvent; +// } + +impl Render for Picker { + type Element = Div; + + 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)) + }, + )) + } +} + +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 View for Picker { +// fn ui_name() -> &'static str { +// "Picker" +// } + +// fn render(&mut self, cx: &mut ViewContext) -> AnyElement { +// let theme = (self.theme.lock())(theme::current(cx).as_ref()); +// let query = self.query(cx); +// let match_count = self.delegate.match_count(); + +// let container_style; +// let editor_style; +// if query.is_empty() && match_count == 0 { +// container_style = theme.empty_container; +// editor_style = theme.empty_input_editor.container; +// } else { +// container_style = theme.container; +// editor_style = theme.input_editor.container; +// }; + +// Flex::new(Axis::Vertical) +// .with_child( +// ChildView::new(&self.query_editor, cx) +// .contained() +// .with_style(editor_style), +// ) +// .with_children(self.delegate.render_header(cx)) +// .with_children(if match_count == 0 { +// if query.is_empty() { +// None +// } else { +// Some( +// Label::new("No matches", theme.no_matches.label.clone()) +// .contained() +// .with_style(theme.no_matches.container) +// .into_any(), +// ) +// } +// } else { +// Some( +// UniformList::new( +// self.list_state.clone(), +// match_count, +// cx, +// move |this, mut range, items, cx| { +// let selected_ix = this.delegate.selected_index(); +// range.end = cmp::min(range.end, this.delegate.match_count()); +// items.extend(range.map(move |ix| { +// MouseEventHandler::new::(ix, cx, |state, cx| { +// this.delegate.render_match(ix, state, ix == selected_ix, cx) +// }) +// // Capture mouse events +// .on_down(MouseButton::Left, |_, _, _| {}) +// .on_up(MouseButton::Left, |_, _, _| {}) +// .on_click(MouseButton::Left, move |click, picker, cx| { +// picker.select_index(ix, click.cmd, cx); +// }) +// .with_cursor_style(CursorStyle::PointingHand) +// .into_any() +// })); +// }, +// ) +// .contained() +// .with_margin_top(6.0) +// .flex(1., false) +// .into_any(), +// ) +// }) +// .with_children(self.delegate.render_footer(cx)) +// .contained() +// .with_style(container_style) +// .constrained() +// .with_max_width(self.max_size.x()) +// .with_max_height(self.max_size.y()) +// .into_any_named("picker") +// } + +// fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) { +// Self::reset_to_default_keymap_context(keymap); +// keymap.add_identifier("menu"); +// } + +// fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { +// self.has_focus = true; +// if cx.is_self_focused() { +// cx.focus(&self.query_editor); +// } +// } + +// fn focus_out(&mut self, _: AnyViewHandle, _: &mut ViewContext) { +// self.has_focus = false; +// } +// } + +// impl Modal for Picker { +// fn has_focus(&self) -> bool { +// self.has_focus +// } + +// fn dismiss_on_event(event: &Self::Event) -> bool { +// matches!(event, PickerEvent::Dismiss) +// } +// } + +// impl Picker { +// pub fn init(cx: &mut AppContext) { +// cx.add_action(Self::select_first); +// cx.add_action(Self::select_last); +// cx.add_action(Self::select_next); +// cx.add_action(Self::select_prev); +// cx.add_action(Self::confirm); +// cx.add_action(Self::secondary_confirm); +// cx.add_action(Self::cancel); +// } + +// 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>, +// )); +// let placeholder_text = delegate.placeholder_text(); +// let query_editor = cx.add_view({ +// let picker_theme = theme.clone(); +// |cx| { +// let mut editor = Editor::single_line( +// Some(Arc::new(move |theme| { +// (picker_theme.lock())(theme).input_editor.clone() +// })), +// cx, +// ); +// editor.set_placeholder_text(placeholder_text, cx); +// editor +// } +// }); +// cx.subscribe(&query_editor, Self::on_query_editor_event) +// .detach(); +// let mut this = Self { +// query_editor, +// list_state: Default::default(), +// delegate, +// max_size: vec2f(540., 420.), +// theme, +// confirmed: false, +// pending_update_matches: None, +// confirm_on_update: None, +// has_focus: false, +// }; +// this.update_matches(String::new(), cx); +// this +// } + +// pub fn with_max_size(mut self, width: f32, height: f32) -> Self { +// self.max_size = vec2f(width, height); +// self +// } + +// pub fn with_theme(self, theme: F) -> Self +// where +// F: 'static + Fn(&theme::Theme) -> theme::Picker, +// { +// *self.theme.lock() = Box::new(theme); +// self +// } + +// pub fn delegate(&self) -> &D { +// &self.delegate +// } + +// pub fn delegate_mut(&mut self) -> &mut D { +// &mut self.delegate +// } + +// pub fn query(&self, cx: &AppContext) -> String { +// self.query_editor.read(cx).text(cx) +// } + +// pub fn set_query(&self, query: impl Into>, cx: &mut ViewContext) { +// self.query_editor +// .update(cx, |editor, cx| editor.set_text(query, cx)); +// } + +// fn on_query_editor_event( +// &mut self, +// _: ViewHandle, +// event: &editor::Event, +// cx: &mut ViewContext, +// ) { +// match event { +// editor::Event::BufferEdited { .. } => self.update_matches(self.query(cx), cx), +// editor::Event::Blurred if !self.confirmed => { +// self.dismiss(cx); +// } +// _ => {} +// } +// } + +// pub fn update_matches(&mut self, query: String, cx: &mut ViewContext) { +// let update = self.delegate.update_matches(query, cx); +// self.matches_updated(cx); +// self.pending_update_matches = Some(cx.spawn(|this, mut cx| async move { +// update.await; +// this.update(&mut cx, |this, cx| { +// this.matches_updated(cx); +// }) +// .log_err() +// })); +// } + +// fn matches_updated(&mut self, cx: &mut ViewContext) { +// let index = self.delegate.selected_index(); +// let target = if self.delegate.center_selection_after_match_updates() { +// ScrollTarget::Center(index) +// } else { +// ScrollTarget::Show(index) +// }; +// self.list_state.scroll_to(target); +// self.pending_update_matches = None; +// if let Some(secondary) = self.confirm_on_update.take() { +// self.confirmed = true; +// self.delegate.confirm(secondary, cx) +// } +// cx.notify(); +// } + +// pub fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext) { +// if self.delegate.match_count() > 0 { +// self.delegate.set_selected_index(0, cx); +// self.list_state.scroll_to(ScrollTarget::Show(0)); +// } + +// cx.notify(); +// } + +// pub fn select_index(&mut self, index: usize, cmd: bool, cx: &mut ViewContext) { +// if self.delegate.match_count() > 0 { +// self.confirmed = true; +// self.delegate.set_selected_index(index, cx); +// self.delegate.confirm(cmd, cx); +// } +// } + +// pub fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext) { +// let match_count = self.delegate.match_count(); +// if match_count > 0 { +// let index = match_count - 1; +// self.delegate.set_selected_index(index, cx); +// self.list_state.scroll_to(ScrollTarget::Show(index)); +// } +// cx.notify(); +// } + +// pub fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext) { +// let next_index = self.delegate.selected_index() + 1; +// if next_index < self.delegate.match_count() { +// self.delegate.set_selected_index(next_index, cx); +// self.list_state.scroll_to(ScrollTarget::Show(next_index)); +// } + +// cx.notify(); +// } + +// pub fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext) { +// let mut selected_index = self.delegate.selected_index(); +// if selected_index > 0 { +// selected_index -= 1; +// self.delegate.set_selected_index(selected_index, cx); +// self.list_state +// .scroll_to(ScrollTarget::Show(selected_index)); +// } + +// cx.notify(); +// } + +// pub fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext) { +// if self.pending_update_matches.is_some() { +// self.confirm_on_update = Some(false) +// } else { +// self.confirmed = true; +// self.delegate.confirm(false, cx); +// } +// } + +// pub fn secondary_confirm(&mut self, _: &SecondaryConfirm, cx: &mut ViewContext) { +// if self.pending_update_matches.is_some() { +// self.confirm_on_update = Some(true) +// } else { +// self.confirmed = true; +// self.delegate.confirm(true, cx); +// } +// } + +// fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext) { +// self.dismiss(cx); +// } + +// fn dismiss(&mut self, cx: &mut ViewContext) { +// cx.emit(PickerEvent::Dismiss); +// self.delegate.dismissed(cx); +// } +// }