Cargo.lock 🔗
@@ -11352,6 +11352,7 @@ dependencies = [
"libc",
"log",
"lsp2",
+ "menu2",
"node_runtime",
"num_cpus",
"parking_lot 0.11.2",
Max Brunsfeld created
Cargo.lock | 1
crates/editor2/src/editor.rs | 72 ++-
crates/go_to_line2/src/go_to_line.rs | 391 +++++++++-----------
crates/gpui2/src/action.rs | 1
crates/gpui2/src/app.rs | 25 +
crates/gpui2/src/app/async_context.rs | 2
crates/gpui2/src/gpui2.rs | 2
crates/gpui2/src/interactive.rs | 4
crates/gpui2/src/styled.rs | 19
crates/gpui2/src/window.rs | 15
crates/menu2/src/menu2.rs | 5
crates/theme2/src/theme2.rs | 2
crates/ui2/src/components.rs | 2
crates/ui2/src/components/elevated_surface.rs | 28 +
crates/ui2/src/elevation.rs | 27 +
crates/workspace2/src/dock.rs | 8
crates/workspace2/src/modal_layer.rs | 103 +++--
crates/workspace2/src/toolbar.rs | 11
crates/workspace2/src/workspace2.rs | 19
crates/zed2/Cargo.toml | 1
crates/zed2/src/main.rs | 4
styles/src/style_tree/assistant.ts | 114 +++++-
styles/src/style_tree/status_bar.ts | 18
styles/src/themes/rose-pine/rose-pine-dawn.ts | 2
24 files changed, 536 insertions(+), 340 deletions(-)
@@ -11352,6 +11352,7 @@ dependencies = [
"libc",
"log",
"lsp2",
+ "menu2",
"node_runtime",
"num_cpus",
"parking_lot 0.11.2",
@@ -39,11 +39,10 @@ use futures::FutureExt;
use fuzzy::{StringMatch, StringMatchCandidate};
use git::diff_hunk_to_display;
use gpui::{
- action, actions, div, point, px, relative, size, AnyElement, AppContext, BackgroundExecutor,
- Bounds, ClipboardItem, Context, DispatchContext, Div, Element, Entity, EventEmitter,
- FocusHandle, FontStyle, FontWeight, HighlightStyle, Hsla, InputHandler, Model, Pixels,
- PlatformInputHandler, Render, Styled, Subscription, Task, TextStyle, View, ViewContext,
- VisualContext, WeakView, WindowContext,
+ action, actions, point, px, relative, rems, size, AnyElement, AppContext, BackgroundExecutor,
+ Bounds, ClipboardItem, Context, DispatchContext, EventEmitter, FocusHandle, FontFeatures,
+ FontStyle, FontWeight, HighlightStyle, Hsla, InputHandler, Model, Pixels, Render, Subscription,
+ Task, TextStyle, View, ViewContext, VisualContext, WeakView, WindowContext,
};
use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HoverState};
@@ -57,6 +56,7 @@ use language::{
Diagnostic, IndentKind, IndentSize, Language, LanguageRegistry, LanguageServerName,
OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId,
};
+use lazy_static::lazy_static;
use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState};
use lsp::{DiagnosticSeverity, Documentation, LanguageServerId};
use movement::TextLayoutDetails;
@@ -66,7 +66,7 @@ pub use multi_buffer::{
ToPoint,
};
use ordered_float::OrderedFloat;
-use parking_lot::RwLock;
+use parking_lot::{Mutex, RwLock};
use project::{FormatTrigger, Location, Project};
use rand::prelude::*;
use rpc::proto::*;
@@ -2180,14 +2180,14 @@ impl Editor {
// self.collaboration_hub = Some(hub);
// }
- // pub fn set_placeholder_text(
- // &mut self,
- // placeholder_text: impl Into<Arc<str>>,
- // cx: &mut ViewContext<Self>,
- // ) {
- // self.placeholder_text = Some(placeholder_text.into());
- // cx.notify();
- // }
+ pub fn set_placeholder_text(
+ &mut self,
+ placeholder_text: impl Into<Arc<str>>,
+ cx: &mut ViewContext<Self>,
+ ) {
+ self.placeholder_text = Some(placeholder_text.into());
+ cx.notify();
+ }
// pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut ViewContext<Self>) {
// self.cursor_shape = cursor_shape;
@@ -9418,18 +9418,42 @@ impl Render for Editor {
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let settings = ThemeSettings::get_global(cx);
- let text_style = TextStyle {
- color: cx.theme().colors().text,
- font_family: settings.buffer_font.family.clone(),
- font_features: settings.buffer_font.features,
- font_size: settings.buffer_font_size.into(),
- font_weight: FontWeight::NORMAL,
- font_style: FontStyle::Normal,
- line_height: relative(settings.buffer_line_height.value()),
- underline: None,
+ let text_style = match self.mode {
+ EditorMode::SingleLine => {
+ TextStyle {
+ color: cx.theme().colors().text,
+ font_family: "Zed Sans".into(), // todo!()
+ font_features: FontFeatures::default(),
+ font_size: rems(1.0).into(),
+ font_weight: FontWeight::NORMAL,
+ font_style: FontStyle::Normal,
+ line_height: relative(1.3).into(), // TODO relative(settings.buffer_line_height.value()),
+ underline: None,
+ }
+ }
+
+ EditorMode::AutoHeight { max_lines } => todo!(),
+
+ EditorMode::Full => TextStyle {
+ color: cx.theme().colors().text,
+ font_family: settings.buffer_font.family.clone(),
+ font_features: settings.buffer_font.features,
+ font_size: settings.buffer_font_size.into(),
+ font_weight: FontWeight::NORMAL,
+ font_style: FontStyle::Normal,
+ line_height: relative(settings.buffer_line_height.value()),
+ underline: None,
+ },
+ };
+
+ let background = match self.mode {
+ EditorMode::SingleLine => cx.theme().system().transparent,
+ EditorMode::AutoHeight { max_lines } => cx.theme().system().transparent,
+ EditorMode::Full => cx.theme().colors().editor_background,
};
+
EditorElement::new(EditorStyle {
- background: cx.theme().colors().editor_background,
+ background,
local_player: cx.theme().players().local(),
text: text_style,
scrollbar_width: px(12.),
@@ -1,220 +1,195 @@
-use gpui::{actions, div, px, red, AppContext, Div, Render, Styled, ViewContext, VisualContext};
-use workspace::ModalRegistry;
+use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Editor};
+use gpui::{
+ actions, div, AppContext, Div, EventEmitter, ParentElement, Render, SharedString,
+ StatefulInteractivity, StatelessInteractive, Styled, Subscription, View, ViewContext,
+ VisualContext, WindowContext,
+};
+use text::{Bias, Point};
+use theme::ActiveTheme;
+use ui::{h_stack, modal, v_stack, Label, LabelColor};
+use util::paths::FILE_ROW_COLUMN_DELIMITER;
+use workspace::{Modal, ModalEvent, Workspace};
actions!(Toggle);
pub fn init(cx: &mut AppContext) {
- cx.global_mut::<ModalRegistry>()
- .register_modal(Toggle, |_, cx| {
- // if let Some(editor) = workspace
- // .active_item(cx)
- // .and_then(|active_item| active_item.downcast::<Editor>())
- // {
- // cx.build_view(|cx| GoToLine::new(editor, cx))
- // }
- let view = cx.build_view(|_| GoToLine);
- view
- });
+ cx.observe_new_views(
+ |workspace: &mut Workspace, _: &mut ViewContext<Workspace>| {
+ workspace
+ .modal_layer()
+ .register_modal(Toggle, |workspace, cx| {
+ let editor = workspace
+ .active_item(cx)
+ .and_then(|active_item| active_item.downcast::<Editor>())?;
+
+ Some(cx.build_view(|cx| GoToLine::new(editor, cx)))
+ });
+ },
+ )
+ .detach();
+}
+
+pub struct GoToLine {
+ line_editor: View<Editor>,
+ active_editor: View<Editor>,
+ current_text: SharedString,
+ prev_scroll_position: Option<gpui::Point<f32>>,
+ _subscriptions: Vec<Subscription>,
+}
- // cx.add_action(GoToLine::toggle);
- // cx.add_action(GoToLine::confirm);
- // cx.add_action(GoToLine::cancel);
+pub enum Event {
+ Dismissed,
}
-pub struct GoToLine;
+impl EventEmitter for GoToLine {
+ type Event = Event;
+}
-impl Render for GoToLine {
- type Element = Div<Self>;
+impl GoToLine {
+ pub fn new(active_editor: View<Editor>, cx: &mut ViewContext<Self>) -> Self {
+ let line_editor = cx.build_view(|cx| {
+ let editor = Editor::single_line(cx);
+ editor.focus(cx);
+ editor
+ });
+ let line_editor_change = cx.subscribe(&line_editor, Self::on_line_editor_event);
+
+ let editor = active_editor.read(cx);
+ let cursor = editor.selections.last::<Point>(cx).head();
+ let last_line = editor.buffer().read(cx).snapshot(cx).max_point().row;
+ let scroll_position = active_editor.update(cx, |editor, cx| editor.scroll_position(cx));
+
+ let current_text = format!(
+ "line {} of {} (column {})",
+ cursor.row + 1,
+ last_line + 1,
+ cursor.column + 1,
+ );
+
+ Self {
+ line_editor,
+ active_editor,
+ current_text: current_text.into(),
+ prev_scroll_position: Some(scroll_position),
+ _subscriptions: vec![line_editor_change, cx.on_release(Self::release)],
+ }
+ }
+
+ fn release(&mut self, cx: &mut WindowContext) {
+ let scroll_position = self.prev_scroll_position.take();
+ self.active_editor.update(cx, |editor, cx| {
+ editor.focus(cx);
+ editor.highlight_rows(None);
+ if let Some(scroll_position) = scroll_position {
+ editor.set_scroll_position(scroll_position, cx);
+ }
+ cx.notify();
+ })
+ }
+
+ fn on_line_editor_event(
+ &mut self,
+ _: View<Editor>,
+ event: &editor::Event,
+ cx: &mut ViewContext<Self>,
+ ) {
+ match event {
+ // todo!() this isn't working...
+ editor::Event::Blurred => cx.emit(Event::Dismissed),
+ editor::Event::BufferEdited { .. } => self.highlight_current_line(cx),
+ _ => {}
+ }
+ }
- fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
- div().bg(red()).w(px(100.0)).h(px(100.0))
+ fn highlight_current_line(&mut self, cx: &mut ViewContext<Self>) {
+ if let Some(point) = self.point_from_query(cx) {
+ self.active_editor.update(cx, |active_editor, cx| {
+ let snapshot = active_editor.snapshot(cx).display_snapshot;
+ let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left);
+ let display_point = point.to_display_point(&snapshot);
+ let row = display_point.row();
+ active_editor.highlight_rows(Some(row..row + 1));
+ active_editor.request_autoscroll(Autoscroll::center(), cx);
+ });
+ cx.notify();
+ }
+ }
+
+ fn point_from_query(&self, cx: &ViewContext<Self>) -> Option<Point> {
+ let line_editor = self.line_editor.read(cx).text(cx);
+ let mut components = line_editor
+ .splitn(2, FILE_ROW_COLUMN_DELIMITER)
+ .map(str::trim)
+ .fuse();
+ let row = components.next().and_then(|row| row.parse::<u32>().ok())?;
+ let column = components.next().and_then(|col| col.parse::<u32>().ok());
+ Some(Point::new(
+ row.saturating_sub(1),
+ column.unwrap_or(0).saturating_sub(1),
+ ))
+ }
+
+ fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
+ cx.emit(Event::Dismissed);
+ }
+
+ fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
+ if let Some(point) = self.point_from_query(cx) {
+ self.active_editor.update(cx, |active_editor, cx| {
+ let snapshot = active_editor.snapshot(cx).display_snapshot;
+ let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left);
+ active_editor.change_selections(Some(Autoscroll::center()), cx, |s| {
+ s.select_ranges([point..point])
+ });
+ });
+ self.prev_scroll_position.take();
+ }
+
+ cx.emit(Event::Dismissed);
}
}
-// pub struct GoToLine {
-// //line_editor: View<Editor>,
-// active_editor: View<Editor>,
-// prev_scroll_position: Option<gpui::Point<Pixels>>,
-// cursor_point: Point,
-// max_point: Point,
-// has_focus: bool,
-// }
-
-// pub enum Event {
-// Dismissed,
-// }
-
-// impl GoToLine {
-// pub fn new(active_editor: View<Editor>, cx: &mut ViewContext<Self>) -> Self {
-// // let line_editor = cx.build_view(|cx| {
-// // Editor::single_line(
-// // Some(Arc::new(|theme| theme.picker.input_editor.clone())),
-// // cx,
-// // )
-// // });
-// // cx.subscribe(&line_editor, Self::on_line_editor_event)
-// // .detach();
-
-// let (scroll_position, cursor_point, max_point) = active_editor.update(cx, |editor, cx| {
-// let scroll_position = editor.scroll_position(cx);
-// let buffer = editor.buffer().read(cx).snapshot(cx);
-// (
-// Some(scroll_position),
-// editor.selections.newest(cx).head(),
-// buffer.max_point(),
-// )
-// });
-
-// cx.on_release(|_, on_release| {}).detach();
-
-// Self {
-// //line_editor,
-// active_editor,
-// prev_scroll_position: scroll_position,
-// cursor_point,
-// max_point,
-// has_focus: false,
-// }
-// }
-
-// fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
-// cx.emit(Event::Dismissed);
-// }
-
-// fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
-// self.prev_scroll_position.take();
-// if let Some(point) = self.point_from_query(cx) {
-// self.active_editor.update(cx, |active_editor, cx| {
-// let snapshot = active_editor.snapshot(cx).display_snapshot;
-// let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left);
-// active_editor.change_selections(Some(Autoscroll::center()), cx, |s| {
-// s.select_ranges([point..point])
-// });
-// });
-// }
-
-// cx.emit(Event::Dismissed);
-// }
-
-// fn on_line_editor_event(
-// &mut self,
-// _: View<Editor>,
-// event: &editor::Event,
-// cx: &mut ViewContext<Self>,
-// ) {
-// match event {
-// editor::Event::Blurred => cx.emit(Event::Dismissed),
-// editor::Event::BufferEdited { .. } => {
-// if let Some(point) = self.point_from_query(cx) {
-// // todo!()
-// // self.active_editor.update(cx, |active_editor, cx| {
-// // let snapshot = active_editor.snapshot(cx).display_snapshot;
-// // let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left);
-// // let display_point = point.to_display_point(&snapshot);
-// // let row = display_point.row();
-// // active_editor.highlight_rows(Some(row..row + 1));
-// // active_editor.request_autoscroll(Autoscroll::center(), cx);
-// // });
-// cx.notify();
-// }
-// }
-// _ => {}
-// }
-// }
-
-// fn point_from_query(&self, cx: &ViewContext<Self>) -> Option<Point> {
-// return None;
-// // todo!()
-// // let line_editor = self.line_editor.read(cx).text(cx);
-// // let mut components = line_editor
-// // .splitn(2, FILE_ROW_COLUMN_DELIMITER)
-// // .map(str::trim)
-// // .fuse();
-// // let row = components.next().and_then(|row| row.parse::<u32>().ok())?;
-// // let column = components.next().and_then(|col| col.parse::<u32>().ok());
-// // Some(Point::new(
-// // row.saturating_sub(1),
-// // column.unwrap_or(0).saturating_sub(1),
-// // ))
-// }
-// }
-
-// impl EventEmitter for GoToLine {
-// type Event = Event;
-// }
-
-// impl Entity for GoToLine {
-// fn release(&mut self, cx: &mut AppContext) {
-// let scroll_position = self.prev_scroll_position.take();
-// self.active_editor.window().update(cx, |cx| {
-// self.active_editor.update(cx, |editor, cx| {
-// editor.highlight_rows(None);
-// if let Some(scroll_position) = scroll_position {
-// editor.set_scroll_position(scroll_position, cx);
-// }
-// })
-// });
-// }
-// }
-
-// impl Render for GoToLine {
-// type Element = Div<Self>;
-
-// fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-// // todo!()
-// div()
-// }
-// }
-
-// impl View for GoToLine {
-// fn ui_name() -> &'static str {
-// "GoToLine"
-// }
-
-// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-// let theme = &theme::current(cx).picker;
-
-// let label = format!(
-// "{}{FILE_ROW_COLUMN_DELIMITER}{} of {} lines",
-// self.cursor_point.row + 1,
-// self.cursor_point.column + 1,
-// self.max_point.row + 1
-// );
-
-// Flex::new(Axis::Vertical)
-// .with_child(
-// ChildView::new(&self.line_editor, cx)
-// .contained()
-// .with_style(theme.input_editor.container),
-// )
-// .with_child(
-// Label::new(label, theme.no_matches.label.clone())
-// .contained()
-// .with_style(theme.no_matches.container),
-// )
-// .contained()
-// .with_style(theme.container)
-// .constrained()
-// .with_max_width(500.0)
-// .into_any_named("go to line")
-// }
-
-// fn focus_in(&mut self, _: AnyView, cx: &mut ViewContext<Self>) {
-// self.has_focus = true;
-// cx.focus(&self.line_editor);
-// }
-
-// fn focus_out(&mut self, _: AnyView, _: &mut ViewContext<Self>) {
-// self.has_focus = false;
-// }
-// }
-
-// impl Modal for GoToLine {
-// fn has_focus(&self) -> bool {
-// self.has_focus
-// }
-
-// fn dismiss_on_event(event: &Self::Event) -> bool {
-// matches!(event, Event::Dismissed)
-// }
-// }
+impl Modal for GoToLine {
+ fn to_modal_event(&self, e: &Self::Event) -> Option<ModalEvent> {
+ match e {
+ Event::Dismissed => Some(ModalEvent::Dismissed),
+ }
+ }
+}
+
+impl Render for GoToLine {
+ type Element = Div<Self, StatefulInteractivity<Self>>;
+
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+ modal(cx)
+ .id("go to line")
+ .on_action(Self::cancel)
+ .on_action(Self::confirm)
+ .w_96()
+ .child(
+ v_stack()
+ .px_1()
+ .pt_0p5()
+ .gap_px()
+ .child(
+ v_stack()
+ .py_0p5()
+ .px_1()
+ .child(div().px_1().py_0p5().child(self.line_editor.clone())),
+ )
+ .child(
+ div()
+ .h_px()
+ .w_full()
+ .bg(cx.theme().colors().element_background),
+ )
+ .child(
+ h_stack()
+ .justify_between()
+ .px_2()
+ .py_1()
+ .child(Label::new(self.current_text.clone()).color(LabelColor::Muted)),
+ ),
+ )
+ }
+}
@@ -123,6 +123,7 @@ pub fn register_action<A: Action>() {
/// Construct an action based on its name and optional JSON parameters sourced from the keymap.
pub fn build_action(name: &str, params: Option<serde_json::Value>) -> Result<Box<dyn Action>> {
let lock = ACTION_REGISTRY.read();
+
let build_action = lock
.builders_by_name
.get(name)
@@ -18,8 +18,8 @@ use crate::{
AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId,
Entity, EventEmitter, FocusEvent, FocusHandle, FocusId, ForegroundExecutor, KeyBinding, Keymap,
LayoutId, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, SubscriberSet,
- Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, Window,
- WindowContext, WindowHandle, WindowId,
+ Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, ViewContext,
+ Window, WindowContext, WindowHandle, WindowId,
};
use anyhow::{anyhow, Result};
use collections::{HashMap, HashSet, VecDeque};
@@ -167,6 +167,7 @@ type Handler = Box<dyn FnMut(&mut AppContext) -> bool + 'static>;
type Listener = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool + 'static>;
type QuitHandler = Box<dyn FnOnce(&mut AppContext) -> LocalBoxFuture<'static, ()> + 'static>;
type ReleaseListener = Box<dyn FnOnce(&mut dyn Any, &mut AppContext) + 'static>;
+type NewViewListener = Box<dyn FnMut(AnyView, &mut WindowContext) + 'static>;
// struct FrameConsumer {
// next_frame_callbacks: Vec<FrameCallback>,
@@ -193,6 +194,7 @@ pub struct AppContext {
pub(crate) text_style_stack: Vec<TextStyleRefinement>,
pub(crate) globals_by_type: HashMap<TypeId, AnyBox>,
pub(crate) entities: EntityMap,
+ pub(crate) new_view_observers: SubscriberSet<TypeId, NewViewListener>,
pub(crate) windows: SlotMap<WindowId, Option<Window>>,
pub(crate) keymap: Arc<Mutex<Keymap>>,
pub(crate) global_action_listeners:
@@ -251,6 +253,7 @@ impl AppContext {
text_style_stack: Vec::new(),
globals_by_type: HashMap::default(),
entities,
+ new_view_observers: SubscriberSet::new(),
windows: SlotMap::with_key(),
keymap: Arc::new(Mutex::new(Keymap::default())),
global_action_listeners: HashMap::default(),
@@ -599,6 +602,7 @@ impl AppContext {
fn apply_notify_effect(&mut self, emitter: EntityId) {
self.pending_notifications.remove(&emitter);
+
self.observers
.clone()
.retain(&emitter, |handler| handler(self));
@@ -838,6 +842,23 @@ impl AppContext {
self.globals_by_type.insert(global_type, lease.global);
}
+ pub fn observe_new_views<V: 'static>(
+ &mut self,
+ on_new: impl 'static + Fn(&mut V, &mut ViewContext<V>),
+ ) -> Subscription {
+ self.new_view_observers.insert(
+ TypeId::of::<V>(),
+ Box::new(move |any_view: AnyView, cx: &mut WindowContext| {
+ any_view
+ .downcast::<V>()
+ .unwrap()
+ .update(cx, |view_state, cx| {
+ on_new(view_state, cx);
+ })
+ }),
+ )
+ }
+
pub fn observe_release<E, T>(
&mut self,
handle: &E,
@@ -258,7 +258,7 @@ impl VisualContext for AsyncWindowContext {
build_view_state: impl FnOnce(&mut ViewContext<'_, V>) -> V,
) -> Self::Result<View<V>>
where
- V: 'static,
+ V: 'static + Render,
{
self.window
.update(self, |_, cx| cx.build_view(build_view_state))
@@ -112,7 +112,7 @@ pub trait VisualContext: Context {
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
) -> Self::Result<View<V>>
where
- V: 'static;
+ V: 'static + Render;
fn update_view<V: 'static, R>(
&mut self,
@@ -414,10 +414,14 @@ pub trait ElementInteractivity<V: 'static>: 'static {
Box::new(move |_, key_down, context, phase, cx| {
if phase == DispatchPhase::Bubble {
let key_down = key_down.downcast_ref::<KeyDownEvent>().unwrap();
+ dbg!(key_down);
if let KeyMatch::Some(action) =
cx.match_keystroke(&global_id, &key_down.keystroke, context)
{
+ dbg!(&action);
return Some(action);
+ } else {
+ dbg!("none");
}
}
@@ -5,7 +5,7 @@ use crate::{
};
use crate::{BoxShadow, TextStyleRefinement};
use refineable::Refineable;
-use smallvec::smallvec;
+use smallvec::{smallvec, SmallVec};
pub trait Styled {
fn style(&mut self) -> &mut StyleRefinement;
@@ -295,24 +295,11 @@ pub trait Styled {
/// Sets the box shadow of the element.
/// [Docs](https://tailwindcss.com/docs/box-shadow)
- fn shadow(mut self) -> Self
+ fn shadow(mut self, shadows: SmallVec<[BoxShadow; 2]>) -> Self
where
Self: Sized,
{
- self.style().box_shadow = Some(smallvec![
- BoxShadow {
- color: hsla(0., 0., 0., 0.1),
- offset: point(px(0.), px(1.)),
- blur_radius: px(3.),
- spread_radius: px(0.),
- },
- BoxShadow {
- color: hsla(0., 0., 0., 0.1),
- offset: point(px(0.), px(1.)),
- blur_radius: px(2.),
- spread_radius: px(-1.),
- }
- ]);
+ self.style().box_shadow = Some(shadows);
self
}
@@ -1437,7 +1437,7 @@ impl VisualContext for WindowContext<'_> {
build_view_state: impl FnOnce(&mut ViewContext<'_, V>) -> V,
) -> Self::Result<View<V>>
where
- V: 'static,
+ V: 'static + Render,
{
let slot = self.app.entities.reserve();
let view = View {
@@ -1445,7 +1445,16 @@ impl VisualContext for WindowContext<'_> {
};
let mut cx = ViewContext::new(&mut *self.app, &mut *self.window, &view);
let entity = build_view_state(&mut cx);
- self.entities.insert(slot, entity);
+ cx.entities.insert(slot, entity);
+
+ cx.new_view_observers
+ .clone()
+ .retain(&TypeId::of::<V>(), |observer| {
+ let any_view = AnyView::from(view.clone());
+ (observer)(any_view, self);
+ true
+ });
+
view
}
@@ -2219,7 +2228,7 @@ impl<V> Context for ViewContext<'_, V> {
}
impl<V: 'static> VisualContext for ViewContext<'_, V> {
- fn build_view<W: 'static>(
+ fn build_view<W: Render + 'static>(
&mut self,
build_view_state: impl FnOnce(&mut ViewContext<'_, W>) -> W,
) -> Self::Result<View<W>> {
@@ -1,5 +1,10 @@
use gpui::actions;
+// todo!(remove this)
+// https://github.com/rust-lang/rust/issues/47384
+// https://github.com/mmastrac/rust-ctor/issues/280
+pub fn unused() {}
+
actions!(
Cancel,
Confirm,
@@ -71,7 +71,7 @@ impl Theme {
&self.styles.system
}
- /// Returns the [`ThemeColors`] for the theme.
+ /// Returns the [`PlayerColors`] for the theme.
#[inline(always)]
pub fn players(&self) -> &PlayerColors {
&self.styles.player
@@ -3,6 +3,7 @@ mod button;
mod checkbox;
mod context_menu;
mod details;
+mod elevated_surface;
mod facepile;
mod icon;
mod icon_button;
@@ -30,6 +31,7 @@ pub use button::*;
pub use checkbox::*;
pub use context_menu::*;
pub use details::*;
+pub use elevated_surface::*;
pub use facepile::*;
pub use icon::*;
pub use icon_button::*;
@@ -0,0 +1,28 @@
+use gpui::Div;
+
+use crate::{prelude::*, v_stack};
+
+/// Create an elevated surface.
+///
+/// Must be used inside of a relative parent element
+pub fn elevated_surface<V: 'static>(level: ElevationIndex, cx: &mut ViewContext<V>) -> Div<V> {
+ let colors = cx.theme().colors();
+
+ // let shadow = BoxShadow {
+ // color: hsla(0., 0., 0., 0.1),
+ // offset: point(px(0.), px(1.)),
+ // blur_radius: px(3.),
+ // spread_radius: px(0.),
+ // };
+
+ v_stack()
+ .rounded_lg()
+ .bg(colors.elevated_surface_background)
+ .border()
+ .border_color(colors.border)
+ .shadow(level.shadow())
+}
+
+pub fn modal<V>(cx: &mut ViewContext<V>) -> Div<V> {
+ elevated_surface(ElevationIndex::ModalSurfaces, cx)
+}
@@ -1,3 +1,6 @@
+use gpui::{hsla, point, px, BoxShadow};
+use smallvec::{smallvec, SmallVec};
+
#[doc = include_str!("elevation.md")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Elevation {
@@ -17,8 +20,8 @@ pub enum ElevationIndex {
}
impl ElevationIndex {
- pub fn usize(&self) -> usize {
- match *self {
+ pub fn z_index(self) -> u32 {
+ match self {
ElevationIndex::AppBackground => 0,
ElevationIndex::UISurface => 100,
ElevationIndex::ElevatedSurface => 200,
@@ -27,6 +30,26 @@ impl ElevationIndex {
ElevationIndex::DraggedElement => 900,
}
}
+
+ pub fn shadow(self) -> SmallVec<[BoxShadow; 2]> {
+ match self {
+ ElevationIndex::AppBackground => smallvec![],
+
+ ElevationIndex::UISurface => smallvec![BoxShadow {
+ color: hsla(0., 0., 0., 0.12),
+ offset: point(px(0.), px(1.)),
+ blur_radius: px(3.),
+ spread_radius: px(0.),
+ }],
+
+ _ => smallvec![BoxShadow {
+ color: hsla(0., 0., 0., 0.32),
+ offset: point(px(1.), px(3.)),
+ blur_radius: px(12.),
+ spread_radius: px(0.),
+ }],
+ }
+ }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -407,6 +407,14 @@ impl Dock {
// }
}
+impl Render for Dock {
+ type Element = Div<Self>;
+
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+ todo!()
+ }
+}
+
// todo!()
// impl View for Dock {
// fn ui_name() -> &'static str {
@@ -1,35 +1,38 @@
use crate::Workspace;
use gpui::{
- div, AnyView, AppContext, Div, ParentElement, Render, StatelessInteractive, View, ViewContext,
+ div, px, AnyView, Component, Div, EventEmitter, ParentElement, Render, StatelessInteractive,
+ Styled, Subscription, View, ViewContext,
};
use std::{any::TypeId, sync::Arc};
+use ui::v_stack;
-pub struct ModalRegistry {
- registered_modals: Vec<(TypeId, Box<dyn Fn(Div<Workspace>) -> Div<Workspace>>)>,
-}
-
-pub trait Modal {}
-
-#[derive(Clone)]
pub struct ModalLayer {
open_modal: Option<AnyView>,
+ subscription: Option<Subscription>,
+ registered_modals: Vec<(TypeId, Box<dyn Fn(Div<Workspace>) -> Div<Workspace>>)>,
}
-pub fn init_modal_registry(cx: &mut AppContext) {
- cx.set_global(ModalRegistry {
- registered_modals: Vec::new(),
- });
+pub enum ModalEvent {
+ Dismissed,
}
-struct ToggleModal {
- name: String,
+pub trait Modal: EventEmitter + Render {
+ fn to_modal_event(&self, _: &Self::Event) -> Option<ModalEvent>;
}
-impl ModalRegistry {
+impl ModalLayer {
+ pub fn new() -> Self {
+ Self {
+ open_modal: None,
+ subscription: None,
+ registered_modals: Vec::new(),
+ }
+ }
+
pub fn register_modal<A: 'static, V, B>(&mut self, action: A, build_view: B)
where
- V: Render,
- B: Fn(&Workspace, &mut ViewContext<Workspace>) -> View<V> + 'static,
+ V: Modal,
+ B: Fn(&mut Workspace, &mut ViewContext<Workspace>) -> Option<View<V>> + 'static,
{
let build_view = Arc::new(build_view);
@@ -38,42 +41,56 @@ impl ModalRegistry {
Box::new(move |mut div| {
let build_view = build_view.clone();
- div.on_action(
- move |workspace: &mut Workspace, event: &A, cx: &mut ViewContext<Workspace>| {
- let new_modal = (build_view)(workspace, cx);
- workspace.modal_layer.update(cx, |modal_layer, _| {
- modal_layer.open_modal = Some(new_modal.into());
- });
-
- cx.notify();
- },
- )
+ div.on_action(move |workspace, event: &A, cx| {
+ let Some(new_modal) = (build_view)(workspace, cx) else {
+ return;
+ };
+ workspace.modal_layer().show_modal(new_modal, cx);
+ })
}),
));
}
-}
-impl ModalLayer {
- pub fn new() -> Self {
- Self { open_modal: None }
+ pub fn show_modal<V: Modal>(&mut self, new_modal: View<V>, cx: &mut ViewContext<Workspace>) {
+ self.subscription = Some(cx.subscribe(&new_modal, |this, modal, e, cx| {
+ match modal.read(cx).to_modal_event(e) {
+ Some(ModalEvent::Dismissed) => this.modal_layer().hide_modal(cx),
+ None => {}
+ }
+ }));
+ self.open_modal = Some(new_modal.into());
+ cx.notify();
}
- pub fn render(&self, workspace: &Workspace, cx: &ViewContext<Workspace>) -> Div<Workspace> {
- let mut div = div();
-
- // div, c workspace.toggle_modal()div.on_action()) {
- //
- // }
+ pub fn hide_modal(&mut self, cx: &mut ViewContext<Workspace>) {
+ self.open_modal.take();
+ self.subscription.take();
+ cx.notify();
+ }
- // for (type_id, action) in cx.global::<ModalRegistry>().registered_modals.iter() {
- // div = div.useful_on_action(*type_id, action)
- // }
+ pub fn wrapper_element(&self, cx: &ViewContext<Workspace>) -> Div<Workspace> {
+ let mut parent = div().relative().size_full();
- for (_, action) in cx.global::<ModalRegistry>().registered_modals.iter() {
- div = (action)(div);
+ for (_, action) in self.registered_modals.iter() {
+ parent = (action)(parent);
}
- div.children(self.open_modal.clone())
+ parent.when_some(self.open_modal.as_ref(), |parent, open_modal| {
+ let container1 = div()
+ .absolute()
+ .flex()
+ .flex_col()
+ .items_center()
+ .size_full()
+ .top_0()
+ .left_0()
+ .z_index(400);
+
+ // transparent layer
+ let container2 = v_stack().h(px(0.0)).relative().top_20();
+
+ parent.child(container1.child(container2.child(open_modal.clone())))
+ })
}
}
@@ -1,6 +1,7 @@
use crate::ItemHandle;
use gpui::{
- AnyView, AppContext, Entity, EntityId, EventEmitter, Render, View, ViewContext, WindowContext,
+ AnyView, AppContext, Div, Entity, EntityId, EventEmitter, Render, View, ViewContext,
+ WindowContext,
};
pub trait ToolbarItemView: Render + EventEmitter {
@@ -56,6 +57,14 @@ pub struct Toolbar {
items: Vec<(Box<dyn ToolbarItemViewHandle>, ToolbarItemLocation)>,
}
+impl Render for Toolbar {
+ type Element = Div<Self>;
+
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+ todo!()
+ }
+}
+
// todo!()
// impl View for Toolbar {
// fn ui_name() -> &'static str {
@@ -46,8 +46,7 @@ use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings,
use itertools::Itertools;
use language2::LanguageRegistry;
use lazy_static::lazy_static;
-pub use modal_layer::ModalRegistry;
-use modal_layer::{init_modal_registry, ModalLayer};
+pub use modal_layer::*;
use node_runtime::NodeRuntime;
use notifications::{simple_message_notification::MessageNotification, NotificationHandle};
pub use pane::*;
@@ -227,7 +226,6 @@ pub fn init_settings(cx: &mut AppContext) {
pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
init_settings(cx);
- init_modal_registry(cx);
pane::init(cx);
notifications::init(cx);
@@ -547,7 +545,7 @@ pub struct Workspace {
last_active_center_pane: Option<WeakView<Pane>>,
last_active_view_id: Option<proto::ViewId>,
status_bar: View<StatusBar>,
- modal_layer: View<ModalLayer>,
+ modal_layer: ModalLayer,
// titlebar_item: Option<AnyViewHandle>,
notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
project: Model<Project>,
@@ -698,7 +696,8 @@ impl Workspace {
status_bar
});
- let modal_layer = cx.build_view(|cx| ModalLayer::new());
+ let workspace_handle = cx.view().downgrade();
+ let modal_layer = ModalLayer::new();
// todo!()
// cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
@@ -782,6 +781,10 @@ impl Workspace {
}
}
+ pub fn modal_layer(&mut self) -> &mut ModalLayer {
+ &mut self.modal_layer
+ }
+
fn new_local(
abs_paths: Vec<PathBuf>,
app_state: Arc<AppState>,
@@ -3712,13 +3715,13 @@ impl Render for Workspace {
.bg(cx.theme().colors().background)
.child(self.render_titlebar(cx))
.child(
+ // todo! should this be a component a view?
self.modal_layer
- .read(cx)
- .render(self, cx)
+ .wrapper_element(cx)
+ .relative()
.flex_1()
.w_full()
.flex()
- .flex_row()
.overflow_hidden()
.border_t()
.border_b()
@@ -48,6 +48,7 @@ journal = { package = "journal2", path = "../journal2" }
language = { package = "language2", path = "../language2" }
# language_selector = { path = "../language_selector" }
lsp = { package = "lsp2", path = "../lsp2" }
+menu = { package = "menu2", path = "../menu2" }
language_tools = { path = "../language_tools" }
node_runtime = { path = "../node_runtime" }
# assistant = { path = "../assistant" }
@@ -56,6 +56,10 @@ use zed2::{
mod open_listener;
fn main() {
+ //TODO!(figure out what the linker issues are here)
+ // https://github.com/rust-lang/rust/issues/47384
+ // https://github.com/mmastrac/rust-ctor/issues/280
+ menu::unused();
let http = http::client();
init_paths();
init_logger();
@@ -23,7 +23,7 @@ export default function assistant(): any {
const theme = useTheme()
const interactive_role = (
- color: StyleSets
+ color: StyleSets,
): Interactive<RoleCycleButton> => {
return interactive({
base: {
@@ -94,7 +94,7 @@ export default function assistant(): any {
margin: { left: 8, right: 18 },
color: foreground(theme.highest, "positive"),
width: 12,
- }
+ },
},
retrieve_context: toggleable({
base: interactive({
@@ -106,7 +106,8 @@ export default function assistant(): any {
background: background(theme.highest, "on"),
corner_radius: 2,
border: {
- width: 1., color: background(theme.highest, "on")
+ width: 1,
+ color: background(theme.highest, "on"),
},
margin: { left: 2 },
padding: {
@@ -118,17 +119,45 @@ export default function assistant(): any {
},
state: {
hovered: {
- ...text(theme.highest, "mono", "variant", "hovered"),
- background: background(theme.highest, "on", "hovered"),
+ ...text(
+ theme.highest,
+ "mono",
+ "variant",
+ "hovered",
+ ),
+ background: background(
+ theme.highest,
+ "on",
+ "hovered",
+ ),
border: {
- width: 1., color: background(theme.highest, "on", "hovered")
+ width: 1,
+ color: background(
+ theme.highest,
+ "on",
+ "hovered",
+ ),
},
},
clicked: {
- ...text(theme.highest, "mono", "variant", "pressed"),
- background: background(theme.highest, "on", "pressed"),
+ ...text(
+ theme.highest,
+ "mono",
+ "variant",
+ "pressed",
+ ),
+ background: background(
+ theme.highest,
+ "on",
+ "pressed",
+ ),
border: {
- width: 1., color: background(theme.highest, "on", "pressed")
+ width: 1,
+ color: background(
+ theme.highest,
+ "on",
+ "pressed",
+ ),
},
},
},
@@ -143,11 +172,19 @@ export default function assistant(): any {
border: border(theme.highest, "accent"),
},
hovered: {
- background: background(theme.highest, "accent", "hovered"),
+ background: background(
+ theme.highest,
+ "accent",
+ "hovered",
+ ),
border: border(theme.highest, "accent", "hovered"),
},
clicked: {
- background: background(theme.highest, "accent", "pressed"),
+ background: background(
+ theme.highest,
+ "accent",
+ "pressed",
+ ),
border: border(theme.highest, "accent", "pressed"),
},
},
@@ -163,7 +200,8 @@ export default function assistant(): any {
background: background(theme.highest, "on"),
corner_radius: 2,
border: {
- width: 1., color: background(theme.highest, "on")
+ width: 1,
+ color: background(theme.highest, "on"),
},
padding: {
left: 4,
@@ -174,17 +212,45 @@ export default function assistant(): any {
},
state: {
hovered: {
- ...text(theme.highest, "mono", "variant", "hovered"),
- background: background(theme.highest, "on", "hovered"),
+ ...text(
+ theme.highest,
+ "mono",
+ "variant",
+ "hovered",
+ ),
+ background: background(
+ theme.highest,
+ "on",
+ "hovered",
+ ),
border: {
- width: 1., color: background(theme.highest, "on", "hovered")
+ width: 1,
+ color: background(
+ theme.highest,
+ "on",
+ "hovered",
+ ),
},
},
clicked: {
- ...text(theme.highest, "mono", "variant", "pressed"),
- background: background(theme.highest, "on", "pressed"),
+ ...text(
+ theme.highest,
+ "mono",
+ "variant",
+ "pressed",
+ ),
+ background: background(
+ theme.highest,
+ "on",
+ "pressed",
+ ),
border: {
- width: 1., color: background(theme.highest, "on", "pressed")
+ width: 1,
+ color: background(
+ theme.highest,
+ "on",
+ "pressed",
+ ),
},
},
},
@@ -199,11 +265,19 @@ export default function assistant(): any {
border: border(theme.highest, "accent"),
},
hovered: {
- background: background(theme.highest, "accent", "hovered"),
+ background: background(
+ theme.highest,
+ "accent",
+ "hovered",
+ ),
border: border(theme.highest, "accent", "hovered"),
},
clicked: {
- background: background(theme.highest, "accent", "pressed"),
+ background: background(
+ theme.highest,
+ "accent",
+ "pressed",
+ ),
border: border(theme.highest, "accent", "pressed"),
},
},
@@ -78,33 +78,33 @@ export default function status_bar(): any {
padding: { top: 2, bottom: 2, left: 6, right: 6 },
},
container_warning: diagnostic_status_container,
- container_error: diagnostic_status_container
+ container_error: diagnostic_status_container,
},
state: {
hovered: {
icon_color_ok: foreground(layer, "on"),
container_ok: {
- background: background(layer, "hovered")
+ background: background(layer, "hovered"),
},
container_warning: {
- background: background(layer, "hovered")
+ background: background(layer, "hovered"),
},
container_error: {
- background: background(layer, "hovered")
+ background: background(layer, "hovered"),
},
},
clicked: {
icon_color_ok: foreground(layer, "on"),
container_ok: {
- background: background(layer, "pressed")
+ background: background(layer, "pressed"),
},
container_warning: {
- background: background(layer, "pressed")
+ background: background(layer, "pressed"),
},
container_error: {
- background: background(layer, "pressed")
- }
- }
+ background: background(layer, "pressed"),
+ },
+ },
},
}),
panel_buttons: {
@@ -31,7 +31,7 @@ export const theme: ThemeConfig = {
color.muted,
color.subtle,
color.text,
- ].reverse()
+ ].reverse(),
)
.domain([0, 0.35, 0.45, 0.65, 0.7, 0.8, 0.9, 1]),
red: color_ramp(chroma(color.love)),