diff --git a/Cargo.lock b/Cargo.lock index 4b2a74c297..71090e06e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2531,6 +2531,7 @@ dependencies = [ "client", "collections", "editor", + "futures 0.3.28", "gpui", "language", "log", @@ -9802,7 +9803,7 @@ dependencies = [ [[package]] name = "tree-sitter-vue" version = "0.0.1" -source = "git+https://github.com/zed-industries/tree-sitter-vue?rev=95b2890#95b28908d90e928c308866f7631e73ef6e1d4b5f" +source = "git+https://github.com/zed-industries/tree-sitter-vue?rev=9b6cb221ccb8d0b956fcb17e9a1efac2feefeb58#9b6cb221ccb8d0b956fcb17e9a1efac2feefeb58" dependencies = [ "cc", "tree-sitter", diff --git a/Cargo.toml b/Cargo.toml index acc8659117..9e1201e428 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -177,7 +177,7 @@ tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", tree-sitter-lua = "0.0.14" tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" } tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "786689b0562b9799ce53e824cb45a1a2a04dc673"} -tree-sitter-vue = {git = "https://github.com/zed-industries/tree-sitter-vue", rev = "95b2890"} +tree-sitter-vue = {git = "https://github.com/zed-industries/tree-sitter-vue", rev = "9b6cb221ccb8d0b956fcb17e9a1efac2feefeb58"} [patch.crates-io] tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "35a6052fbcafc5e5fc0f9415b8652be7dcaf7222" } async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" } diff --git a/assets/icons/dash.svg b/assets/icons/dash.svg new file mode 100644 index 0000000000..efff9eab5e --- /dev/null +++ b/assets/icons/dash.svg @@ -0,0 +1 @@ + diff --git a/crates/diagnostics/Cargo.toml b/crates/diagnostics/Cargo.toml index 26a2a82999..0f9d108831 100644 --- a/crates/diagnostics/Cargo.toml +++ b/crates/diagnostics/Cargo.toml @@ -22,6 +22,7 @@ workspace = { path = "../workspace" } log.workspace = true anyhow.workspace = true +futures.workspace = true schemars.workspace = true serde.workspace = true serde_derive.workspace = true diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index a3d779531b..e794771434 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -2,8 +2,8 @@ pub mod items; mod project_diagnostics_settings; mod toolbar_controls; -use anyhow::Result; -use collections::{BTreeSet, HashSet}; +use anyhow::{Context, Result}; +use collections::{HashMap, HashSet}; use editor::{ diagnostic_block_renderer, display_map::{BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock}, @@ -11,9 +11,10 @@ use editor::{ scroll::autoscroll::Autoscroll, Editor, ExcerptId, ExcerptRange, MultiBuffer, ToOffset, }; +use futures::future::try_join_all; use gpui::{ actions, elements::*, fonts::TextStyle, serde_json, AnyViewHandle, AppContext, Entity, - ModelHandle, Task, View, ViewContext, ViewHandle, WeakViewHandle, + ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use language::{ Anchor, Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection, @@ -28,6 +29,7 @@ use std::{ any::{Any, TypeId}, borrow::Cow, cmp::Ordering, + mem, ops::Range, path::PathBuf, sync::Arc, @@ -60,8 +62,10 @@ struct ProjectDiagnosticsEditor { summary: DiagnosticSummary, excerpts: ModelHandle, path_states: Vec, - paths_to_update: BTreeSet<(ProjectPath, LanguageServerId)>, + paths_to_update: HashMap>, + current_diagnostics: HashMap>, include_warnings: bool, + _subscriptions: Vec, } struct PathState { @@ -125,9 +129,12 @@ impl View for ProjectDiagnosticsEditor { "summary": project.diagnostic_summary(cx), }), "summary": self.summary, - "paths_to_update": self.paths_to_update.iter().map(|(path, server_id)| - (path.path.to_string_lossy(), server_id.0) - ).collect::>(), + "paths_to_update": self.paths_to_update.iter().map(|(server_id, paths)| + (server_id.0, paths.into_iter().map(|path| path.path.to_string_lossy()).collect::>()) + ).collect::>(), + "current_diagnostics": self.current_diagnostics.iter().map(|(server_id, paths)| + (server_id.0, paths.into_iter().map(|path| path.path.to_string_lossy()).collect::>()) + ).collect::>(), "paths_states": self.path_states.iter().map(|state| json!({ "path": state.path.path.to_string_lossy(), @@ -149,25 +156,30 @@ impl ProjectDiagnosticsEditor { workspace: WeakViewHandle, cx: &mut ViewContext, ) -> Self { - cx.subscribe(&project_handle, |this, _, event, cx| match event { - project::Event::DiskBasedDiagnosticsFinished { language_server_id } => { - log::debug!("Disk based diagnostics finished for server {language_server_id}"); - this.update_excerpts(Some(*language_server_id), cx); - this.update_title(cx); - } - project::Event::DiagnosticsUpdated { - language_server_id, - path, - } => { - log::debug!("Adding path {path:?} to update for server {language_server_id}"); - this.paths_to_update - .insert((path.clone(), *language_server_id)); - this.update_excerpts(Some(*language_server_id), cx); - this.update_title(cx); - } - _ => {} - }) - .detach(); + let project_event_subscription = + cx.subscribe(&project_handle, |this, _, event, cx| match event { + project::Event::DiskBasedDiagnosticsFinished { language_server_id } => { + log::debug!("Disk based diagnostics finished for server {language_server_id}"); + this.update_excerpts(Some(*language_server_id), cx); + } + project::Event::DiagnosticsUpdated { + language_server_id, + path, + } => { + log::debug!("Adding path {path:?} to update for server {language_server_id}"); + this.paths_to_update + .entry(*language_server_id) + .or_default() + .insert(path.clone()); + let no_multiselections = this.editor.update(cx, |editor, cx| { + editor.selections.all::(cx).len() <= 1 + }); + if no_multiselections && !this.is_dirty(cx) { + this.update_excerpts(Some(*language_server_id), cx); + } + } + _ => {} + }); let excerpts = cx.add_model(|cx| MultiBuffer::new(project_handle.read(cx).replica_id())); let editor = cx.add_view(|cx| { @@ -176,19 +188,14 @@ impl ProjectDiagnosticsEditor { editor.set_vertical_scroll_margin(5, cx); editor }); - cx.subscribe(&editor, |this, _, event, cx| { + let editor_event_subscription = cx.subscribe(&editor, |this, _, event, cx| { cx.emit(event.clone()); if event == &editor::Event::Focused && this.path_states.is_empty() { cx.focus_self() } - }) - .detach(); + }); let project = project_handle.read(cx); - let paths_to_update = project - .diagnostic_summaries(cx) - .map(|(path, server_id, _)| (path, server_id)) - .collect(); let summary = project.diagnostic_summary(cx); let mut this = Self { project: project_handle, @@ -197,8 +204,10 @@ impl ProjectDiagnosticsEditor { excerpts, editor, path_states: Default::default(), - paths_to_update, + paths_to_update: HashMap::default(), include_warnings: settings::get::(cx).include_warnings, + current_diagnostics: HashMap::default(), + _subscriptions: vec![project_event_subscription, editor_event_subscription], }; this.update_excerpts(None, cx); this @@ -218,12 +227,7 @@ impl ProjectDiagnosticsEditor { fn toggle_warnings(&mut self, _: &ToggleWarnings, cx: &mut ViewContext) { self.include_warnings = !self.include_warnings; - self.paths_to_update = self - .project - .read(cx) - .diagnostic_summaries(cx) - .map(|(path, server_id, _)| (path, server_id)) - .collect(); + self.paths_to_update = self.current_diagnostics.clone(); self.update_excerpts(None, cx); cx.notify(); } @@ -234,29 +238,93 @@ impl ProjectDiagnosticsEditor { cx: &mut ViewContext, ) { log::debug!("Updating excerpts for server {language_server_id:?}"); - let mut paths = Vec::new(); - self.paths_to_update.retain(|(path, server_id)| { - if language_server_id - .map_or(true, |language_server_id| language_server_id == *server_id) - { - paths.push(path.clone()); - false + let mut paths_to_recheck = HashSet::default(); + let mut new_summaries: HashMap> = self + .project + .read(cx) + .diagnostic_summaries(cx) + .fold(HashMap::default(), |mut summaries, (path, server_id, _)| { + summaries.entry(server_id).or_default().insert(path); + summaries + }); + let mut old_diagnostics = if let Some(language_server_id) = language_server_id { + new_summaries.retain(|server_id, _| server_id == &language_server_id); + self.paths_to_update.retain(|server_id, paths| { + if server_id == &language_server_id { + paths_to_recheck.extend(paths.drain()); + false + } else { + true + } + }); + let mut old_diagnostics = HashMap::default(); + if let Some(new_paths) = new_summaries.get(&language_server_id) { + if let Some(old_paths) = self + .current_diagnostics + .insert(language_server_id, new_paths.clone()) + { + old_diagnostics.insert(language_server_id, old_paths); + } } else { - true + if let Some(old_paths) = self.current_diagnostics.remove(&language_server_id) { + old_diagnostics.insert(language_server_id, old_paths); + } } - }); + old_diagnostics + } else { + paths_to_recheck.extend(self.paths_to_update.drain().flat_map(|(_, paths)| paths)); + mem::replace(&mut self.current_diagnostics, new_summaries.clone()) + }; + for (server_id, new_paths) in new_summaries { + match old_diagnostics.remove(&server_id) { + Some(mut old_paths) => { + paths_to_recheck.extend( + new_paths + .into_iter() + .filter(|new_path| !old_paths.remove(new_path)), + ); + paths_to_recheck.extend(old_paths); + } + None => paths_to_recheck.extend(new_paths), + } + } + paths_to_recheck.extend(old_diagnostics.into_iter().flat_map(|(_, paths)| paths)); + + if paths_to_recheck.is_empty() { + log::debug!("No paths to recheck for language server {language_server_id:?}"); + return; + } + log::debug!( + "Rechecking {} paths for language server {:?}", + paths_to_recheck.len(), + language_server_id + ); let project = self.project.clone(); cx.spawn(|this, mut cx| { async move { - for path in paths { - let buffer = project - .update(&mut cx, |project, cx| project.open_buffer(path.clone(), cx)) - .await?; - this.update(&mut cx, |this, cx| { - this.populate_excerpts(path, language_server_id, buffer, cx) - })?; - } - Result::<_, anyhow::Error>::Ok(()) + let _: Vec<()> = try_join_all(paths_to_recheck.into_iter().map(|path| { + let mut cx = cx.clone(); + let project = project.clone(); + async move { + let buffer = project + .update(&mut cx, |project, cx| project.open_buffer(path.clone(), cx)) + .await + .with_context(|| format!("opening buffer for path {path:?}"))?; + this.update(&mut cx, |this, cx| { + this.populate_excerpts(path, language_server_id, buffer, cx); + }) + .context("missing project")?; + anyhow::Ok(()) + } + })) + .await + .context("rechecking diagnostics for paths")?; + + this.update(&mut cx, |this, cx| { + this.summary = this.project.read(cx).diagnostic_summary(cx); + cx.emit(Event::TitleChanged); + })?; + anyhow::Ok(()) } .log_err() }) @@ -559,11 +627,6 @@ impl ProjectDiagnosticsEditor { } cx.notify(); } - - fn update_title(&mut self, cx: &mut ViewContext) { - self.summary = self.project.read(cx).diagnostic_summary(cx); - cx.emit(Event::TitleChanged); - } } impl Item for ProjectDiagnosticsEditor { diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index ea747de5de..52628f61b5 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -37,8 +37,8 @@ use futures::FutureExt; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ div, AnyElement, AppContext, BackgroundExecutor, Context, Div, Element, EventEmitter, - FocusHandle, Hsla, Model, Pixels, Render, Subscription, Task, TextStyle, View, ViewContext, - VisualContext, WeakView, WindowContext, + FocusHandle, Hsla, Model, Pixels, Render, Styled, Subscription, Task, TextStyle, View, + ViewContext, VisualContext, WeakView, WindowContext, }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; @@ -68,6 +68,7 @@ use scroll::{ use selections_collection::{MutableSelectionsCollection, SelectionsCollection}; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; +use smallvec::SmallVec; use std::{ any::TypeId, borrow::Cow, @@ -8347,51 +8348,51 @@ impl Editor { // .text() // } - // pub fn wrap_guides(&self, cx: &AppContext) -> SmallVec<[(usize, bool); 2]> { - // let mut wrap_guides = smallvec::smallvec![]; + pub fn wrap_guides(&self, cx: &AppContext) -> SmallVec<[(usize, bool); 2]> { + let mut wrap_guides = smallvec::smallvec![]; - // if self.show_wrap_guides == Some(false) { - // return wrap_guides; - // } + if self.show_wrap_guides == Some(false) { + return wrap_guides; + } - // let settings = self.buffer.read(cx).settings_at(0, cx); - // if settings.show_wrap_guides { - // if let SoftWrap::Column(soft_wrap) = self.soft_wrap_mode(cx) { - // wrap_guides.push((soft_wrap as usize, true)); - // } - // wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false))) - // } + let settings = self.buffer.read(cx).settings_at(0, cx); + if settings.show_wrap_guides { + if let SoftWrap::Column(soft_wrap) = self.soft_wrap_mode(cx) { + wrap_guides.push((soft_wrap as usize, true)); + } + wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false))) + } - // wrap_guides - // } + wrap_guides + } - // pub fn soft_wrap_mode(&self, cx: &AppContext) -> SoftWrap { - // let settings = self.buffer.read(cx).settings_at(0, cx); - // let mode = self - // .soft_wrap_mode_override - // .unwrap_or_else(|| settings.soft_wrap); - // match mode { - // language_settings::SoftWrap::None => SoftWrap::None, - // language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth, - // language_settings::SoftWrap::PreferredLineLength => { - // SoftWrap::Column(settings.preferred_line_length) - // } - // } - // } + pub fn soft_wrap_mode(&self, cx: &AppContext) -> SoftWrap { + let settings = self.buffer.read(cx).settings_at(0, cx); + let mode = self + .soft_wrap_mode_override + .unwrap_or_else(|| settings.soft_wrap); + match mode { + language_settings::SoftWrap::None => SoftWrap::None, + language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth, + language_settings::SoftWrap::PreferredLineLength => { + SoftWrap::Column(settings.preferred_line_length) + } + } + } - // pub fn set_soft_wrap_mode( - // &mut self, - // mode: language_settings::SoftWrap, - // cx: &mut ViewContext, - // ) { - // self.soft_wrap_mode_override = Some(mode); - // cx.notify(); - // } + pub fn set_soft_wrap_mode( + &mut self, + mode: language_settings::SoftWrap, + cx: &mut ViewContext, + ) { + self.soft_wrap_mode_override = Some(mode); + cx.notify(); + } - // pub fn set_wrap_width(&self, width: Option, cx: &mut AppContext) -> bool { - // self.display_map - // .update(cx, |map, cx| map.set_wrap_width(width, cx)) - // } + pub fn set_wrap_width(&self, width: Option, cx: &mut AppContext) -> bool { + self.display_map + .update(cx, |map, cx| map.set_wrap_width(width, cx)) + } // pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, cx: &mut ViewContext) { // if self.soft_wrap_mode_override.is_some() { @@ -9321,11 +9322,14 @@ impl EventEmitter for Editor { } impl Render for Editor { - type Element = Div; + type Element = EditorElement; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { - // todo!() - div() + EditorElement::new(EditorStyle { + text: cx.text_style(), + line_height_scalar: 1., + theme_id: 0, + }) } } diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 645cdc7646..6420d1e6cd 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -3,17 +3,18 @@ use super::{ }; use crate::{ display_map::{BlockStyle, DisplaySnapshot}, - EditorStyle, + EditorMode, EditorStyle, SoftWrap, }; use anyhow::Result; use gpui::{ - black, px, relative, AnyElement, Bounds, Element, Hsla, Line, Pixels, Size, Style, TextRun, - TextSystem, + black, point, px, relative, size, AnyElement, Bounds, Element, Hsla, Line, Pixels, Size, Style, + TextRun, TextSystem, ViewContext, }; use language::{CursorShape, Selection}; use smallvec::SmallVec; -use std::{ops::Range, sync::Arc}; +use std::{cmp, ops::Range, sync::Arc}; use sum_tree::Bias; +use theme::ActiveTheme; enum FoldMarkers {} @@ -1321,29 +1322,31 @@ impl EditorElement { // } // } - // fn column_pixels(&self, column: usize, cx: &ViewContext) -> f32 { - // let style = &self.style; + fn column_pixels(&self, column: usize, cx: &ViewContext) -> Pixels { + let style = &self.style; + let font_size = style.text.font_size * cx.rem_size(); + let layout = cx + .text_system() + .layout_text( + " ".repeat(column).as_str(), + font_size, + &[TextRun { + len: column, + font: style.text.font(), + color: Hsla::default(), + underline: None, + }], + None, + ) + .unwrap(); - // cx.text_layout_cache() - // .layout_str( - // " ".repeat(column).as_str(), - // style.text.font_size, - // &[( - // column, - // RunStyle { - // font_id: style.text.font_id, - // color: Color::black(), - // underline: Default::default(), - // }, - // )], - // ) - // .width() - // } + layout[0].width + } - // fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &ViewContext) -> f32 { - // let digit_count = (snapshot.max_buffer_row() as f32 + 1.).log10().floor() as usize + 1; - // self.column_pixels(digit_count, cx) - // } + fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &ViewContext) -> Pixels { + let digit_count = (snapshot.max_buffer_row() as f32 + 1.).log10().floor() as usize + 1; + self.column_pixels(digit_count, cx) + } //Folds contained in a hunk are ignored apart from shrinking visual size //If a fold contains any hunks then that fold line is marked as modified @@ -2002,6 +2005,7 @@ impl Element for EditorElement { element_state: &mut Self::ElementState, cx: &mut gpui::ViewContext, ) -> gpui::LayoutId { + let rem_size = cx.rem_size(); let mut style = Style::default(); style.size.width = relative(1.).into(); style.size.height = relative(1.).into(); @@ -2011,18 +2015,125 @@ impl Element for EditorElement { fn paint( &mut self, bounds: Bounds, - view_state: &mut Editor, + editor: &mut Editor, element_state: &mut Self::ElementState, cx: &mut gpui::ViewContext, ) { - let text_style = cx.text_style(); + // let mut size = constraint.max; + // if size.x().is_infinite() { + // unimplemented!("we don't yet handle an infinite width constraint on buffer elements"); + // } - let layout_text = cx.text_system().layout_text( - "hello world", - text_style.font_size * cx.rem_size(), - &[text_style.to_run("hello world".len())], - None, - ); + let snapshot = editor.snapshot(cx); + let style = self.style.clone(); + let font_id = cx.text_system().font_id(&style.text.font()).unwrap(); + let font_size = style.text.font_size * cx.rem_size(); + let line_height = (font_size * style.line_height_scalar).round(); + let em_width = cx + .text_system() + .typographic_bounds(font_id, font_size, 'm') + .unwrap() + .size + .width; + let em_advance = cx + .text_system() + .advance(font_id, font_size, 'm') + .unwrap() + .width; + + let gutter_padding; + let gutter_width; + let gutter_margin; + if snapshot.show_gutter { + let descent = cx.text_system().descent(font_id, font_size).unwrap(); + + let gutter_padding_factor = 3.5; + gutter_padding = (em_width * gutter_padding_factor).round(); + gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0; + gutter_margin = -descent; + } else { + gutter_padding = px(0.0); + gutter_width = px(0.0); + gutter_margin = px(0.0); + }; + + let text_width = bounds.size.width - gutter_width; + let overscroll = point(em_width, px(0.)); + let snapshot = { + editor.set_visible_line_count((bounds.size.height / line_height).into(), cx); + + let editor_width = text_width - gutter_margin - overscroll.x - em_width; + let wrap_width = match editor.soft_wrap_mode(cx) { + SoftWrap::None => (MAX_LINE_LEN / 2) as f32 * em_advance, + SoftWrap::EditorWidth => editor_width, + SoftWrap::Column(column) => editor_width.min(column as f32 * em_advance), + }; + + if editor.set_wrap_width(Some(wrap_width), cx) { + editor.snapshot(cx) + } else { + snapshot + } + }; + + let wrap_guides = editor + .wrap_guides(cx) + .iter() + .map(|(guide, active)| (self.column_pixels(*guide, cx), *active)) + .collect::>(); + + let scroll_height = Pixels::from(snapshot.max_point().row() + 1) * line_height; + // todo!("this should happen during layout") + if let EditorMode::AutoHeight { max_lines } = snapshot.mode { + todo!() + // size.set_y( + // scroll_height + // .min(constraint.max_along(Axis::Vertical)) + // .max(constraint.min_along(Axis::Vertical)) + // .max(line_height) + // .min(line_height * max_lines as f32), + // ) + } else if let EditorMode::SingleLine = snapshot.mode { + todo!() + // size.set_y(line_height.max(constraint.min_along(Axis::Vertical))) + } + // todo!() + // else if size.y().is_infinite() { + // // size.set_y(scroll_height); + // } + // + let gutter_size = size(gutter_width, bounds.size.height); + let text_size = size(text_width, bounds.size.height); + + let autoscroll_horizontally = + editor.autoscroll_vertically(bounds.size.height, line_height, cx); + let mut snapshot = editor.snapshot(cx); + + let scroll_position = snapshot.scroll_position(); + // The scroll position is a fractional point, the whole number of which represents + // the top of the window in terms of display rows. + let start_row = scroll_position.y as u32; + let height_in_lines = f32::from(bounds.size.height / line_height); + let max_row = snapshot.max_point().row(); + + // Add 1 to ensure selections bleed off screen + let end_row = 1 + cmp::min((scroll_position.y + height_in_lines).ceil() as u32, max_row); + + dbg!(start_row..end_row); + // let text_style = cx.text_style(); + // let layout_text = cx.text_system().layout_text( + // "hello world", + // text_style.font_size * cx.rem_size(), + // &[text_style.to_run("hello world".len())], + // None, + // ); + // let line_height = text_style + // .line_height + // .to_pixels(text_style.font_size.into(), cx.rem_size()); + + // layout_text.unwrap()[0] + // .paint(bounds.origin, line_height, cx) + // .unwrap(); } } diff --git a/crates/editor2/src/items.rs b/crates/editor2/src/items.rs index c439adcc44..a4d34ad36f 100644 --- a/crates/editor2/src/items.rs +++ b/crates/editor2/src/items.rs @@ -578,18 +578,24 @@ impl Item for Editor { fn tab_content(&self, detail: Option, cx: &AppContext) -> AnyElement { let theme = cx.theme(); + AnyElement::new( div() .flex() .flex_row() .items_center() - .bg(gpui::white()) - .text_color(gpui::white()) + .gap_2() .child(self.title(cx).to_string()) .children(detail.and_then(|detail| { let path = path_for_buffer(&self.buffer, detail, false, cx)?; let description = path.to_string_lossy(); - Some(util::truncate_and_trailoff(&description, MAX_TAB_TITLE_LEN)) + + Some( + div() + .text_color(theme.colors().text_muted) + .text_xs() + .child(util::truncate_and_trailoff(&description, MAX_TAB_TITLE_LEN)), + ) })), ) } @@ -625,8 +631,7 @@ impl Item for Editor { fn deactivated(&mut self, cx: &mut ViewContext) { let selection = self.selections.newest_anchor(); - todo!() - // self.push_to_nav_history(selection.head(), None, cx); + self.push_to_nav_history(selection.head(), None, cx); } fn workspace_deactivated(&mut self, cx: &mut ViewContext) { diff --git a/crates/editor2/src/scroll.rs b/crates/editor2/src/scroll.rs index 5e4b32265a..1876952ae2 100644 --- a/crates/editor2/src/scroll.rs +++ b/crates/editor2/src/scroll.rs @@ -303,20 +303,20 @@ impl Editor { self.scroll_manager.visible_line_count } - // pub(crate) fn set_visible_line_count(&mut self, lines: f32, cx: &mut ViewContext) { - // let opened_first_time = self.scroll_manager.visible_line_count.is_none(); - // self.scroll_manager.visible_line_count = Some(lines); - // if opened_first_time { - // cx.spawn(|editor, mut cx| async move { - // editor - // .update(&mut cx, |editor, cx| { - // editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx) - // }) - // .ok() - // }) - // .detach() - // } - // } + pub(crate) fn set_visible_line_count(&mut self, lines: f32, cx: &mut ViewContext) { + let opened_first_time = self.scroll_manager.visible_line_count.is_none(); + self.scroll_manager.visible_line_count = Some(lines); + if opened_first_time { + cx.spawn(|editor, mut cx| async move { + editor + .update(&mut cx, |editor, cx| { + editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx) + }) + .ok() + }) + .detach() + } + } pub fn set_scroll_position( &mut self, diff --git a/crates/editor2/src/scroll/autoscroll.rs b/crates/editor2/src/scroll/autoscroll.rs index a4c37a258e..9315d5c099 100644 --- a/crates/editor2/src/scroll/autoscroll.rs +++ b/crates/editor2/src/scroll/autoscroll.rs @@ -48,11 +48,11 @@ impl AutoscrollStrategy { impl Editor { pub fn autoscroll_vertically( &mut self, - viewport_height: f32, - line_height: f32, + viewport_height: Pixels, + line_height: Pixels, cx: &mut ViewContext, ) -> bool { - let visible_lines = viewport_height / line_height; + let visible_lines = f32::from(viewport_height / line_height); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let mut scroll_position = self.scroll_manager.scroll_position(&display_map); let max_scroll_top = if matches!(self.mode, EditorMode::AutoHeight { .. }) { diff --git a/crates/gpui2/build.rs b/crates/gpui2/build.rs index c9abfaa6bb..6e8a0868b9 100644 --- a/crates/gpui2/build.rs +++ b/crates/gpui2/build.rs @@ -20,6 +20,7 @@ fn generate_dispatch_bindings() { .header("src/platform/mac/dispatch.h") .allowlist_var("_dispatch_main_q") .allowlist_var("DISPATCH_QUEUE_PRIORITY_DEFAULT") + .allowlist_var("DISPATCH_TIME_NOW") .allowlist_function("dispatch_get_global_queue") .allowlist_function("dispatch_async_f") .allowlist_function("dispatch_after_f") diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 05ab7b2932..e016c193f2 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -165,6 +165,7 @@ pub struct AppContext { flushing_effects: bool, pending_updates: usize, pub(crate) active_drag: Option, + pub(crate) active_tooltip: Option, pub(crate) next_frame_callbacks: HashMap>, pub(crate) frame_consumers: HashMap>, pub(crate) background_executor: BackgroundExecutor, @@ -223,6 +224,7 @@ impl AppContext { flushing_effects: false, pending_updates: 0, active_drag: None, + active_tooltip: None, next_frame_callbacks: HashMap::default(), frame_consumers: HashMap::default(), background_executor: executor, @@ -1012,3 +1014,9 @@ pub(crate) struct AnyDrag { pub view: AnyView, pub cursor_offset: Point, } + +#[derive(Clone)] +pub(crate) struct AnyTooltip { + pub view: AnyView, + pub cursor_offset: Point, +} diff --git a/crates/gpui2/src/element.rs b/crates/gpui2/src/element.rs index a92dbd6ff9..2a0f557272 100644 --- a/crates/gpui2/src/element.rs +++ b/crates/gpui2/src/element.rs @@ -212,6 +212,19 @@ pub trait Component { { self.map(|this| if condition { then(this) } else { this }) } + + fn when_some(self, option: Option, then: impl FnOnce(Self, T) -> Self) -> Self + where + Self: Sized, + { + self.map(|this| { + if let Some(value) = option { + then(this, value) + } else { + this + } + }) + } } impl Component for AnyElement { diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index 56940efce4..e011041bae 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -3,7 +3,7 @@ use crate::{ ElementInteraction, FocusDisabled, FocusEnabled, FocusHandle, FocusListeners, Focusable, GlobalElementId, GroupBounds, InteractiveElementState, LayoutId, Overflow, ParentElement, Pixels, Point, SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction, - StatelessInteractive, Style, StyleRefinement, Styled, ViewContext, + StatelessInteractive, Style, StyleRefinement, Styled, ViewContext, Visibility, }; use refineable::Refineable; use smallvec::SmallVec; @@ -249,11 +249,22 @@ where cx: &mut ViewContext, ) { self.with_element_id(cx, |this, _global_id, cx| { + let style = this.compute_style(bounds, element_state, cx); + if style.visibility == Visibility::Hidden { + return; + } + + if let Some(mouse_cursor) = style.mouse_cursor { + let hovered = bounds.contains_point(&cx.mouse_position()); + if hovered { + cx.set_cursor_style(mouse_cursor); + } + } + if let Some(group) = this.group.clone() { GroupBounds::push(group, bounds, cx); } - let style = this.compute_style(bounds, element_state, cx); let z_index = style.z_index.unwrap_or(0); let mut child_min = point(Pixels::MAX, Pixels::MAX); diff --git a/crates/gpui2/src/geometry.rs b/crates/gpui2/src/geometry.rs index b2fad4efda..d6755a5397 100644 --- a/crates/gpui2/src/geometry.rs +++ b/crates/gpui2/src/geometry.rs @@ -21,7 +21,7 @@ pub fn point(x: T, y: T) -> Point { } impl Point { - pub fn new(x: T, y: T) -> Self { + pub const fn new(x: T, y: T) -> Self { Self { x, y } } @@ -825,6 +825,12 @@ impl From for u32 { } } +impl From for Pixels { + fn from(pixels: u32) -> Self { + Pixels(pixels as f32) + } +} + impl From for usize { fn from(pixels: Pixels) -> Self { pixels.0 as usize diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index 020cb82cd2..da208b3813 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -1,8 +1,8 @@ use crate::{ - div, point, px, Action, AnyDrag, AnyView, AppContext, BorrowWindow, Bounds, Component, - DispatchContext, DispatchPhase, Div, Element, ElementId, FocusHandle, KeyMatch, Keystroke, - Modifiers, Overflow, Pixels, Point, Render, SharedString, Size, Style, StyleRefinement, View, - ViewContext, + div, point, px, Action, AnyDrag, AnyTooltip, AnyView, AppContext, BorrowWindow, Bounds, + Component, DispatchContext, DispatchPhase, Div, Element, ElementId, FocusHandle, KeyMatch, + Keystroke, Modifiers, Overflow, Pixels, Point, Render, SharedString, Size, Style, + StyleRefinement, Task, View, ViewContext, }; use collections::HashMap; use derive_more::{Deref, DerefMut}; @@ -17,9 +17,12 @@ use std::{ ops::Deref, path::PathBuf, sync::Arc, + time::Duration, }; const DRAG_THRESHOLD: f64 = 2.; +const TOOLTIP_DELAY: Duration = Duration::from_millis(500); +const TOOLTIP_OFFSET: Point = Point::new(px(10.0), px(8.0)); pub trait StatelessInteractive: Element { fn stateless_interaction(&mut self) -> &mut StatelessInteraction; @@ -333,6 +336,37 @@ pub trait StatefulInteractive: StatelessInteractive { })); self } + + fn on_hover(mut self, listener: impl 'static + Fn(&mut V, bool, &mut ViewContext)) -> Self + where + Self: Sized, + { + debug_assert!( + self.stateful_interaction().hover_listener.is_none(), + "calling on_hover more than once on the same element is not supported" + ); + self.stateful_interaction().hover_listener = Some(Box::new(listener)); + self + } + + fn tooltip( + mut self, + build_tooltip: impl Fn(&mut V, &mut ViewContext) -> View + 'static, + ) -> Self + where + Self: Sized, + W: 'static + Render, + { + debug_assert!( + self.stateful_interaction().tooltip_builder.is_none(), + "calling tooltip more than once on the same element is not supported" + ); + self.stateful_interaction().tooltip_builder = Some(Arc::new(move |view_state, cx| { + build_tooltip(view_state, cx).into() + })); + + self + } } pub trait ElementInteraction: 'static { @@ -568,6 +602,77 @@ pub trait ElementInteraction: 'static { } } + if let Some(hover_listener) = stateful.hover_listener.take() { + let was_hovered = element_state.hover_state.clone(); + let has_mouse_down = element_state.pending_mouse_down.clone(); + + cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| { + if phase != DispatchPhase::Bubble { + return; + } + let is_hovered = + bounds.contains_point(&event.position) && has_mouse_down.lock().is_none(); + let mut was_hovered = was_hovered.lock(); + + if is_hovered != was_hovered.clone() { + *was_hovered = is_hovered; + drop(was_hovered); + + hover_listener(view_state, is_hovered, cx); + } + }); + } + + if let Some(tooltip_builder) = stateful.tooltip_builder.take() { + let active_tooltip = element_state.active_tooltip.clone(); + let pending_mouse_down = element_state.pending_mouse_down.clone(); + + cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| { + if phase != DispatchPhase::Bubble { + return; + } + + let is_hovered = bounds.contains_point(&event.position) + && pending_mouse_down.lock().is_none(); + if !is_hovered { + active_tooltip.lock().take(); + return; + } + + if active_tooltip.lock().is_none() { + let task = cx.spawn({ + let active_tooltip = active_tooltip.clone(); + let tooltip_builder = tooltip_builder.clone(); + + move |view, mut cx| async move { + cx.background_executor().timer(TOOLTIP_DELAY).await; + view.update(&mut cx, move |view_state, cx| { + active_tooltip.lock().replace(ActiveTooltip { + waiting: None, + tooltip: Some(AnyTooltip { + view: tooltip_builder(view_state, cx), + cursor_offset: cx.mouse_position() + TOOLTIP_OFFSET, + }), + }); + cx.notify(); + }) + .ok(); + } + }); + active_tooltip.lock().replace(ActiveTooltip { + waiting: Some(task), + tooltip: None, + }); + } + }); + + if let Some(active_tooltip) = element_state.active_tooltip.lock().as_ref() { + if active_tooltip.tooltip.is_some() { + cx.active_tooltip = active_tooltip.tooltip.clone() + } + } + } + let active_state = element_state.active_state.clone(); if active_state.lock().is_none() { let active_group_bounds = stateful @@ -639,6 +744,8 @@ pub struct StatefulInteraction { active_style: StyleRefinement, group_active_style: Option, drag_listener: Option>, + hover_listener: Option>, + tooltip_builder: Option>, } impl ElementInteraction for StatefulInteraction { @@ -666,6 +773,8 @@ impl From for StatefulInteraction { stateless: StatelessInteraction::default(), click_listeners: SmallVec::new(), drag_listener: None, + hover_listener: None, + tooltip_builder: None, active_style: StyleRefinement::default(), group_active_style: None, } @@ -695,6 +804,8 @@ impl StatelessInteraction { stateless: self, click_listeners: SmallVec::new(), drag_listener: None, + hover_listener: None, + tooltip_builder: None, active_style: StyleRefinement::default(), group_active_style: None, } @@ -746,8 +857,16 @@ impl ActiveState { #[derive(Default)] pub struct InteractiveElementState { active_state: Arc>, + hover_state: Arc>, pending_mouse_down: Arc>>, scroll_offset: Option>>>, + active_tooltip: Arc>>, +} + +struct ActiveTooltip { + #[allow(unused)] // used to drop the task + waiting: Option>, + tooltip: Option, } impl InteractiveElementState { @@ -1097,6 +1216,10 @@ pub type ClickListener = Box) pub(crate) type DragListener = Box, &mut ViewContext) -> AnyDrag + 'static>; +pub(crate) type HoverListener = Box) + 'static>; + +pub(crate) type TooltipBuilder = Arc) -> AnyView + 'static>; + pub type KeyListener = Box< dyn Fn( &mut V, diff --git a/crates/gpui2/src/platform/mac/dispatcher.rs b/crates/gpui2/src/platform/mac/dispatcher.rs index f5334912c6..68c0e3b4f5 100644 --- a/crates/gpui2/src/platform/mac/dispatcher.rs +++ b/crates/gpui2/src/platform/mac/dispatcher.rs @@ -11,11 +11,7 @@ use objc::{ }; use parking::{Parker, Unparker}; use parking_lot::Mutex; -use std::{ - ffi::c_void, - sync::Arc, - time::{Duration, SystemTime}, -}; +use std::{ffi::c_void, sync::Arc, time::Duration}; include!(concat!(env!("OUT_DIR"), "/dispatch_sys.rs")); @@ -62,16 +58,10 @@ impl PlatformDispatcher for MacDispatcher { } fn dispatch_after(&self, duration: Duration, runnable: Runnable) { - let now = SystemTime::now(); - let after_duration = now - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_nanos() as u64 - + duration.as_nanos() as u64; unsafe { let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.try_into().unwrap(), 0); - let when = dispatch_time(0, after_duration as i64); + let when = dispatch_time(DISPATCH_TIME_NOW as u64, duration.as_nanos() as i64); dispatch_after_f( when, queue, diff --git a/crates/gpui2/src/style.rs b/crates/gpui2/src/style.rs index b30d4aa002..d2571a3253 100644 --- a/crates/gpui2/src/style.rs +++ b/crates/gpui2/src/style.rs @@ -1,8 +1,8 @@ use crate::{ black, phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask, - Corners, CornersRefinement, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures, - FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rems, Result, Rgba, - SharedString, Size, SizeRefinement, Styled, TextRun, ViewContext, WindowContext, + Corners, CornersRefinement, CursorStyle, DefiniteLength, Edges, EdgesRefinement, Font, + FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rems, + Result, Rgba, SharedString, Size, SizeRefinement, Styled, TextRun, ViewContext, WindowContext, }; use refineable::{Cascade, Refineable}; use smallvec::SmallVec; @@ -19,6 +19,9 @@ pub struct Style { /// What layout strategy should be used? pub display: Display, + /// Should the element be painted on screen? + pub visibility: Visibility, + // Overflow properties /// How children overflowing their container should affect layout #[refineable] @@ -98,6 +101,9 @@ pub struct Style { /// TEXT pub text: TextStyleRefinement, + /// The mouse cursor style shown when the mouse pointer is over an element. + pub mouse_cursor: Option, + pub z_index: Option, } @@ -107,6 +113,13 @@ impl Styled for StyleRefinement { } } +#[derive(Default, Clone, Copy, Debug, Eq, PartialEq)] +pub enum Visibility { + #[default] + Visible, + Hidden, +} + #[derive(Clone, Debug)] pub struct BoxShadow { pub color: Hsla, @@ -297,6 +310,7 @@ impl Default for Style { fn default() -> Self { Style { display: Display::Block, + visibility: Visibility::Visible, overflow: Point { x: Overflow::Visible, y: Overflow::Visible, @@ -328,6 +342,7 @@ impl Default for Style { corner_radii: Corners::default(), box_shadow: Default::default(), text: TextStyleRefinement::default(), + mouse_cursor: None, z_index: None, } } diff --git a/crates/gpui2/src/styled.rs b/crates/gpui2/src/styled.rs index 1eed74f096..a272ab95b1 100644 --- a/crates/gpui2/src/styled.rs +++ b/crates/gpui2/src/styled.rs @@ -1,6 +1,7 @@ use crate::{ - self as gpui2, hsla, point, px, relative, rems, AlignItems, DefiniteLength, Display, Fill, - FlexDirection, Hsla, JustifyContent, Length, Position, Rems, SharedString, StyleRefinement, + self as gpui, hsla, point, px, relative, rems, AlignItems, CursorStyle, DefiniteLength, + Display, Fill, FlexDirection, Hsla, JustifyContent, Length, Position, Rems, SharedString, + StyleRefinement, Visibility, }; use crate::{BoxShadow, TextStyleRefinement}; use smallvec::smallvec; @@ -60,6 +61,54 @@ pub trait Styled { self } + /// Sets the visibility of the element to `visible`. + /// [Docs](https://tailwindcss.com/docs/visibility) + fn visible(mut self) -> Self + where + Self: Sized, + { + self.style().visibility = Some(Visibility::Visible); + self + } + + /// Sets the visibility of the element to `hidden`. + /// [Docs](https://tailwindcss.com/docs/visibility) + fn invisible(mut self) -> Self + where + Self: Sized, + { + self.style().visibility = Some(Visibility::Hidden); + self + } + + fn cursor(mut self, cursor: CursorStyle) -> Self + where + Self: Sized, + { + self.style().mouse_cursor = Some(cursor); + self + } + + /// Sets the cursor style when hovering an element to `default`. + /// [Docs](https://tailwindcss.com/docs/cursor) + fn cursor_default(mut self) -> Self + where + Self: Sized, + { + self.style().mouse_cursor = Some(CursorStyle::Arrow); + self + } + + /// Sets the cursor style when hovering an element to `pointer`. + /// [Docs](https://tailwindcss.com/docs/cursor) + fn cursor_pointer(mut self) -> Self + where + Self: Sized, + { + self.style().mouse_cursor = Some(CursorStyle::PointingHand); + self + } + /// Sets the flex direction of the element to `column`. /// [Docs](https://tailwindcss.com/docs/flex-direction#column) fn flex_col(mut self) -> Self diff --git a/crates/gpui2/src/subscription.rs b/crates/gpui2/src/subscription.rs index 64fcd74dd2..2f4ec0d2f1 100644 --- a/crates/gpui2/src/subscription.rs +++ b/crates/gpui2/src/subscription.rs @@ -14,7 +14,7 @@ impl Clone for SubscriberSet { } struct SubscriberSetState { - subscribers: BTreeMap>, + subscribers: BTreeMap>>, dropped_subscribers: BTreeSet<(EmitterKey, usize)>, next_subscriber_id: usize, } @@ -38,12 +38,18 @@ where lock.subscribers .entry(emitter_key.clone()) .or_default() + .get_or_insert_with(|| Default::default()) .insert(subscriber_id, callback); let this = self.0.clone(); Subscription { unsubscribe: Some(Box::new(move || { let mut lock = this.lock(); - if let Some(subscribers) = lock.subscribers.get_mut(&emitter_key) { + let Some(subscribers) = lock.subscribers.get_mut(&emitter_key) else { + // remove was called with this emitter_key + return; + }; + + if let Some(subscribers) = subscribers { subscribers.remove(&subscriber_id); if subscribers.is_empty() { lock.subscribers.remove(&emitter_key); @@ -62,34 +68,43 @@ where pub fn remove(&self, emitter: &EmitterKey) -> impl IntoIterator { let subscribers = self.0.lock().subscribers.remove(&emitter); - subscribers.unwrap_or_default().into_values() + subscribers + .unwrap_or_default() + .map(|s| s.into_values()) + .into_iter() + .flatten() } pub fn retain(&self, emitter: &EmitterKey, mut f: F) where F: FnMut(&mut Callback) -> bool, { - let entry = self.0.lock().subscribers.remove_entry(emitter); - if let Some((emitter, mut subscribers)) = entry { - subscribers.retain(|_, callback| f(callback)); - let mut lock = self.0.lock(); + let Some(mut subscribers) = self + .0 + .lock() + .subscribers + .get_mut(emitter) + .and_then(|s| s.take()) + else { + return; + }; - // Add any new subscribers that were added while invoking the callback. - if let Some(new_subscribers) = lock.subscribers.remove(&emitter) { - subscribers.extend(new_subscribers); - } + subscribers.retain(|_, callback| f(callback)); + let mut lock = self.0.lock(); - // Remove any dropped subscriptions that were dropped while invoking the callback. - for (dropped_emitter, dropped_subscription_id) in - mem::take(&mut lock.dropped_subscribers) - { - debug_assert_eq!(emitter, dropped_emitter); - subscribers.remove(&dropped_subscription_id); - } + // Add any new subscribers that were added while invoking the callback. + if let Some(Some(new_subscribers)) = lock.subscribers.remove(&emitter) { + subscribers.extend(new_subscribers); + } - if !subscribers.is_empty() { - lock.subscribers.insert(emitter, subscribers); - } + // Remove any dropped subscriptions that were dropped while invoking the callback. + for (dropped_emitter, dropped_subscription_id) in mem::take(&mut lock.dropped_subscribers) { + debug_assert_eq!(*emitter, dropped_emitter); + subscribers.remove(&dropped_subscription_id); + } + + if !subscribers.is_empty() { + lock.subscribers.insert(emitter.clone(), Some(subscribers)); } } } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 056d4d334e..a722dca140 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1,14 +1,14 @@ use crate::{ px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace, - Bounds, BoxShadow, Context, Corners, DevicePixels, DispatchContext, DisplayId, Edges, Effect, - Entity, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId, - Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, LayoutId, - Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, - MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformWindow, Point, - PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, - RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, - Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, - WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, + Bounds, BoxShadow, Context, Corners, CursorStyle, DevicePixels, DispatchContext, DisplayId, + Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId, + GlobalElementId, GlyphId, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, + KeyMatcher, Keystroke, LayoutId, Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, + MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, + PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, + RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, + Style, SubscriberSet, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, + VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Result}; use collections::HashMap; @@ -190,6 +190,7 @@ pub struct Window { pub(crate) focus_handles: Arc>>, default_prevented: bool, mouse_position: Point, + requested_cursor_style: Option, scale_factor: f32, bounds: WindowBounds, bounds_observers: SubscriberSet<(), AnyObserver>, @@ -283,6 +284,7 @@ impl Window { focus_handles: Arc::new(RwLock::new(SlotMap::with_key())), default_prevented: true, mouse_position, + requested_cursor_style: None, scale_factor, bounds, bounds_observers: SubscriberSet::new(), @@ -669,6 +671,10 @@ impl<'a> WindowContext<'a> { self.window.mouse_position } + pub fn set_cursor_style(&mut self, style: CursorStyle) { + self.window.requested_cursor_style = Some(style) + } + /// Called during painting to invoke the given closure in a new stacking context. The given /// z-index is interpreted relative to the previous call to `stack`. pub fn stack(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R { @@ -981,12 +987,27 @@ impl<'a> WindowContext<'a> { cx.active_drag = Some(active_drag); }); }); + } else if let Some(active_tooltip) = self.app.active_tooltip.take() { + self.stack(1, |cx| { + cx.with_element_offset(Some(active_tooltip.cursor_offset), |cx| { + let available_space = + size(AvailableSpace::MinContent, AvailableSpace::MinContent); + active_tooltip.view.draw(available_space, cx); + }); + }); } self.window.root_view = Some(root_view); let scene = self.window.scene_builder.build(); self.window.platform_window.draw(scene); + let cursor_style = self + .window + .requested_cursor_style + .take() + .unwrap_or(CursorStyle::Arrow); + self.platform.set_cursor_style(cursor_style); + self.window.dirty = false; } diff --git a/crates/gpui2_macros/src/derive_component.rs b/crates/gpui2_macros/src/derive_component.rs index a946703310..aaf814497a 100644 --- a/crates/gpui2_macros/src/derive_component.rs +++ b/crates/gpui2_macros/src/derive_component.rs @@ -28,9 +28,9 @@ pub fn derive_component(input: TokenStream) -> TokenStream { let (_, ty_generics, _) = ast.generics.split_for_impl(); let expanded = quote! { - impl #impl_generics gpui2::Component<#view_type> for #name #ty_generics #where_clause { - fn render(self) -> gpui2::AnyElement<#view_type> { - (move |view_state: &mut #view_type, cx: &mut gpui2::ViewContext<'_, #view_type>| self.render(view_state, cx)) + impl #impl_generics gpui::Component<#view_type> for #name #ty_generics #where_clause { + fn render(self) -> gpui::AnyElement<#view_type> { + (move |view_state: &mut #view_type, cx: &mut gpui::ViewContext<'_, #view_type>| self.render(view_state, cx)) .render() } } diff --git a/crates/gpui2_macros/src/style_helpers.rs b/crates/gpui2_macros/src/style_helpers.rs index 9e47d85ef1..57aef03afa 100644 --- a/crates/gpui2_macros/src/style_helpers.rs +++ b/crates/gpui2_macros/src/style_helpers.rs @@ -123,7 +123,7 @@ fn generate_predefined_setter( .iter() .map(|field_tokens| { quote! { - style.#field_tokens = Some((#negation_token gpui2::#length_tokens).into()); + style.#field_tokens = Some((#negation_token gpui::#length_tokens).into()); } }) .collect::>(); @@ -163,7 +163,7 @@ fn generate_custom_value_setter( let method = quote! { #[doc = #doc_string] - fn #method_name(mut self, length: impl std::clone::Clone + Into) -> Self where Self: std::marker::Sized { + fn #method_name(mut self, length: impl std::clone::Clone + Into) -> Self where Self: std::marker::Sized { let style = self.style(); #(#field_assignments)* self diff --git a/crates/gpui_macros/src/gpui_macros.rs b/crates/gpui_macros/src/gpui_macros.rs index aa55c27eaa..62fba3b612 100644 --- a/crates/gpui_macros/src/gpui_macros.rs +++ b/crates/gpui_macros/src/gpui_macros.rs @@ -170,6 +170,8 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { #max_retries, #detect_nondeterminism, &mut |cx, foreground_platform, deterministic, seed| { + // some of the macro contents do not use all variables, silence the warnings + let _ = (&cx, &foreground_platform, &deterministic, &seed); #cx_vars cx.foreground().run(#inner_fn_name(#inner_fn_args)); #cx_teardowns @@ -247,6 +249,8 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { #max_retries, #detect_nondeterminism, &mut |cx, foreground_platform, deterministic, seed| { + // some of the macro contents do not use all variables, silence the warnings + let _ = (&cx, &foreground_platform, &deterministic, &seed); #cx_vars #inner_fn_name(#inner_fn_args); #cx_teardowns diff --git a/crates/live_kit_client2/Cargo.toml b/crates/live_kit_client2/Cargo.toml index 5adb711948..b606434b05 100644 --- a/crates/live_kit_client2/Cargo.toml +++ b/crates/live_kit_client2/Cargo.toml @@ -16,14 +16,14 @@ name = "test_app" test-support = [ "async-trait", "collections/test-support", - "gpui2/test-support", + "gpui/test-support", "live_kit_server", "nanoid", ] [dependencies] collections = { path = "../collections", optional = true } -gpui2 = { package = "gpui2", path = "../gpui2", optional = true } +gpui = { package = "gpui2", path = "../gpui2", optional = true } live_kit_server = { path = "../live_kit_server", optional = true } media = { path = "../media" } @@ -41,7 +41,7 @@ nanoid = { version ="0.4", optional = true} [dev-dependencies] collections = { path = "../collections", features = ["test-support"] } -gpui2 = { package = "gpui2", path = "../gpui2", features = ["test-support"] } +gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } live_kit_server = { path = "../live_kit_server" } media = { path = "../media" } nanoid = "0.4" diff --git a/crates/live_kit_client2/examples/test_app.rs b/crates/live_kit_client2/examples/test_app.rs index 4062441a06..98302eb35c 100644 --- a/crates/live_kit_client2/examples/test_app.rs +++ b/crates/live_kit_client2/examples/test_app.rs @@ -1,7 +1,7 @@ use std::{sync::Arc, time::Duration}; use futures::StreamExt; -use gpui2::KeyBinding; +use gpui::KeyBinding; use live_kit_client2::{ LocalAudioTrack, LocalVideoTrack, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate, Room, }; @@ -16,7 +16,7 @@ struct Quit; fn main() { SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger"); - gpui2::App::production(Arc::new(())).run(|cx| { + gpui::App::production(Arc::new(())).run(|cx| { #[cfg(any(test, feature = "test-support"))] println!("USING TEST LIVEKIT"); @@ -173,6 +173,6 @@ fn main() { }); } -fn quit(_: &Quit, cx: &mut gpui2::AppContext) { +fn quit(_: &Quit, cx: &mut gpui::AppContext) { cx.quit(); } diff --git a/crates/live_kit_client2/src/test.rs b/crates/live_kit_client2/src/test.rs index 275b8e844d..1106e66f31 100644 --- a/crates/live_kit_client2/src/test.rs +++ b/crates/live_kit_client2/src/test.rs @@ -2,7 +2,7 @@ use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use collections::{BTreeMap, HashMap}; use futures::Stream; -use gpui2::BackgroundExecutor; +use gpui::BackgroundExecutor; use live_kit_server::token; use media::core_video::CVImageBuffer; use parking_lot::Mutex; diff --git a/crates/storybook2/Cargo.toml b/crates/storybook2/Cargo.toml index 0f335ae984..1f3a0b33cc 100644 --- a/crates/storybook2/Cargo.toml +++ b/crates/storybook2/Cargo.toml @@ -14,7 +14,7 @@ anyhow.workspace = true backtrace-on-stack-overflow = "0.3.0" clap = { version = "4.4", features = ["derive", "string"] } chrono = "0.4" -gpui2 = { path = "../gpui2" } +gpui = { package = "gpui2", path = "../gpui2" } itertools = "0.11.0" log.workspace = true rust-embed.workspace = true @@ -29,4 +29,4 @@ ui = { package = "ui2", path = "../ui2", features = ["stories"] } util = { path = "../util" } [dev-dependencies] -gpui2 = { path = "../gpui2", features = ["test-support"] } +gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } diff --git a/crates/storybook2/src/assets.rs b/crates/storybook2/src/assets.rs index 3d42984548..9fc71917b4 100644 --- a/crates/storybook2/src/assets.rs +++ b/crates/storybook2/src/assets.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use anyhow::{anyhow, Result}; -use gpui2::{AssetSource, SharedString}; +use gpui::{AssetSource, SharedString}; use rust_embed::RustEmbed; #[derive(RustEmbed)] diff --git a/crates/storybook2/src/stories/colors.rs b/crates/storybook2/src/stories/colors.rs index c1c65d62fa..13b7b36a8c 100644 --- a/crates/storybook2/src/stories/colors.rs +++ b/crates/storybook2/src/stories/colors.rs @@ -1,5 +1,5 @@ use crate::story::Story; -use gpui2::{px, Div, Render}; +use gpui::{px, Div, Render}; use theme2::{default_color_scales, ColorScaleStep}; use ui::prelude::*; @@ -20,7 +20,7 @@ impl Render for ColorsStory { .flex_col() .gap_1() .overflow_y_scroll() - .text_color(gpui2::white()) + .text_color(gpui::white()) .children(color_scales.into_iter().map(|scale| { div() .flex() diff --git a/crates/storybook2/src/stories/focus.rs b/crates/storybook2/src/stories/focus.rs index aa71040b47..16c03f87d5 100644 --- a/crates/storybook2/src/stories/focus.rs +++ b/crates/storybook2/src/stories/focus.rs @@ -1,4 +1,4 @@ -use gpui2::{ +use gpui::{ div, Div, FocusEnabled, Focusable, KeyBinding, ParentElement, Render, StatefulInteraction, StatelessInteractive, Styled, View, VisualContext, WindowContext, }; @@ -33,7 +33,7 @@ impl FocusStory { impl Render for FocusStory { type Element = Div, FocusEnabled>; - fn render(&mut self, cx: &mut gpui2::ViewContext) -> Self::Element { + fn render(&mut self, cx: &mut gpui::ViewContext) -> Self::Element { let theme = cx.theme(); let color_1 = theme.styles.git.created; let color_2 = theme.styles.git.modified; diff --git a/crates/storybook2/src/stories/kitchen_sink.rs b/crates/storybook2/src/stories/kitchen_sink.rs index cf8277c4f4..54d6f2a3a9 100644 --- a/crates/storybook2/src/stories/kitchen_sink.rs +++ b/crates/storybook2/src/stories/kitchen_sink.rs @@ -1,5 +1,5 @@ use crate::{story::Story, story_selector::ComponentStory}; -use gpui2::{Div, Render, StatefulInteraction, View, VisualContext}; +use gpui::{Div, Render, StatefulInteraction, View, VisualContext}; use strum::IntoEnumIterator; use ui::prelude::*; diff --git a/crates/storybook2/src/stories/scroll.rs b/crates/storybook2/src/stories/scroll.rs index 9236629c34..cdb48603e0 100644 --- a/crates/storybook2/src/stories/scroll.rs +++ b/crates/storybook2/src/stories/scroll.rs @@ -1,4 +1,4 @@ -use gpui2::{ +use gpui::{ div, px, Component, Div, ParentElement, Render, SharedString, StatefulInteraction, Styled, View, VisualContext, WindowContext, }; @@ -15,7 +15,7 @@ impl ScrollStory { impl Render for ScrollStory { type Element = Div>; - fn render(&mut self, cx: &mut gpui2::ViewContext) -> Self::Element { + fn render(&mut self, cx: &mut gpui::ViewContext) -> Self::Element { let theme = cx.theme(); let color_1 = theme.styles.git.created; let color_2 = theme.styles.git.modified; diff --git a/crates/storybook2/src/stories/text.rs b/crates/storybook2/src/stories/text.rs index 85a9fd51a6..b4a4c86e7e 100644 --- a/crates/storybook2/src/stories/text.rs +++ b/crates/storybook2/src/stories/text.rs @@ -1,4 +1,4 @@ -use gpui2::{div, white, Div, ParentElement, Render, Styled, View, VisualContext, WindowContext}; +use gpui::{div, white, Div, ParentElement, Render, Styled, View, VisualContext, WindowContext}; pub struct TextStory; @@ -11,7 +11,7 @@ impl TextStory { impl Render for TextStory { type Element = Div; - fn render(&mut self, cx: &mut gpui2::ViewContext) -> Self::Element { + fn render(&mut self, cx: &mut gpui::ViewContext) -> Self::Element { div().size_full().bg(white()).child(concat!( "The quick brown fox jumps over the lazy dog. ", "Meanwhile, the lazy dog decided it was time for a change. ", diff --git a/crates/storybook2/src/stories/z_index.rs b/crates/storybook2/src/stories/z_index.rs index c0e1456bc0..46ec0f4a35 100644 --- a/crates/storybook2/src/stories/z_index.rs +++ b/crates/storybook2/src/stories/z_index.rs @@ -1,4 +1,4 @@ -use gpui2::{px, rgb, Div, Hsla, Render}; +use gpui::{px, rgb, Div, Hsla, Render}; use ui::prelude::*; use crate::story::Story; diff --git a/crates/storybook2/src/story_selector.rs b/crates/storybook2/src/story_selector.rs index a78705c7bb..f59208ccb8 100644 --- a/crates/storybook2/src/story_selector.rs +++ b/crates/storybook2/src/story_selector.rs @@ -5,7 +5,7 @@ use crate::stories::*; use anyhow::anyhow; use clap::builder::PossibleValue; use clap::ValueEnum; -use gpui2::{AnyView, VisualContext}; +use gpui::{AnyView, VisualContext}; use strum::{EnumIter, EnumString, IntoEnumIterator}; use ui::prelude::*; use ui::{AvatarStory, ButtonStory, DetailsStory, IconStory, InputStory, LabelStory}; @@ -19,6 +19,7 @@ pub enum ComponentStory { Buffer, Button, ChatPanel, + Checkbox, CollabPanel, Colors, CommandPalette, @@ -61,6 +62,7 @@ impl ComponentStory { Self::Buffer => cx.build_view(|_| ui::BufferStory).into(), Self::Button => cx.build_view(|_| ButtonStory).into(), Self::ChatPanel => cx.build_view(|_| ui::ChatPanelStory).into(), + Self::Checkbox => cx.build_view(|_| ui::CheckboxStory).into(), Self::CollabPanel => cx.build_view(|_| ui::CollabPanelStory).into(), Self::Colors => cx.build_view(|_| ColorsStory).into(), Self::CommandPalette => cx.build_view(|_| ui::CommandPaletteStory).into(), diff --git a/crates/storybook2/src/storybook2.rs b/crates/storybook2/src/storybook2.rs index 4e2c439db0..c8849c1342 100644 --- a/crates/storybook2/src/storybook2.rs +++ b/crates/storybook2/src/storybook2.rs @@ -8,7 +8,7 @@ mod story_selector; use std::sync::Arc; use clap::Parser; -use gpui2::{ +use gpui::{ div, px, size, AnyView, AppContext, Bounds, Div, Render, ViewContext, VisualContext, WindowBounds, WindowOptions, }; @@ -22,7 +22,7 @@ use ui::prelude::*; use crate::assets::Assets; use crate::story_selector::StorySelector; -// gpui2::actions! { +// gpui::actions! { // storybook, // [ToggleInspector] // } @@ -51,7 +51,7 @@ fn main() { let theme_name = args.theme.unwrap_or("Zed Pro Moonlight".to_string()); let asset_source = Arc::new(Assets); - gpui2::App::production(asset_source).run(move |cx| { + gpui::App::production(asset_source).run(move |cx| { load_embedded_fonts(cx).unwrap(); let mut store = SettingsStore::default(); @@ -116,7 +116,7 @@ impl Render for StoryWrapper { } } -fn load_embedded_fonts(cx: &AppContext) -> gpui2::Result<()> { +fn load_embedded_fonts(cx: &AppContext) -> gpui::Result<()> { let font_paths = cx.asset_source().list("fonts")?; let mut embedded_fonts = Vec::new(); for font_path in font_paths { diff --git a/crates/terminal2/src/mappings/colors.rs b/crates/terminal2/src/mappings/colors.rs index fc3557b4e8..d3c8443cbf 100644 --- a/crates/terminal2/src/mappings/colors.rs +++ b/crates/terminal2/src/mappings/colors.rs @@ -1,6 +1,6 @@ // todo!() use alacritty_terminal::term::color::Rgb as AlacRgb; -// use gpui2::color::Color; +// use gpui::color::Color; // use theme2::TerminalStyle; ///Converts a 2, 8, or 24 bit color ANSI color to the GPUI equivalent diff --git a/crates/theme2/src/colors.rs b/crates/theme2/src/colors.rs index b02a9c14df..1a1fd2e99e 100644 --- a/crates/theme2/src/colors.rs +++ b/crates/theme2/src/colors.rs @@ -54,18 +54,20 @@ pub struct ThemeColors { pub border: Hsla, pub border_variant: Hsla, pub border_focused: Hsla, + pub border_selected: Hsla, pub border_transparent: Hsla, - pub elevated_surface: Hsla, - pub surface: Hsla, + pub border_disabled: Hsla, + pub elevated_surface_background: Hsla, + pub surface_background: Hsla, pub background: Hsla, - pub element: Hsla, + pub element_background: Hsla, pub element_hover: Hsla, pub element_active: Hsla, pub element_selected: Hsla, pub element_disabled: Hsla, pub element_placeholder: Hsla, pub element_drop_target: Hsla, - pub ghost_element: Hsla, + pub ghost_element_background: Hsla, pub ghost_element_hover: Hsla, pub ghost_element_active: Hsla, pub ghost_element_selected: Hsla, @@ -80,15 +82,32 @@ pub struct ThemeColors { pub icon_disabled: Hsla, pub icon_placeholder: Hsla, pub icon_accent: Hsla, - pub status_bar: Hsla, - pub title_bar: Hsla, - pub toolbar: Hsla, - pub tab_bar: Hsla, - pub tab_inactive: Hsla, - pub tab_active: Hsla, - pub editor: Hsla, - pub editor_subheader: Hsla, + pub status_bar_background: Hsla, + pub title_bar_background: Hsla, + pub toolbar_background: Hsla, + pub tab_bar_background: Hsla, + pub tab_inactive_background: Hsla, + pub tab_active_background: Hsla, + pub editor_background: Hsla, + pub editor_subheader_background: Hsla, pub editor_active_line: Hsla, + pub terminal_background: Hsla, + pub terminal_ansi_bright_black: Hsla, + pub terminal_ansi_bright_red: Hsla, + pub terminal_ansi_bright_green: Hsla, + pub terminal_ansi_bright_yellow: Hsla, + pub terminal_ansi_bright_blue: Hsla, + pub terminal_ansi_bright_magenta: Hsla, + pub terminal_ansi_bright_cyan: Hsla, + pub terminal_ansi_bright_white: Hsla, + pub terminal_ansi_black: Hsla, + pub terminal_ansi_red: Hsla, + pub terminal_ansi_green: Hsla, + pub terminal_ansi_yellow: Hsla, + pub terminal_ansi_blue: Hsla, + pub terminal_ansi_magenta: Hsla, + pub terminal_ansi_cyan: Hsla, + pub terminal_ansi_white: Hsla, } #[derive(Refineable, Clone)] diff --git a/crates/theme2/src/default_colors.rs b/crates/theme2/src/default_colors.rs index 802392d296..53e34acf16 100644 --- a/crates/theme2/src/default_colors.rs +++ b/crates/theme2/src/default_colors.rs @@ -205,18 +205,20 @@ impl ThemeColors { border: neutral().light().step_6(), border_variant: neutral().light().step_5(), border_focused: blue().light().step_5(), + border_disabled: neutral().light().step_3(), + border_selected: blue().light().step_5(), border_transparent: system.transparent, - elevated_surface: neutral().light().step_2(), - surface: neutral().light().step_2(), + elevated_surface_background: neutral().light().step_2(), + surface_background: neutral().light().step_2(), background: neutral().light().step_1(), - element: neutral().light().step_3(), + element_background: neutral().light().step_3(), element_hover: neutral().light().step_4(), element_active: neutral().light().step_5(), element_selected: neutral().light().step_5(), element_disabled: neutral().light_alpha().step_3(), element_placeholder: neutral().light().step_11(), element_drop_target: blue().light_alpha().step_2(), - ghost_element: system.transparent, + ghost_element_background: system.transparent, ghost_element_hover: neutral().light().step_4(), ghost_element_active: neutral().light().step_5(), ghost_element_selected: neutral().light().step_5(), @@ -231,15 +233,32 @@ impl ThemeColors { icon_disabled: neutral().light().step_9(), icon_placeholder: neutral().light().step_10(), icon_accent: blue().light().step_11(), - status_bar: neutral().light().step_2(), - title_bar: neutral().light().step_2(), - toolbar: neutral().light().step_1(), - tab_bar: neutral().light().step_2(), - tab_active: neutral().light().step_1(), - tab_inactive: neutral().light().step_2(), - editor: neutral().light().step_1(), - editor_subheader: neutral().light().step_2(), + status_bar_background: neutral().light().step_2(), + title_bar_background: neutral().light().step_2(), + toolbar_background: neutral().light().step_1(), + tab_bar_background: neutral().light().step_2(), + tab_active_background: neutral().light().step_1(), + tab_inactive_background: neutral().light().step_2(), + editor_background: neutral().light().step_1(), + editor_subheader_background: neutral().light().step_2(), editor_active_line: neutral().light_alpha().step_3(), + terminal_background: neutral().light().step_1(), + terminal_ansi_black: black().light().step_12(), + terminal_ansi_red: red().light().step_11(), + terminal_ansi_green: green().light().step_11(), + terminal_ansi_yellow: yellow().light().step_11(), + terminal_ansi_blue: blue().light().step_11(), + terminal_ansi_magenta: violet().light().step_11(), + terminal_ansi_cyan: cyan().light().step_11(), + terminal_ansi_white: neutral().light().step_12(), + terminal_ansi_bright_black: black().light().step_11(), + terminal_ansi_bright_red: red().light().step_10(), + terminal_ansi_bright_green: green().light().step_10(), + terminal_ansi_bright_yellow: yellow().light().step_10(), + terminal_ansi_bright_blue: blue().light().step_10(), + terminal_ansi_bright_magenta: violet().light().step_10(), + terminal_ansi_bright_cyan: cyan().light().step_10(), + terminal_ansi_bright_white: neutral().light().step_11(), } } @@ -250,18 +269,20 @@ impl ThemeColors { border: neutral().dark().step_6(), border_variant: neutral().dark().step_5(), border_focused: blue().dark().step_5(), + border_disabled: neutral().dark().step_3(), + border_selected: blue().dark().step_5(), border_transparent: system.transparent, - elevated_surface: neutral().dark().step_2(), - surface: neutral().dark().step_2(), + elevated_surface_background: neutral().dark().step_2(), + surface_background: neutral().dark().step_2(), background: neutral().dark().step_1(), - element: neutral().dark().step_3(), + element_background: neutral().dark().step_3(), element_hover: neutral().dark().step_4(), element_active: neutral().dark().step_5(), element_selected: neutral().dark().step_5(), element_disabled: neutral().dark_alpha().step_3(), element_placeholder: neutral().dark().step_11(), element_drop_target: blue().dark_alpha().step_2(), - ghost_element: system.transparent, + ghost_element_background: system.transparent, ghost_element_hover: neutral().dark().step_4(), ghost_element_active: neutral().dark().step_5(), ghost_element_selected: neutral().dark().step_5(), @@ -276,15 +297,32 @@ impl ThemeColors { icon_disabled: neutral().dark().step_9(), icon_placeholder: neutral().dark().step_10(), icon_accent: blue().dark().step_11(), - status_bar: neutral().dark().step_2(), - title_bar: neutral().dark().step_2(), - toolbar: neutral().dark().step_1(), - tab_bar: neutral().dark().step_2(), - tab_active: neutral().dark().step_1(), - tab_inactive: neutral().dark().step_2(), - editor: neutral().dark().step_1(), - editor_subheader: neutral().dark().step_2(), + status_bar_background: neutral().dark().step_2(), + title_bar_background: neutral().dark().step_2(), + toolbar_background: neutral().dark().step_1(), + tab_bar_background: neutral().dark().step_2(), + tab_active_background: neutral().dark().step_1(), + tab_inactive_background: neutral().dark().step_2(), + editor_background: neutral().dark().step_1(), + editor_subheader_background: neutral().dark().step_2(), editor_active_line: neutral().dark_alpha().step_3(), + terminal_background: neutral().dark().step_1(), + terminal_ansi_black: black().dark().step_12(), + terminal_ansi_red: red().dark().step_11(), + terminal_ansi_green: green().dark().step_11(), + terminal_ansi_yellow: yellow().dark().step_11(), + terminal_ansi_blue: blue().dark().step_11(), + terminal_ansi_magenta: violet().dark().step_11(), + terminal_ansi_cyan: cyan().dark().step_11(), + terminal_ansi_white: neutral().dark().step_12(), + terminal_ansi_bright_black: black().dark().step_11(), + terminal_ansi_bright_red: red().dark().step_10(), + terminal_ansi_bright_green: green().dark().step_10(), + terminal_ansi_bright_yellow: yellow().dark().step_10(), + terminal_ansi_bright_blue: blue().dark().step_10(), + terminal_ansi_bright_magenta: violet().dark().step_10(), + terminal_ansi_bright_cyan: cyan().dark().step_10(), + terminal_ansi_bright_white: neutral().dark().step_11(), } } } diff --git a/crates/theme2/src/theme2.rs b/crates/theme2/src/theme2.rs index faf252e2e5..b8e22f8319 100644 --- a/crates/theme2/src/theme2.rs +++ b/crates/theme2/src/theme2.rs @@ -17,7 +17,7 @@ pub use syntax::*; use gpui::{AppContext, Hsla, SharedString}; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, PartialEq, Clone, Copy)] pub enum Appearance { Light, Dark, diff --git a/crates/theme2/src/themes/.gitkeep b/crates/theme2/src/themes/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/ui2/Cargo.toml b/crates/ui2/Cargo.toml index f11fd652b6..754bca371f 100644 --- a/crates/ui2/Cargo.toml +++ b/crates/ui2/Cargo.toml @@ -7,7 +7,7 @@ publish = false [dependencies] anyhow.workspace = true chrono = "0.4" -gpui2 = { path = "../gpui2" } +gpui = { package = "gpui2", path = "../gpui2" } itertools = { version = "0.11.0", optional = true } serde.workspace = true settings2 = { path = "../settings2" } diff --git a/crates/ui2/docs/building-ui.md b/crates/ui2/docs/building-ui.md index a2a3ff697b..e0160e336e 100644 --- a/crates/ui2/docs/building-ui.md +++ b/crates/ui2/docs/building-ui.md @@ -2,6 +2,16 @@ ## Common patterns +### Method ordering + +- id +- Flex properties +- Position properties +- Size properties +- Style properties +- Handlers +- State properties + ### Using the Label Component to Create UI Text The `Label` component helps in displaying text on user interfaces. It creates an interface where specific parameters such as label color, line height style, and strikethrough can be set. diff --git a/crates/ui2/docs/hello-world.md b/crates/ui2/docs/hello-world.md index c6ded9ce34..e8ed3bb944 100644 --- a/crates/ui2/docs/hello-world.md +++ b/crates/ui2/docs/hello-world.md @@ -40,12 +40,12 @@ impl TodoList { All of this is relatively straightforward. -We use [gpui2::SharedString] in components instead of [std::string::String]. This allows us to [TODO: someone who actually knows please explain why we use SharedString]. +We use [gpui::SharedString] in components instead of [std::string::String]. This allows us to [TODO: someone who actually knows please explain why we use SharedString]. When we want to pass an action we pass a `ClickHandler`. Whenever we want to add an action, the struct it belongs to needs to be generic over the view type `V`. ~~~rust -use gpui2::hsla +use gpui::hsla impl TodoList { // ... @@ -74,7 +74,7 @@ As you start using the Tailwind-style conventions you will be surprised how quic **Why `50.0/360.0` in `hsla()`?** -gpui [gpui2::Hsla] use `0.0-1.0` for all it's values, but it is common for tools to use `0-360` for hue. +gpui [gpui::Hsla] use `0.0-1.0` for all it's values, but it is common for tools to use `0-360` for hue. This may change in the future, but this is a little trick that let's you use familiar looking values. @@ -98,7 +98,7 @@ impl TodoList { Now we have access to the complete set of colors defined in the theme. ~~~rust -use gpui2::hsla +use gpui::hsla impl TodoList { // ... @@ -113,7 +113,7 @@ impl TodoList { Let's finish up some basic styles for the container then move on to adding the other elements. ~~~rust -use gpui2::hsla +use gpui::hsla impl TodoList { // ... diff --git a/crates/ui2/src/components.rs b/crates/ui2/src/components.rs index f7674c2bd4..857d0f1042 100644 --- a/crates/ui2/src/components.rs +++ b/crates/ui2/src/components.rs @@ -1,5 +1,6 @@ mod avatar; mod button; +mod checkbox; mod context_menu; mod details; mod facepile; @@ -16,13 +17,17 @@ mod palette; mod panel; mod player; mod player_stack; +mod slot; mod stack; mod tab; mod toast; +mod toggle; mod tool_divider; +mod tooltip; pub use avatar::*; pub use button::*; +pub use checkbox::*; pub use context_menu::*; pub use details::*; pub use facepile::*; @@ -39,7 +44,10 @@ pub use palette::*; pub use panel::*; pub use player::*; pub use player_stack::*; +pub use slot::*; pub use stack::*; pub use tab::*; pub use toast::*; +pub use toggle::*; pub use tool_divider::*; +pub use tooltip::*; diff --git a/crates/ui2/src/components/avatar.rs b/crates/ui2/src/components/avatar.rs index ff574a2042..9c993dfc06 100644 --- a/crates/ui2/src/components/avatar.rs +++ b/crates/ui2/src/components/avatar.rs @@ -1,4 +1,4 @@ -use gpui2::img; +use gpui::img; use crate::prelude::*; @@ -33,7 +33,7 @@ impl Avatar { img.uri(self.src.clone()) .size_4() // todo!(Pull the avatar fallback background from the theme.) - .bg(gpui2::red()) + .bg(gpui::red()) } } @@ -44,7 +44,7 @@ pub use stories::*; mod stories { use super::*; use crate::Story; - use gpui2::{Div, Render}; + use gpui::{Div, Render}; pub struct AvatarStory; diff --git a/crates/ui2/src/components/button.rs b/crates/ui2/src/components/button.rs index 073bcdbb45..437daaa982 100644 --- a/crates/ui2/src/components/button.rs +++ b/crates/ui2/src/components/button.rs @@ -1,9 +1,28 @@ use std::sync::Arc; -use gpui2::{div, rems, DefiniteLength, Hsla, MouseButton, WindowContext}; +use gpui::{div, rems, DefiniteLength, Hsla, MouseButton, WindowContext}; -use crate::prelude::*; use crate::{h_stack, Icon, IconColor, IconElement, Label, LabelColor, LineHeightStyle}; +use crate::{prelude::*, IconButton}; + +/// Provides the flexibility to use either a standard +/// button or an icon button in a given context. +pub enum ButtonOrIconButton { + Button(Button), + IconButton(IconButton), +} + +impl From> for ButtonOrIconButton { + fn from(value: Button) -> Self { + Self::Button(value) + } +} + +impl From> for ButtonOrIconButton { + fn from(value: IconButton) -> Self { + Self::IconButton(value) + } +} #[derive(Default, PartialEq, Clone, Copy)] pub enum IconPosition { @@ -22,8 +41,8 @@ pub enum ButtonVariant { impl ButtonVariant { pub fn bg_color(&self, cx: &mut WindowContext) -> Hsla { match self { - ButtonVariant::Ghost => cx.theme().colors().ghost_element, - ButtonVariant::Filled => cx.theme().colors().element, + ButtonVariant::Ghost => cx.theme().colors().ghost_element_background, + ButtonVariant::Filled => cx.theme().colors().element_background, } } @@ -42,7 +61,7 @@ impl ButtonVariant { } } -pub type ClickHandler = Arc) + Send + Sync>; +pub type ClickHandler = Arc) + Send + Sync>; struct ButtonHandlers { click: Option>, @@ -215,7 +234,7 @@ pub use stories::*; mod stories { use super::*; use crate::{h_stack, v_stack, LabelColor, Story}; - use gpui2::{rems, Div, Render}; + use gpui::{rems, Div, Render}; use strum::IntoEnumIterator; pub struct ButtonStory; diff --git a/crates/ui2/src/components/checkbox.rs b/crates/ui2/src/components/checkbox.rs new file mode 100644 index 0000000000..9f7c10a104 --- /dev/null +++ b/crates/ui2/src/components/checkbox.rs @@ -0,0 +1,229 @@ +use std::sync::Arc; + +use gpui::{ + div, Component, ElementId, ParentElement, StatefulInteractive, StatelessInteractive, Styled, + ViewContext, +}; +use theme2::ActiveTheme; + +use crate::{Icon, IconColor, IconElement, Selection}; + +pub type CheckHandler = Arc) + Send + Sync>; + +/// # Checkbox +/// +/// Checkboxes are used for multiple choices, not for mutually exclusive choices. +/// Each checkbox works independently from other checkboxes in the list, +/// therefore checking an additional box does not affect any other selections. +#[derive(Component)] +pub struct Checkbox { + id: ElementId, + checked: Selection, + disabled: bool, + on_click: Option>, +} + +impl Checkbox { + pub fn new(id: impl Into, checked: Selection) -> Self { + Self { + id: id.into(), + checked, + disabled: false, + on_click: None, + } + } + + pub fn disabled(mut self, disabled: bool) -> Self { + self.disabled = disabled; + self + } + + pub fn on_click( + mut self, + handler: impl 'static + Fn(Selection, &mut V, &mut ViewContext) + Send + Sync, + ) -> Self { + self.on_click = Some(Arc::new(handler)); + self + } + + pub fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + let group_id = format!("checkbox_group_{:?}", self.id); + + let icon = match self.checked { + // When selected, we show a checkmark. + Selection::Selected => { + Some( + IconElement::new(Icon::Check) + .size(crate::IconSize::Small) + .color( + // If the checkbox is disabled we change the color of the icon. + if self.disabled { + IconColor::Disabled + } else { + IconColor::Selected + }, + ), + ) + } + // In an indeterminate state, we show a dash. + Selection::Indeterminate => { + Some( + IconElement::new(Icon::Dash) + .size(crate::IconSize::Small) + .color( + // If the checkbox is disabled we change the color of the icon. + if self.disabled { + IconColor::Disabled + } else { + IconColor::Selected + }, + ), + ) + } + // When unselected, we show nothing. + Selection::Unselected => None, + }; + + // A checkbox could be in an indeterminate state, + // for example the indeterminate state could represent: + // - a group of options of which only some are selected + // - an enabled option that is no longer available + // - a previously agreed to license that has been updated + // + // For the sake of styles we treat the indeterminate state as selected, + // but it's icon will be different. + let selected = + self.checked == Selection::Selected || self.checked == Selection::Indeterminate; + + // We could use something like this to make the checkbox background when selected: + // + // ~~~rust + // ... + // .when(selected, |this| { + // this.bg(cx.theme().colors().element_selected) + // }) + // ~~~ + // + // But we use a match instead here because the checkbox might be disabled, + // and it could be disabled _while_ it is selected, as well as while it is not selected. + let (bg_color, border_color) = match (self.disabled, selected) { + (true, _) => ( + cx.theme().colors().ghost_element_disabled, + cx.theme().colors().border_disabled, + ), + (false, true) => ( + cx.theme().colors().element_selected, + cx.theme().colors().border, + ), + (false, false) => ( + cx.theme().colors().element_background, + cx.theme().colors().border, + ), + }; + + div() + .id(self.id) + // Rather than adding `px_1()` to add some space around the checkbox, + // we use a larger parent element to create a slightly larger + // click area for the checkbox. + .size_5() + // Because we've enlarged the click area, we need to create a + // `group` to pass down interaction events to the checkbox. + .group(group_id.clone()) + .child( + div() + .flex() + // This prevent the flex element from growing + // or shrinking in response to any size changes + .flex_none() + // The combo of `justify_center()` and `items_center()` + // is used frequently to center elements in a flex container. + // + // We use this to center the icon in the checkbox. + .justify_center() + .items_center() + .m_1() + .size_4() + .rounded_sm() + .bg(bg_color) + .border() + .border_color(border_color) + // We only want the interaction states to fire when we + // are in a checkbox that isn't disabled. + .when(!self.disabled, |this| { + // Here instead of `hover()` we use `group_hover()` + // to pass it the group id. + this.group_hover(group_id.clone(), |el| { + el.bg(cx.theme().colors().element_hover) + }) + }) + .children(icon), + ) + .when_some( + self.on_click.filter(|_| !self.disabled), + |this, on_click| { + this.on_click(move |view, _, cx| on_click(self.checked.inverse(), view, cx)) + }, + ) + } +} + +#[cfg(feature = "stories")] +pub use stories::*; + +#[cfg(feature = "stories")] +mod stories { + use super::*; + use crate::{h_stack, Story}; + use gpui::{Div, Render}; + + pub struct CheckboxStory; + + impl Render for CheckboxStory { + type Element = Div; + + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + Story::container(cx) + .child(Story::title_for::<_, Checkbox>(cx)) + .child(Story::label(cx, "Default")) + .child( + h_stack() + .p_2() + .gap_2() + .rounded_md() + .border() + .border_color(cx.theme().colors().border) + .child(Checkbox::new("checkbox-enabled", Selection::Unselected)) + .child(Checkbox::new( + "checkbox-intermediate", + Selection::Indeterminate, + )) + .child(Checkbox::new("checkbox-selected", Selection::Selected)), + ) + .child(Story::label(cx, "Disabled")) + .child( + h_stack() + .p_2() + .gap_2() + .rounded_md() + .border() + .border_color(cx.theme().colors().border) + .child( + Checkbox::new("checkbox-disabled", Selection::Unselected) + .disabled(true), + ) + .child( + Checkbox::new( + "checkbox-disabled-intermediate", + Selection::Indeterminate, + ) + .disabled(true), + ) + .child( + Checkbox::new("checkbox-disabled-selected", Selection::Selected) + .disabled(true), + ), + ) + } + } +} diff --git a/crates/ui2/src/components/context_menu.rs b/crates/ui2/src/components/context_menu.rs index 8345be1b35..117be12779 100644 --- a/crates/ui2/src/components/context_menu.rs +++ b/crates/ui2/src/components/context_menu.rs @@ -8,7 +8,7 @@ pub enum ContextMenuItem { } impl ContextMenuItem { - fn to_list_item(self) -> ListItem { + fn to_list_item(self) -> ListItem { match self { ContextMenuItem::Header(label) => ListSubHeader::new(label).into(), ContextMenuItem::Entry(label) => { @@ -46,18 +46,15 @@ impl ContextMenu { fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { v_stack() .flex() - .bg(cx.theme().colors().elevated_surface) + .bg(cx.theme().colors().elevated_surface_background) .border() .border_color(cx.theme().colors().border) - .child( - List::new( - self.items - .into_iter() - .map(ContextMenuItem::to_list_item) - .collect(), - ) - .toggle(ToggleState::Toggled), - ) + .child(List::new( + self.items + .into_iter() + .map(ContextMenuItem::to_list_item::) + .collect(), + )) } } @@ -68,7 +65,7 @@ pub use stories::*; mod stories { use super::*; use crate::story::Story; - use gpui2::{Div, Render}; + use gpui::{Div, Render}; pub struct ContextMenuStory; diff --git a/crates/ui2/src/components/details.rs b/crates/ui2/src/components/details.rs index 1d22c81774..c7f6cc1839 100644 --- a/crates/ui2/src/components/details.rs +++ b/crates/ui2/src/components/details.rs @@ -47,7 +47,7 @@ pub use stories::*; mod stories { use super::*; use crate::{Button, Story}; - use gpui2::{Div, Render}; + use gpui::{Div, Render}; pub struct DetailsStory; diff --git a/crates/ui2/src/components/facepile.rs b/crates/ui2/src/components/facepile.rs index 21dd848a28..efac4925f8 100644 --- a/crates/ui2/src/components/facepile.rs +++ b/crates/ui2/src/components/facepile.rs @@ -33,7 +33,7 @@ pub use stories::*; mod stories { use super::*; use crate::{static_players, Story}; - use gpui2::{Div, Render}; + use gpui::{Div, Render}; pub struct FacepileStory; diff --git a/crates/ui2/src/components/icon.rs b/crates/ui2/src/components/icon.rs index 5885d76101..907f3f9187 100644 --- a/crates/ui2/src/components/icon.rs +++ b/crates/ui2/src/components/icon.rs @@ -1,4 +1,4 @@ -use gpui2::{rems, svg, Hsla}; +use gpui::{rems, svg, Hsla}; use strum::EnumIter; use crate::prelude::*; @@ -22,6 +22,7 @@ pub enum IconColor { Warning, Success, Info, + Selected, } impl IconColor { @@ -36,6 +37,7 @@ impl IconColor { IconColor::Warning => cx.theme().status().warning, IconColor::Success => cx.theme().status().success, IconColor::Info => cx.theme().status().info, + IconColor::Selected => cx.theme().colors().icon_accent, } } } @@ -55,6 +57,7 @@ pub enum Icon { ChevronRight, ChevronUp, Close, + Dash, Exit, ExclamationTriangle, File, @@ -112,6 +115,7 @@ impl Icon { Icon::ChevronRight => "icons/chevron_right.svg", Icon::ChevronUp => "icons/chevron_up.svg", Icon::Close => "icons/x.svg", + Icon::Dash => "icons/dash.svg", Icon::Exit => "icons/exit.svg", Icon::ExclamationTriangle => "icons/warning.svg", Icon::File => "icons/file.svg", @@ -182,7 +186,6 @@ impl IconElement { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let fill = self.color.color(cx); let svg_size = match self.size { IconSize::Small => rems(0.75), IconSize::Medium => rems(0.9375), @@ -192,7 +195,7 @@ impl IconElement { .size(svg_size) .flex_none() .path(self.icon.path()) - .text_color(fill) + .text_color(self.color.color(cx)) } } @@ -201,7 +204,7 @@ pub use stories::*; #[cfg(feature = "stories")] mod stories { - use gpui2::{Div, Render}; + use gpui::{Div, Render}; use strum::IntoEnumIterator; use crate::Story; diff --git a/crates/ui2/src/components/icon_button.rs b/crates/ui2/src/components/icon_button.rs index 101c845a76..cb4fb4d7f0 100644 --- a/crates/ui2/src/components/icon_button.rs +++ b/crates/ui2/src/components/icon_button.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use gpui2::{rems, MouseButton}; +use gpui::{rems, MouseButton}; use crate::{h_stack, prelude::*}; use crate::{ClickHandler, Icon, IconColor, IconElement}; @@ -73,12 +73,12 @@ impl IconButton { let (bg_color, bg_hover_color, bg_active_color) = match self.variant { ButtonVariant::Filled => ( - cx.theme().colors().element, + cx.theme().colors().element_background, cx.theme().colors().element_hover, cx.theme().colors().element_active, ), ButtonVariant::Ghost => ( - cx.theme().colors().ghost_element, + cx.theme().colors().ghost_element_background, cx.theme().colors().ghost_element_hover, cx.theme().colors().ghost_element_active, ), diff --git a/crates/ui2/src/components/indicator.rs b/crates/ui2/src/components/indicator.rs index 1f6e00e621..83030ebbee 100644 --- a/crates/ui2/src/components/indicator.rs +++ b/crates/ui2/src/components/indicator.rs @@ -1,4 +1,4 @@ -use gpui2::px; +use gpui::px; use crate::prelude::*; @@ -14,7 +14,7 @@ impl UnreadIndicator { div() .rounded_full() .border_2() - .border_color(cx.theme().colors().surface) + .border_color(cx.theme().colors().surface_background) .w(px(9.0)) .h(px(9.0)) .z_index(2) diff --git a/crates/ui2/src/components/input.rs b/crates/ui2/src/components/input.rs index 2884470ce2..e9f520346c 100644 --- a/crates/ui2/src/components/input.rs +++ b/crates/ui2/src/components/input.rs @@ -59,12 +59,12 @@ impl Input { fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { let (input_bg, input_hover_bg, input_active_bg) = match self.variant { InputVariant::Ghost => ( - cx.theme().colors().ghost_element, + cx.theme().colors().ghost_element_background, cx.theme().colors().ghost_element_hover, cx.theme().colors().ghost_element_active, ), InputVariant::Filled => ( - cx.theme().colors().element, + cx.theme().colors().element_background, cx.theme().colors().element_hover, cx.theme().colors().element_active, ), @@ -111,7 +111,7 @@ pub use stories::*; mod stories { use super::*; use crate::Story; - use gpui2::{Div, Render}; + use gpui::{Div, Render}; pub struct InputStory; diff --git a/crates/ui2/src/components/keybinding.rs b/crates/ui2/src/components/keybinding.rs index 88cabbdc88..22bbc747a2 100644 --- a/crates/ui2/src/components/keybinding.rs +++ b/crates/ui2/src/components/keybinding.rs @@ -66,7 +66,7 @@ impl Key { .rounded_md() .text_sm() .text_color(cx.theme().colors().text) - .bg(cx.theme().colors().element) + .bg(cx.theme().colors().element_background) .child(self.key.clone()) } } @@ -158,7 +158,7 @@ pub use stories::*; mod stories { use super::*; use crate::Story; - use gpui2::{Div, Render}; + use gpui::{Div, Render}; use itertools::Itertools; pub struct KeybindingStory; diff --git a/crates/ui2/src/components/label.rs b/crates/ui2/src/components/label.rs index d1d4d6630c..dd078d2331 100644 --- a/crates/ui2/src/components/label.rs +++ b/crates/ui2/src/components/label.rs @@ -1,4 +1,4 @@ -use gpui2::{relative, rems, Hsla, WindowContext}; +use gpui::{relative, rems, Hsla, WindowContext}; use smallvec::SmallVec; use crate::prelude::*; @@ -194,7 +194,7 @@ pub use stories::*; mod stories { use super::*; use crate::Story; - use gpui2::{Div, Render}; + use gpui::{Div, Render}; pub struct LabelStory; diff --git a/crates/ui2/src/components/list.rs b/crates/ui2/src/components/list.rs index 50a86ff256..c11860e9a5 100644 --- a/crates/ui2/src/components/list.rs +++ b/crates/ui2/src/components/list.rs @@ -1,11 +1,11 @@ -use gpui2::{div, px, relative, Div}; +use gpui::div; use crate::settings::user_settings; use crate::{ - h_stack, v_stack, Avatar, ClickHandler, Icon, IconColor, IconElement, IconSize, Label, - LabelColor, + disclosure_control, h_stack, v_stack, Avatar, Icon, IconColor, IconElement, IconSize, Label, + LabelColor, Toggle, }; -use crate::{prelude::*, Button}; +use crate::{prelude::*, GraphicSlot}; #[derive(Clone, Copy, Default, Debug, PartialEq)] pub enum ListItemVariant { @@ -29,7 +29,7 @@ pub struct ListHeader { left_icon: Option, meta: Option, variant: ListItemVariant, - toggleable: Toggleable, + toggle: Toggle, } impl ListHeader { @@ -39,17 +39,12 @@ impl ListHeader { left_icon: None, meta: None, variant: ListItemVariant::default(), - toggleable: Toggleable::NotToggleable, + toggle: Toggle::NotToggleable, } } - pub fn toggle(mut self, toggle: ToggleState) -> Self { - self.toggleable = toggle.into(); - self - } - - pub fn toggleable(mut self, toggleable: Toggleable) -> Self { - self.toggleable = toggleable; + pub fn toggle(mut self, toggle: Toggle) -> Self { + self.toggle = toggle; self } @@ -63,30 +58,8 @@ impl ListHeader { self } - fn disclosure_control(&self) -> Div { - let is_toggleable = self.toggleable != Toggleable::NotToggleable; - let is_toggled = Toggleable::is_toggled(&self.toggleable); - - match (is_toggleable, is_toggled) { - (false, _) => div(), - (_, true) => div().child( - IconElement::new(Icon::ChevronDown) - .color(IconColor::Muted) - .size(IconSize::Small), - ), - (_, false) => div().child( - IconElement::new(Icon::ChevronRight) - .color(IconColor::Muted) - .size(IconSize::Small), - ), - } - } - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let is_toggleable = self.toggleable != Toggleable::NotToggleable; - let is_toggled = self.toggleable.is_toggled(); - - let disclosure_control = self.disclosure_control(); + let disclosure_control = disclosure_control(self.toggle); let meta = match self.meta { Some(ListHeaderMeta::Tools(icons)) => div().child( @@ -106,7 +79,7 @@ impl ListHeader { h_stack() .w_full() - .bg(cx.theme().colors().surface) + .bg(cx.theme().colors().surface_background) // TODO: Add focus state // .when(self.state == InteractionState::Focused, |this| { // this.border() @@ -193,12 +166,6 @@ impl ListSubHeader { } } -#[derive(Clone)] -pub enum LeftContent { - Icon(Icon), - Avatar(SharedString), -} - #[derive(Default, PartialEq, Copy, Clone)] pub enum ListEntrySize { #[default] @@ -207,44 +174,36 @@ pub enum ListEntrySize { } #[derive(Component)] -pub enum ListItem { +pub enum ListItem { Entry(ListEntry), - Details(ListDetailsEntry), Separator(ListSeparator), Header(ListSubHeader), } -impl From for ListItem { +impl From for ListItem { fn from(entry: ListEntry) -> Self { Self::Entry(entry) } } -impl From> for ListItem { - fn from(entry: ListDetailsEntry) -> Self { - Self::Details(entry) - } -} - -impl From for ListItem { +impl From for ListItem { fn from(entry: ListSeparator) -> Self { Self::Separator(entry) } } -impl From for ListItem { +impl From for ListItem { fn from(entry: ListSubHeader) -> Self { Self::Header(entry) } } -impl ListItem { - fn render(self, view: &mut V, cx: &mut ViewContext) -> impl Component { +impl ListItem { + fn render(self, view: &mut V, cx: &mut ViewContext) -> impl Component { match self { ListItem::Entry(entry) => div().child(entry.render(view, cx)), ListItem::Separator(separator) => div().child(separator.render(view, cx)), ListItem::Header(header) => div().child(header.render(view, cx)), - ListItem::Details(details) => div().child(details.render(view, cx)), } } @@ -263,31 +222,29 @@ impl ListItem { #[derive(Component)] pub struct ListEntry { - disclosure_control_style: DisclosureControlVisibility, + disabled: bool, + // TODO: Reintroduce this + // disclosure_control_style: DisclosureControlVisibility, indent_level: u32, label: Label, - left_content: Option, - variant: ListItemVariant, - size: ListEntrySize, - state: InteractionState, - toggle: Option, + left_slot: Option, overflow: OverflowStyle, + size: ListEntrySize, + toggle: Toggle, + variant: ListItemVariant, } impl ListEntry { pub fn new(label: Label) -> Self { Self { - disclosure_control_style: DisclosureControlVisibility::default(), + disabled: false, indent_level: 0, label, - variant: ListItemVariant::default(), - left_content: None, - size: ListEntrySize::default(), - state: InteractionState::default(), - // TODO: Should use Toggleable::NotToggleable - // or remove Toggleable::NotToggleable from the system - toggle: None, + left_slot: None, overflow: OverflowStyle::Hidden, + size: ListEntrySize::default(), + toggle: Toggle::NotToggleable, + variant: ListItemVariant::default(), } } @@ -301,28 +258,23 @@ impl ListEntry { self } - pub fn toggle(mut self, toggle: ToggleState) -> Self { - self.toggle = Some(toggle); + pub fn toggle(mut self, toggle: Toggle) -> Self { + self.toggle = toggle; self } - pub fn left_content(mut self, left_content: LeftContent) -> Self { - self.left_content = Some(left_content); + pub fn left_content(mut self, left_content: GraphicSlot) -> Self { + self.left_slot = Some(left_content); self } pub fn left_icon(mut self, left_icon: Icon) -> Self { - self.left_content = Some(LeftContent::Icon(left_icon)); + self.left_slot = Some(GraphicSlot::Icon(left_icon)); self } pub fn left_avatar(mut self, left_avatar: impl Into) -> Self { - self.left_content = Some(LeftContent::Avatar(left_avatar.into())); - self - } - - pub fn state(mut self, state: InteractionState) -> Self { - self.state = state; + self.left_slot = Some(GraphicSlot::Avatar(left_avatar.into())); self } @@ -331,63 +283,19 @@ impl ListEntry { self } - pub fn disclosure_control_style( - mut self, - disclosure_control_style: DisclosureControlVisibility, - ) -> Self { - self.disclosure_control_style = disclosure_control_style; - self - } - - fn label_color(&self) -> LabelColor { - match self.state { - InteractionState::Disabled => LabelColor::Disabled, - _ => Default::default(), - } - } - - fn icon_color(&self) -> IconColor { - match self.state { - InteractionState::Disabled => IconColor::Disabled, - _ => Default::default(), - } - } - - fn disclosure_control( - &mut self, - cx: &mut ViewContext, - ) -> Option> { - let disclosure_control_icon = if let Some(ToggleState::Toggled) = self.toggle { - IconElement::new(Icon::ChevronDown) - } else { - IconElement::new(Icon::ChevronRight) - } - .color(IconColor::Muted) - .size(IconSize::Small); - - match (self.toggle, self.disclosure_control_style) { - (Some(_), DisclosureControlVisibility::OnHover) => { - Some(div().absolute().neg_left_5().child(disclosure_control_icon)) - } - (Some(_), DisclosureControlVisibility::Always) => { - Some(div().child(disclosure_control_icon)) - } - (None, _) => None, - } - } - - fn render(mut self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { let settings = user_settings(cx); - let left_content = match self.left_content.clone() { - Some(LeftContent::Icon(i)) => Some( + let left_content = match self.left_slot.clone() { + Some(GraphicSlot::Icon(i)) => Some( h_stack().child( IconElement::new(i) .size(IconSize::Small) .color(IconColor::Muted), ), ), - Some(LeftContent::Avatar(src)) => Some(h_stack().child(Avatar::new(src))), + Some(GraphicSlot::Avatar(src)) => Some(h_stack().child(Avatar::new(src))), + Some(GraphicSlot::PublicActor(src)) => Some(h_stack().child(Avatar::new(src))), None => None, }; @@ -399,11 +307,8 @@ impl ListEntry { div() .relative() .group("") - .bg(cx.theme().colors().surface) - .when(self.state == InteractionState::Focused, |this| { - this.border() - .border_color(cx.theme().colors().border_focused) - }) + .bg(cx.theme().colors().surface_background) + // TODO: Add focus state .child( sized_item .when(self.variant == ListItemVariant::Inset, |this| this.px_2()) @@ -425,131 +330,13 @@ impl ListEntry { .gap_1() .items_center() .relative() - .children(self.disclosure_control(cx)) + .child(disclosure_control(self.toggle)) .children(left_content) .child(self.label), ) } } -struct ListDetailsEntryHandlers { - click: Option>, -} - -impl Default for ListDetailsEntryHandlers { - fn default() -> Self { - Self { click: None } - } -} - -#[derive(Component)] -pub struct ListDetailsEntry { - label: SharedString, - meta: Option, - left_content: Option, - handlers: ListDetailsEntryHandlers, - actions: Option>>, - // TODO: make this more generic instead of - // specifically for notifications - seen: bool, -} - -impl ListDetailsEntry { - pub fn new(label: impl Into) -> Self { - Self { - label: label.into(), - meta: None, - left_content: None, - handlers: ListDetailsEntryHandlers::default(), - actions: None, - seen: false, - } - } - - pub fn meta(mut self, meta: impl Into) -> Self { - self.meta = Some(meta.into()); - self - } - - pub fn seen(mut self, seen: bool) -> Self { - self.seen = seen; - self - } - - pub fn on_click(mut self, handler: ClickHandler) -> Self { - self.handlers.click = Some(handler); - self - } - - pub fn actions(mut self, actions: Vec>) -> Self { - self.actions = Some(actions); - self - } - - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let settings = user_settings(cx); - - let (item_bg, item_bg_hover, item_bg_active) = ( - cx.theme().colors().ghost_element, - cx.theme().colors().ghost_element_hover, - cx.theme().colors().ghost_element_active, - ); - - let label_color = match self.seen { - true => LabelColor::Muted, - false => LabelColor::Default, - }; - - div() - .relative() - .group("") - .bg(item_bg) - .px_2() - .py_1p5() - .w_full() - .z_index(1) - .when(!self.seen, |this| { - this.child( - div() - .absolute() - .left(px(3.0)) - .top_3() - .rounded_full() - .border_2() - .border_color(cx.theme().colors().surface) - .w(px(9.0)) - .h(px(9.0)) - .z_index(2) - .bg(cx.theme().status().info), - ) - }) - .child( - v_stack() - .w_full() - .line_height(relative(1.2)) - .gap_1() - .child( - div() - .w_5() - .h_5() - .rounded_full() - .bg(cx.theme().colors().icon_accent), - ) - .child(Label::new(self.label.clone()).color(label_color)) - .children( - self.meta - .map(|meta| Label::new(meta).color(LabelColor::Muted)), - ) - .child( - h_stack() - .gap_1() - .justify_end() - .children(self.actions.unwrap_or_default()), - ), - ) - } -} - #[derive(Clone, Component)] pub struct ListSeparator; @@ -564,20 +351,22 @@ impl ListSeparator { } #[derive(Component)] -pub struct List { - items: Vec>, +pub struct List { + items: Vec, + /// Message to display when the list is empty + /// Defaults to "No items" empty_message: SharedString, header: Option, - toggleable: Toggleable, + toggle: Toggle, } -impl List { - pub fn new(items: Vec>) -> Self { +impl List { + pub fn new(items: Vec) -> Self { Self { items, empty_message: "No items".into(), header: None, - toggleable: Toggleable::default(), + toggle: Toggle::NotToggleable, } } @@ -591,19 +380,16 @@ impl List { self } - pub fn toggle(mut self, toggle: ToggleState) -> Self { - self.toggleable = toggle.into(); + pub fn toggle(mut self, toggle: Toggle) -> Self { + self.toggle = toggle; self } - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let is_toggleable = self.toggleable != Toggleable::NotToggleable; - let is_toggled = Toggleable::is_toggled(&self.toggleable); - - let list_content = match (self.items.is_empty(), is_toggled) { + fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + let list_content = match (self.items.is_empty(), self.toggle) { (false, _) => div().children(self.items), - (true, false) => div(), - (true, true) => { + (true, Toggle::Toggled(false)) => div(), + (true, _) => { div().child(Label::new(self.empty_message.clone()).color(LabelColor::Muted)) } }; @@ -611,7 +397,7 @@ impl List { v_stack() .w_full() .py_1() - .children(self.header.map(|header| header.toggleable(self.toggleable))) + .children(self.header.map(|header| header)) .child(list_content) } } diff --git a/crates/ui2/src/components/modal.rs b/crates/ui2/src/components/modal.rs index 26986474e0..75528b5c34 100644 --- a/crates/ui2/src/components/modal.rs +++ b/crates/ui2/src/components/modal.rs @@ -1,4 +1,4 @@ -use gpui2::AnyElement; +use gpui::AnyElement; use smallvec::SmallVec; use crate::{h_stack, prelude::*, v_stack, Button, Icon, IconButton, Label}; diff --git a/crates/ui2/src/components/notification_toast.rs b/crates/ui2/src/components/notification_toast.rs index 59078c98f4..aeb2aa6ed9 100644 --- a/crates/ui2/src/components/notification_toast.rs +++ b/crates/ui2/src/components/notification_toast.rs @@ -1,4 +1,4 @@ -use gpui2::rems; +use gpui::rems; use crate::prelude::*; use crate::{h_stack, Icon}; @@ -34,7 +34,7 @@ impl NotificationToast { .px_1p5() .rounded_lg() .shadow_md() - .bg(cx.theme().colors().elevated_surface) + .bg(cx.theme().colors().elevated_surface_background) .child(div().size_full().child(self.label.clone())) } } diff --git a/crates/ui2/src/components/palette.rs b/crates/ui2/src/components/palette.rs index a1f3eb7e1c..7f736433fc 100644 --- a/crates/ui2/src/components/palette.rs +++ b/crates/ui2/src/components/palette.rs @@ -47,7 +47,7 @@ impl Palette { .id(self.id.clone()) .w_96() .rounded_lg() - .bg(cx.theme().colors().elevated_surface) + .bg(cx.theme().colors().elevated_surface_background) .border() .border_color(cx.theme().colors().border) .child( @@ -56,7 +56,12 @@ impl Palette { .child(v_stack().py_0p5().px_1().child(div().px_2().py_0p5().child( Label::new(self.input_placeholder.clone()).color(LabelColor::Placeholder), ))) - .child(div().h_px().w_full().bg(cx.theme().colors().element)) + .child( + div() + .h_px() + .w_full() + .bg(cx.theme().colors().element_background), + ) .child( v_stack() .id("items") @@ -148,13 +153,13 @@ impl PaletteItem { } } -use gpui2::ElementId; +use gpui::ElementId; #[cfg(feature = "stories")] pub use stories::*; #[cfg(feature = "stories")] mod stories { - use gpui2::{Div, Render}; + use gpui::{Div, Render}; use crate::{ModifierKeys, Story}; diff --git a/crates/ui2/src/components/panel.rs b/crates/ui2/src/components/panel.rs index 5d941eb50e..1762003a2c 100644 --- a/crates/ui2/src/components/panel.rs +++ b/crates/ui2/src/components/panel.rs @@ -1,4 +1,4 @@ -use gpui2::{AbsoluteLength, AnyElement}; +use gpui::{AbsoluteLength, AnyElement}; use smallvec::SmallVec; use crate::prelude::*; @@ -107,7 +107,7 @@ impl Panel { PanelSide::Right => this.border_l(), PanelSide::Bottom => this.border_b().w_full().h(current_size), }) - .bg(cx.theme().colors().surface) + .bg(cx.theme().colors().surface_background) .border_color(cx.theme().colors().border) .children(self.children) } @@ -126,7 +126,7 @@ pub use stories::*; mod stories { use super::*; use crate::{Label, Story}; - use gpui2::{Div, Render}; + use gpui::{Div, Render}; pub struct PanelStory; diff --git a/crates/ui2/src/components/player.rs b/crates/ui2/src/components/player.rs index c7b7ade1c1..dc034a5373 100644 --- a/crates/ui2/src/components/player.rs +++ b/crates/ui2/src/components/player.rs @@ -1,7 +1,25 @@ -use gpui2::{Hsla, ViewContext}; +use gpui::{Hsla, ViewContext}; use crate::prelude::*; +/// Represents a person with a Zed account's public profile. +/// All data in this struct should be considered public. +pub struct PublicPlayer { + pub username: SharedString, + pub avatar: SharedString, + pub is_contact: bool, +} + +impl PublicPlayer { + pub fn new(username: impl Into, avatar: impl Into) -> Self { + Self { + username: username.into(), + avatar: avatar.into(), + is_contact: false, + } + } +} + #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] pub enum PlayerStatus { #[default] diff --git a/crates/ui2/src/components/slot.rs b/crates/ui2/src/components/slot.rs new file mode 100644 index 0000000000..a672694dc5 --- /dev/null +++ b/crates/ui2/src/components/slot.rs @@ -0,0 +1,14 @@ +use gpui::SharedString; + +use crate::Icon; + +#[derive(Debug, Clone)] +/// A slot utility that provides a way to to pass either +/// an icon or an image to a component. +/// +/// Can be filled with a [] +pub enum GraphicSlot { + Icon(Icon), + Avatar(SharedString), + PublicActor(SharedString), +} diff --git a/crates/ui2/src/components/stack.rs b/crates/ui2/src/components/stack.rs index 1b019ad4d9..d7bd0eb04f 100644 --- a/crates/ui2/src/components/stack.rs +++ b/crates/ui2/src/components/stack.rs @@ -1,4 +1,4 @@ -use gpui2::{div, Div}; +use gpui::{div, Div}; use crate::prelude::*; diff --git a/crates/ui2/src/components/tab.rs b/crates/ui2/src/components/tab.rs index e8b0ee3be5..416db2d172 100644 --- a/crates/ui2/src/components/tab.rs +++ b/crates/ui2/src/components/tab.rs @@ -1,6 +1,6 @@ use crate::prelude::*; use crate::{Icon, IconColor, IconElement, Label, LabelColor}; -use gpui2::{red, Div, ElementId, Render, View, VisualContext}; +use gpui::{red, Div, ElementId, Render, View, VisualContext}; #[derive(Component, Clone)] pub struct Tab { @@ -109,12 +109,12 @@ impl Tab { let (tab_bg, tab_hover_bg, tab_active_bg) = match self.current { false => ( - cx.theme().colors().tab_inactive, + cx.theme().colors().tab_inactive_background, cx.theme().colors().ghost_element_hover, cx.theme().colors().ghost_element_active, ), true => ( - cx.theme().colors().tab_active, + cx.theme().colors().tab_active_background, cx.theme().colors().element_hover, cx.theme().colors().element_active, ), diff --git a/crates/ui2/src/components/toast.rs b/crates/ui2/src/components/toast.rs index 3b81ac42b4..4164be2c3e 100644 --- a/crates/ui2/src/components/toast.rs +++ b/crates/ui2/src/components/toast.rs @@ -1,4 +1,4 @@ -use gpui2::AnyElement; +use gpui::AnyElement; use smallvec::SmallVec; use crate::prelude::*; @@ -54,7 +54,7 @@ impl Toast { .rounded_lg() .shadow_md() .overflow_hidden() - .bg(cx.theme().colors().elevated_surface) + .bg(cx.theme().colors().elevated_surface_background) .children(self.children) } } @@ -70,7 +70,7 @@ pub use stories::*; #[cfg(feature = "stories")] mod stories { - use gpui2::{Div, Render}; + use gpui::{Div, Render}; use crate::{Label, Story}; diff --git a/crates/ui2/src/components/toggle.rs b/crates/ui2/src/components/toggle.rs new file mode 100644 index 0000000000..368c95662f --- /dev/null +++ b/crates/ui2/src/components/toggle.rs @@ -0,0 +1,61 @@ +use gpui::{div, Component, ParentElement}; + +use crate::{Icon, IconColor, IconElement, IconSize}; + +/// Whether the entry is toggleable, and if so, whether it is currently toggled. +/// +/// To make an element toggleable, simply add a `Toggle::Toggled(_)` and handle it's cases. +/// +/// You can check if an element is toggleable with `.is_toggleable()` +/// +/// Possible values: +/// - `Toggle::NotToggleable` - The entry is not toggleable +/// - `Toggle::Toggled(true)` - The entry is toggleable and toggled +/// - `Toggle::Toggled(false)` - The entry is toggleable and not toggled +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Toggle { + NotToggleable, + Toggled(bool), +} + +impl Toggle { + /// Returns true if the entry is toggled (or is not toggleable.) + /// + /// As element that isn't toggleable is always "expanded" or "enabled" + /// returning true in that case makes sense. + pub fn is_toggled(&self) -> bool { + match self { + Self::Toggled(false) => false, + _ => true, + } + } + + pub fn is_toggleable(&self) -> bool { + match self { + Self::Toggled(_) => true, + _ => false, + } + } +} + +impl From for Toggle { + fn from(toggled: bool) -> Self { + Toggle::Toggled(toggled) + } +} + +pub fn disclosure_control(toggle: Toggle) -> impl Component { + match (toggle.is_toggleable(), toggle.is_toggled()) { + (false, _) => div(), + (_, true) => div().child( + IconElement::new(Icon::ChevronDown) + .color(IconColor::Muted) + .size(IconSize::Small), + ), + (_, false) => div().child( + IconElement::new(Icon::ChevronRight) + .color(IconColor::Muted) + .size(IconSize::Small), + ), + } +} diff --git a/crates/ui2/src/components/tooltip.rs b/crates/ui2/src/components/tooltip.rs new file mode 100644 index 0000000000..8463092012 --- /dev/null +++ b/crates/ui2/src/components/tooltip.rs @@ -0,0 +1,31 @@ +use gpui::{div, px, Div, ParentElement, Render, SharedString, Styled, ViewContext}; +use theme2::ActiveTheme; + +#[derive(Clone, Debug)] +pub struct TextTooltip { + title: SharedString, +} + +impl TextTooltip { + pub fn new(str: SharedString) -> Self { + Self { title: str } + } +} + +impl Render for TextTooltip { + type Element = Div; + + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + let theme = cx.theme(); + div() + .bg(theme.colors().background) + .rounded(px(8.)) + .border() + .font("Zed Sans") + .border_color(theme.colors().border) + .text_color(theme.colors().text) + .pl_2() + .pr_2() + .child(self.title.clone()) + } +} diff --git a/crates/ui2/src/prelude.rs b/crates/ui2/src/prelude.rs index fbb7ccc528..c942f0aa3b 100644 --- a/crates/ui2/src/prelude.rs +++ b/crates/ui2/src/prelude.rs @@ -1,4 +1,4 @@ -pub use gpui2::{ +pub use gpui::{ div, Component, Element, ElementId, ParentElement, SharedString, StatefulInteractive, StatelessInteractive, Styled, ViewContext, WindowContext, }; @@ -7,27 +7,9 @@ pub use crate::elevation::*; pub use crate::ButtonVariant; pub use theme2::ActiveTheme; -use gpui2::Hsla; +use gpui::Hsla; use strum::EnumIter; -/// Represents a person with a Zed account's public profile. -/// All data in this struct should be considered public. -pub struct PublicActor { - pub username: SharedString, - pub avatar: SharedString, - pub is_contact: bool, -} - -impl PublicActor { - pub fn new(username: impl Into, avatar: impl Into) -> Self { - Self { - username: username.into(), - avatar: avatar.into(), - is_contact: false, - } - } -} - #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] pub enum FileSystemStatus { #[default] @@ -172,68 +154,19 @@ impl InteractionState { } } -#[derive(Default, PartialEq)] -pub enum SelectedState { +#[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Copy)] +pub enum Selection { #[default] Unselected, - PartiallySelected, + Indeterminate, Selected, } -#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] -pub enum Toggleable { - Toggleable(ToggleState), - #[default] - NotToggleable, -} - -impl Toggleable { - pub fn is_toggled(&self) -> bool { +impl Selection { + pub fn inverse(&self) -> Self { match self { - Self::Toggleable(ToggleState::Toggled) => true, - _ => false, - } - } -} - -impl From for Toggleable { - fn from(state: ToggleState) -> Self { - Self::Toggleable(state) - } -} - -#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] -pub enum ToggleState { - /// The "on" state of a toggleable element. - /// - /// Example: - /// - A collasable list that is currently expanded - /// - A toggle button that is currently on. - Toggled, - /// The "off" state of a toggleable element. - /// - /// Example: - /// - A collasable list that is currently collapsed - /// - A toggle button that is currently off. - #[default] - NotToggled, -} - -impl From for ToggleState { - fn from(toggleable: Toggleable) -> Self { - match toggleable { - Toggleable::Toggleable(state) => state, - Toggleable::NotToggleable => ToggleState::NotToggled, - } - } -} - -impl From for ToggleState { - fn from(toggled: bool) -> Self { - if toggled { - ToggleState::Toggled - } else { - ToggleState::NotToggled + Self::Unselected | Self::Indeterminate => Self::Selected, + Self::Selected => Self::Unselected, } } } diff --git a/crates/ui2/src/settings.rs b/crates/ui2/src/settings.rs index 6a9426f623..c3ac78316e 100644 --- a/crates/ui2/src/settings.rs +++ b/crates/ui2/src/settings.rs @@ -1,6 +1,6 @@ use std::ops::Deref; -use gpui2::{rems, AbsoluteLength, AppContext, WindowContext}; +use gpui::{rems, AbsoluteLength, AppContext, WindowContext}; use crate::prelude::*; diff --git a/crates/ui2/src/static_data.rs b/crates/ui2/src/static_data.rs index 68f625c75d..ffdd3fee98 100644 --- a/crates/ui2/src/static_data.rs +++ b/crates/ui2/src/static_data.rs @@ -3,17 +3,17 @@ use std::str::FromStr; use std::sync::Arc; use chrono::DateTime; -use gpui2::{AppContext, ViewContext}; +use gpui::{AppContext, ViewContext}; use rand::Rng; use theme2::ActiveTheme; +use crate::HighlightedText; use crate::{ Buffer, BufferRow, BufferRows, Button, EditorPane, FileSystemStatus, GitStatus, - HighlightedLine, Icon, Keybinding, Label, LabelColor, ListEntry, ListEntrySize, ListSubHeader, - Livestream, MicStatus, ModifierKeys, Notification, PaletteItem, Player, PlayerCallStatus, - PlayerWithCallStatus, PublicActor, ScreenShareStatus, Symbol, Tab, ToggleState, VideoStatus, + HighlightedLine, Icon, Keybinding, Label, LabelColor, ListEntry, ListEntrySize, Livestream, + MicStatus, ModifierKeys, Notification, PaletteItem, Player, PlayerCallStatus, + PlayerWithCallStatus, PublicPlayer, ScreenShareStatus, Symbol, Tab, Toggle, VideoStatus, }; -use crate::{HighlightedText, ListDetailsEntry}; use crate::{ListItem, NotificationAction}; pub fn static_tabs_example() -> Vec { @@ -345,7 +345,7 @@ pub fn static_new_notification_items_2() -> Vec> { DateTime::parse_from_rfc3339("2023-11-02T12:09:07Z") .unwrap() .naive_local(), - PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"), + PublicPlayer::new("as-cii", "http://github.com/as-cii.png?s=50"), [ NotificationAction::new( Button::new("Decline"), @@ -374,7 +374,7 @@ pub fn static_new_notification_items_2() -> Vec> { DateTime::parse_from_rfc3339("2023-11-01T12:09:07Z") .unwrap() .naive_local(), - PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"), + PublicPlayer::new("as-cii", "http://github.com/as-cii.png?s=50"), [ NotificationAction::new( Button::new("Decline"), @@ -403,7 +403,7 @@ pub fn static_new_notification_items_2() -> Vec> { DateTime::parse_from_rfc3339("2022-10-25T12:09:07Z") .unwrap() .naive_local(), - PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"), + PublicPlayer::new("as-cii", "http://github.com/as-cii.png?s=50"), [ NotificationAction::new( Button::new("Decline"), @@ -432,7 +432,7 @@ pub fn static_new_notification_items_2() -> Vec> { DateTime::parse_from_rfc3339("2021-10-12T12:09:07Z") .unwrap() .naive_local(), - PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"), + PublicPlayer::new("as-cii", "http://github.com/as-cii.png?s=50"), [ NotificationAction::new( Button::new("Decline"), @@ -461,7 +461,7 @@ pub fn static_new_notification_items_2() -> Vec> { DateTime::parse_from_rfc3339("1969-07-20T00:00:00Z") .unwrap() .naive_local(), - PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"), + PublicPlayer::new("as-cii", "http://github.com/as-cii.png?s=50"), [ NotificationAction::new( Button::new("Decline"), @@ -478,89 +478,12 @@ pub fn static_new_notification_items_2() -> Vec> { ] } -pub fn static_new_notification_items() -> Vec> { - vec![ - ListItem::Header(ListSubHeader::new("New")), - ListItem::Details( - ListDetailsEntry::new("maxdeviant invited you to join a stream in #design.") - .meta("4 people in stream."), - ), - ListItem::Details(ListDetailsEntry::new( - "nathansobo accepted your contact request.", - )), - ListItem::Header(ListSubHeader::new("Earlier")), - ListItem::Details( - ListDetailsEntry::new("mikaylamaki added you as a contact.").actions(vec![ - Button::new("Decline"), - Button::new("Accept").variant(crate::ButtonVariant::Filled), - ]), - ), - ListItem::Details( - ListDetailsEntry::new("maxdeviant invited you to a stream in #design.") - .seen(true) - .meta("This stream has ended."), - ), - ListItem::Details(ListDetailsEntry::new( - "as-cii accepted your contact request.", - )), - ListItem::Details( - ListDetailsEntry::new("You were added as an admin on the #gpui2 channel.").seen(true), - ), - ListItem::Details(ListDetailsEntry::new( - "osiewicz accepted your contact request.", - )), - ListItem::Details(ListDetailsEntry::new( - "ConradIrwin accepted your contact request.", - )), - ListItem::Details( - ListDetailsEntry::new("nathansobo invited you to a stream in #gpui2.") - .seen(true) - .meta("This stream has ended."), - ), - ListItem::Details(ListDetailsEntry::new( - "nathansobo accepted your contact request.", - )), - ListItem::Header(ListSubHeader::new("Earlier")), - ListItem::Details( - ListDetailsEntry::new("mikaylamaki added you as a contact.").actions(vec![ - Button::new("Decline"), - Button::new("Accept").variant(crate::ButtonVariant::Filled), - ]), - ), - ListItem::Details( - ListDetailsEntry::new("maxdeviant invited you to a stream in #design.") - .seen(true) - .meta("This stream has ended."), - ), - ListItem::Details(ListDetailsEntry::new( - "as-cii accepted your contact request.", - )), - ListItem::Details( - ListDetailsEntry::new("You were added as an admin on the #gpui2 channel.").seen(true), - ), - ListItem::Details(ListDetailsEntry::new( - "osiewicz accepted your contact request.", - )), - ListItem::Details(ListDetailsEntry::new( - "ConradIrwin accepted your contact request.", - )), - ListItem::Details( - ListDetailsEntry::new("nathansobo invited you to a stream in #gpui2.") - .seen(true) - .meta("This stream has ended."), - ), - ] - .into_iter() - .map(From::from) - .collect() -} - -pub fn static_project_panel_project_items() -> Vec> { +pub fn static_project_panel_project_items() -> Vec { vec![ ListEntry::new(Label::new("zed")) .left_icon(Icon::FolderOpen.into()) .indent_level(0) - .toggle(ToggleState::Toggled), + .toggle(Toggle::Toggled(true)), ListEntry::new(Label::new(".cargo")) .left_icon(Icon::Folder.into()) .indent_level(1), @@ -579,14 +502,14 @@ pub fn static_project_panel_project_items() -> Vec> { ListEntry::new(Label::new("assets")) .left_icon(Icon::Folder.into()) .indent_level(1) - .toggle(ToggleState::Toggled), + .toggle(Toggle::Toggled(true)), ListEntry::new(Label::new("cargo-target").color(LabelColor::Hidden)) .left_icon(Icon::Folder.into()) .indent_level(1), ListEntry::new(Label::new("crates")) .left_icon(Icon::FolderOpen.into()) .indent_level(1) - .toggle(ToggleState::Toggled), + .toggle(Toggle::Toggled(true)), ListEntry::new(Label::new("activity_indicator")) .left_icon(Icon::Folder.into()) .indent_level(2), @@ -608,38 +531,38 @@ pub fn static_project_panel_project_items() -> Vec> { ListEntry::new(Label::new("sqlez").color(LabelColor::Modified)) .left_icon(Icon::Folder.into()) .indent_level(2) - .toggle(ToggleState::NotToggled), + .toggle(Toggle::Toggled(false)), ListEntry::new(Label::new("gpui2")) .left_icon(Icon::FolderOpen.into()) .indent_level(2) - .toggle(ToggleState::Toggled), + .toggle(Toggle::Toggled(true)), ListEntry::new(Label::new("src")) .left_icon(Icon::FolderOpen.into()) .indent_level(3) - .toggle(ToggleState::Toggled), + .toggle(Toggle::Toggled(true)), ListEntry::new(Label::new("derive_element.rs")) .left_icon(Icon::FileRust.into()) .indent_level(4), ListEntry::new(Label::new("storybook").color(LabelColor::Modified)) .left_icon(Icon::FolderOpen.into()) .indent_level(1) - .toggle(ToggleState::Toggled), + .toggle(Toggle::Toggled(true)), ListEntry::new(Label::new("docs").color(LabelColor::Default)) .left_icon(Icon::Folder.into()) .indent_level(2) - .toggle(ToggleState::Toggled), + .toggle(Toggle::Toggled(true)), ListEntry::new(Label::new("src").color(LabelColor::Modified)) .left_icon(Icon::FolderOpen.into()) .indent_level(3) - .toggle(ToggleState::Toggled), + .toggle(Toggle::Toggled(true)), ListEntry::new(Label::new("ui").color(LabelColor::Modified)) .left_icon(Icon::FolderOpen.into()) .indent_level(4) - .toggle(ToggleState::Toggled), + .toggle(Toggle::Toggled(true)), ListEntry::new(Label::new("component").color(LabelColor::Created)) .left_icon(Icon::FolderOpen.into()) .indent_level(5) - .toggle(ToggleState::Toggled), + .toggle(Toggle::Toggled(true)), ListEntry::new(Label::new("facepile.rs").color(LabelColor::Default)) .left_icon(Icon::FileRust.into()) .indent_level(6), @@ -682,7 +605,7 @@ pub fn static_project_panel_project_items() -> Vec> { .collect() } -pub fn static_project_panel_single_items() -> Vec> { +pub fn static_project_panel_single_items() -> Vec { vec![ ListEntry::new(Label::new("todo.md")) .left_icon(Icon::FileDoc.into()) @@ -699,7 +622,7 @@ pub fn static_project_panel_single_items() -> Vec> { .collect() } -pub fn static_collab_panel_current_call() -> Vec> { +pub fn static_collab_panel_current_call() -> Vec { vec![ ListEntry::new(Label::new("as-cii")).left_avatar("http://github.com/as-cii.png?s=50"), ListEntry::new(Label::new("nathansobo")) @@ -712,7 +635,7 @@ pub fn static_collab_panel_current_call() -> Vec> { .collect() } -pub fn static_collab_panel_channels() -> Vec> { +pub fn static_collab_panel_channels() -> Vec { vec![ ListEntry::new(Label::new("zed")) .left_icon(Icon::Hash.into()) diff --git a/crates/ui2/src/story.rs b/crates/ui2/src/story.rs index dea4e342b4..94e38267f4 100644 --- a/crates/ui2/src/story.rs +++ b/crates/ui2/src/story.rs @@ -1,4 +1,4 @@ -use gpui2::Div; +use gpui::Div; use crate::prelude::*; diff --git a/crates/ui2/src/to_extract/assistant_panel.rs b/crates/ui2/src/to_extract/assistant_panel.rs index 51c123ad9e..8a35757f5c 100644 --- a/crates/ui2/src/to_extract/assistant_panel.rs +++ b/crates/ui2/src/to_extract/assistant_panel.rs @@ -1,6 +1,6 @@ use crate::prelude::*; use crate::{Icon, IconButton, Label, Panel, PanelSide}; -use gpui2::{rems, AbsoluteLength}; +use gpui::{rems, AbsoluteLength}; #[derive(Component)] pub struct AssistantPanel { @@ -77,7 +77,7 @@ pub use stories::*; mod stories { use super::*; use crate::Story; - use gpui2::{Div, Render}; + use gpui::{Div, Render}; pub struct AssistantPanelStory; impl Render for AssistantPanelStory { diff --git a/crates/ui2/src/to_extract/breadcrumb.rs b/crates/ui2/src/to_extract/breadcrumb.rs index 163dfabfb0..cd7df87646 100644 --- a/crates/ui2/src/to_extract/breadcrumb.rs +++ b/crates/ui2/src/to_extract/breadcrumb.rs @@ -2,7 +2,7 @@ use std::path::PathBuf; use crate::prelude::*; use crate::{h_stack, HighlightedText}; -use gpui2::Div; +use gpui::Div; #[derive(Clone)] pub struct Symbol(pub Vec); @@ -73,7 +73,7 @@ pub use stories::*; mod stories { use super::*; use crate::Story; - use gpui2::Render; + use gpui::Render; use std::str::FromStr; pub struct BreadcrumbStory; diff --git a/crates/ui2/src/to_extract/buffer.rs b/crates/ui2/src/to_extract/buffer.rs index 2b3db676ce..8ab435e994 100644 --- a/crates/ui2/src/to_extract/buffer.rs +++ b/crates/ui2/src/to_extract/buffer.rs @@ -1,4 +1,4 @@ -use gpui2::{Hsla, WindowContext}; +use gpui::{Hsla, WindowContext}; use crate::prelude::*; use crate::{h_stack, v_stack, Icon, IconElement}; @@ -220,7 +220,7 @@ impl Buffer { .flex_1() .w_full() .h_full() - .bg(cx.theme().colors().editor) + .bg(cx.theme().colors().editor_background) .children(rows) } } @@ -235,7 +235,7 @@ mod stories { empty_buffer_example, hello_world_rust_buffer_example, hello_world_rust_buffer_with_status_example, Story, }; - use gpui2::{rems, Div, Render}; + use gpui::{rems, Div, Render}; pub struct BufferStory; diff --git a/crates/ui2/src/to_extract/buffer_search.rs b/crates/ui2/src/to_extract/buffer_search.rs index 5d7de1b408..9993cd3612 100644 --- a/crates/ui2/src/to_extract/buffer_search.rs +++ b/crates/ui2/src/to_extract/buffer_search.rs @@ -1,4 +1,4 @@ -use gpui2::{Div, Render, View, VisualContext}; +use gpui::{Div, Render, View, VisualContext}; use crate::prelude::*; use crate::{h_stack, Icon, IconButton, IconColor, Input}; @@ -30,14 +30,17 @@ impl Render for BufferSearch { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Div { - h_stack().bg(cx.theme().colors().toolbar).p_2().child( - h_stack().child(Input::new("Search")).child( - IconButton::::new("replace", Icon::Replace) - .when(self.is_replace_open, |this| this.color(IconColor::Accent)) - .on_click(|buffer_search, cx| { - buffer_search.toggle_replace(cx); - }), - ), - ) + h_stack() + .bg(cx.theme().colors().toolbar_background) + .p_2() + .child( + h_stack().child(Input::new("Search")).child( + IconButton::::new("replace", Icon::Replace) + .when(self.is_replace_open, |this| this.color(IconColor::Accent)) + .on_click(|buffer_search, cx| { + buffer_search.toggle_replace(cx); + }), + ), + ) } } diff --git a/crates/ui2/src/to_extract/chat_panel.rs b/crates/ui2/src/to_extract/chat_panel.rs index f4f1ddf433..538b5dfceb 100644 --- a/crates/ui2/src/to_extract/chat_panel.rs +++ b/crates/ui2/src/to_extract/chat_panel.rs @@ -108,7 +108,7 @@ pub use stories::*; #[cfg(feature = "stories")] mod stories { use chrono::DateTime; - use gpui2::{Div, Render}; + use gpui::{Div, Render}; use crate::{Panel, Story}; diff --git a/crates/ui2/src/to_extract/collab_panel.rs b/crates/ui2/src/to_extract/collab_panel.rs index a0e3b55f63..7b785ae9e1 100644 --- a/crates/ui2/src/to_extract/collab_panel.rs +++ b/crates/ui2/src/to_extract/collab_panel.rs @@ -1,7 +1,6 @@ -use crate::prelude::*; +use crate::{prelude::*, Toggle}; use crate::{ - static_collab_panel_channels, static_collab_panel_current_call, v_stack, Icon, List, - ListHeader, ToggleState, + static_collab_panel_channels, static_collab_panel_current_call, v_stack, Icon, List, ListHeader, }; #[derive(Component)] @@ -18,7 +17,7 @@ impl CollabPanel { v_stack() .id(self.id.clone()) .h_full() - .bg(cx.theme().colors().surface) + .bg(cx.theme().colors().surface_background) .child( v_stack() .id("crdb") @@ -34,17 +33,17 @@ impl CollabPanel { .header( ListHeader::new("CRDB") .left_icon(Icon::Hash.into()) - .toggle(ToggleState::Toggled), + .toggle(Toggle::Toggled(true)), ) - .toggle(ToggleState::Toggled), + .toggle(Toggle::Toggled(true)), ), ) .child( v_stack().id("channels").py_1().child( List::new(static_collab_panel_channels()) - .header(ListHeader::new("CHANNELS").toggle(ToggleState::Toggled)) + .header(ListHeader::new("CHANNELS").toggle(Toggle::Toggled(true))) .empty_message("No channels yet. Add a channel to get started.") - .toggle(ToggleState::Toggled), + .toggle(Toggle::Toggled(true)), ), ) .child( @@ -52,9 +51,9 @@ impl CollabPanel { List::new(static_collab_panel_current_call()) .header( ListHeader::new("CONTACTS – ONLINE") - .toggle(ToggleState::Toggled), + .toggle(Toggle::Toggled(true)), ) - .toggle(ToggleState::Toggled), + .toggle(Toggle::Toggled(true)), ), ) .child( @@ -62,9 +61,9 @@ impl CollabPanel { List::new(static_collab_panel_current_call()) .header( ListHeader::new("CONTACTS – OFFLINE") - .toggle(ToggleState::NotToggled), + .toggle(Toggle::Toggled(false)), ) - .toggle(ToggleState::NotToggled), + .toggle(Toggle::Toggled(false)), ), ), ) @@ -93,7 +92,7 @@ pub use stories::*; mod stories { use super::*; use crate::Story; - use gpui2::{Div, Render}; + use gpui::{Div, Render}; pub struct CollabPanelStory; diff --git a/crates/ui2/src/to_extract/command_palette.rs b/crates/ui2/src/to_extract/command_palette.rs index 63db4359e7..8a9461c796 100644 --- a/crates/ui2/src/to_extract/command_palette.rs +++ b/crates/ui2/src/to_extract/command_palette.rs @@ -27,7 +27,7 @@ pub use stories::*; #[cfg(feature = "stories")] mod stories { - use gpui2::{Div, Render}; + use gpui::{Div, Render}; use crate::Story; diff --git a/crates/ui2/src/to_extract/copilot.rs b/crates/ui2/src/to_extract/copilot.rs index 51523d48f0..8750ab3c51 100644 --- a/crates/ui2/src/to_extract/copilot.rs +++ b/crates/ui2/src/to_extract/copilot.rs @@ -25,7 +25,7 @@ pub use stories::*; #[cfg(feature = "stories")] mod stories { - use gpui2::{Div, Render}; + use gpui::{Div, Render}; use crate::Story; diff --git a/crates/ui2/src/to_extract/editor_pane.rs b/crates/ui2/src/to_extract/editor_pane.rs index 8e54d2c2e8..fd21e81242 100644 --- a/crates/ui2/src/to_extract/editor_pane.rs +++ b/crates/ui2/src/to_extract/editor_pane.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use gpui2::{Div, Render, View, VisualContext}; +use gpui::{Div, Render, View, VisualContext}; use crate::prelude::*; use crate::{ diff --git a/crates/ui2/src/to_extract/language_selector.rs b/crates/ui2/src/to_extract/language_selector.rs index fa7f5b2bd7..694ca78e9c 100644 --- a/crates/ui2/src/to_extract/language_selector.rs +++ b/crates/ui2/src/to_extract/language_selector.rs @@ -40,7 +40,7 @@ pub use stories::*; mod stories { use super::*; use crate::Story; - use gpui2::{Div, Render}; + use gpui::{Div, Render}; pub struct LanguageSelectorStory; diff --git a/crates/ui2/src/to_extract/multi_buffer.rs b/crates/ui2/src/to_extract/multi_buffer.rs index ea130f20bd..78a22d51d0 100644 --- a/crates/ui2/src/to_extract/multi_buffer.rs +++ b/crates/ui2/src/to_extract/multi_buffer.rs @@ -24,7 +24,7 @@ impl MultiBuffer { .items_center() .justify_between() .p_4() - .bg(cx.theme().colors().editor_subheader) + .bg(cx.theme().colors().editor_subheader_background) .child(Label::new("main.rs")) .child(IconButton::new("arrow_up_right", Icon::ArrowUpRight)), ) @@ -40,7 +40,7 @@ pub use stories::*; mod stories { use super::*; use crate::{hello_world_rust_buffer_example, Story}; - use gpui2::{Div, Render}; + use gpui::{Div, Render}; pub struct MultiBufferStory; diff --git a/crates/ui2/src/to_extract/notifications_panel.rs b/crates/ui2/src/to_extract/notifications_panel.rs index 74f015ac06..b2cc4a7846 100644 --- a/crates/ui2/src/to_extract/notifications_panel.rs +++ b/crates/ui2/src/to_extract/notifications_panel.rs @@ -1,8 +1,8 @@ use crate::utils::naive_format_distance_from_now; use crate::{ - h_stack, prelude::*, static_new_notification_items_2, v_stack, Avatar, Button, Icon, - IconButton, IconElement, Label, LabelColor, LineHeightStyle, ListHeaderMeta, ListSeparator, - UnreadIndicator, + h_stack, prelude::*, static_new_notification_items_2, v_stack, Avatar, ButtonOrIconButton, + Icon, IconElement, Label, LabelColor, LineHeightStyle, ListHeaderMeta, ListSeparator, + PublicPlayer, UnreadIndicator, }; use crate::{ClickHandler, ListHeader}; @@ -22,7 +22,7 @@ impl NotificationsPanel { .flex() .flex_col() .size_full() - .bg(cx.theme().colors().surface) + .bg(cx.theme().colors().surface_background) .child( ListHeader::new("Notifications").meta(Some(ListHeaderMeta::Tools(vec![ Icon::AtSign, @@ -43,7 +43,7 @@ impl NotificationsPanel { .p_1() // TODO: Add cursor style // .cursor(Cursor::IBeam) - .bg(cx.theme().colors().element) + .bg(cx.theme().colors().element_background) .border() .border_color(cx.theme().colors().border_variant) .child( @@ -57,23 +57,6 @@ impl NotificationsPanel { } } -pub enum ButtonOrIconButton { - Button(Button), - IconButton(IconButton), -} - -impl From> for ButtonOrIconButton { - fn from(value: Button) -> Self { - Self::Button(value) - } -} - -impl From> for ButtonOrIconButton { - fn from(value: IconButton) -> Self { - Self::IconButton(value) - } -} - pub struct NotificationAction { button: ButtonOrIconButton, tooltip: SharedString, @@ -102,7 +85,7 @@ impl NotificationAction { } pub enum ActorOrIcon { - Actor(PublicActor), + Actor(PublicPlayer), Icon(Icon), } @@ -171,7 +154,7 @@ impl Notification { id: impl Into, message: impl Into, date_received: NaiveDateTime, - actor: PublicActor, + actor: PublicPlayer, click_action: ClickHandler, ) -> Self { Self::new( @@ -210,7 +193,7 @@ impl Notification { id: impl Into, message: impl Into, date_received: NaiveDateTime, - actor: PublicActor, + actor: PublicPlayer, actions: [NotificationAction; 2], ) -> Self { Self::new( @@ -362,7 +345,7 @@ impl Notification { } use chrono::NaiveDateTime; -use gpui2::{px, Styled}; +use gpui::{px, Styled}; #[cfg(feature = "stories")] pub use stories::*; @@ -370,7 +353,7 @@ pub use stories::*; mod stories { use super::*; use crate::{Panel, Story}; - use gpui2::{Div, Render}; + use gpui::{Div, Render}; pub struct NotificationsPanelStory; diff --git a/crates/ui2/src/to_extract/panes.rs b/crates/ui2/src/to_extract/panes.rs index bf0f27d43f..b57b77d5ee 100644 --- a/crates/ui2/src/to_extract/panes.rs +++ b/crates/ui2/src/to_extract/panes.rs @@ -1,4 +1,4 @@ -use gpui2::{hsla, red, AnyElement, ElementId, ExternalPaths, Hsla, Length, Size, View}; +use gpui::{hsla, red, AnyElement, ElementId, ExternalPaths, Hsla, Length, Size, View}; use smallvec::SmallVec; use crate::prelude::*; @@ -113,7 +113,7 @@ impl PaneGroup { .gap_px() .w_full() .h_full() - .bg(cx.theme().colors().editor) + .bg(cx.theme().colors().editor_background) .children(self.groups.into_iter().map(|group| group.render(view, cx))); if self.split_direction == SplitDirection::Horizontal { diff --git a/crates/ui2/src/to_extract/project_panel.rs b/crates/ui2/src/to_extract/project_panel.rs index 76fa50d338..d4f5c72426 100644 --- a/crates/ui2/src/to_extract/project_panel.rs +++ b/crates/ui2/src/to_extract/project_panel.rs @@ -18,9 +18,8 @@ impl ProjectPanel { .id(self.id.clone()) .flex() .flex_col() - .w_full() - .h_full() - .bg(cx.theme().colors().surface) + .size_full() + .bg(cx.theme().colors().surface_background) .child( div() .id("project-panel-contents") @@ -30,15 +29,13 @@ impl ProjectPanel { .overflow_y_scroll() .child( List::new(static_project_panel_single_items()) - .header(ListHeader::new("FILES").toggle(ToggleState::Toggled)) - .empty_message("No files in directory") - .toggle(ToggleState::Toggled), + .header(ListHeader::new("FILES")) + .empty_message("No files in directory"), ) .child( List::new(static_project_panel_project_items()) - .header(ListHeader::new("PROJECT").toggle(ToggleState::Toggled)) - .empty_message("No folders in directory") - .toggle(ToggleState::Toggled), + .header(ListHeader::new("PROJECT")) + .empty_message("No folders in directory"), ), ) .child( @@ -49,7 +46,7 @@ impl ProjectPanel { } } -use gpui2::ElementId; +use gpui::ElementId; #[cfg(feature = "stories")] pub use stories::*; @@ -57,7 +54,7 @@ pub use stories::*; mod stories { use super::*; use crate::{Panel, Story}; - use gpui2::{Div, Render}; + use gpui::{Div, Render}; pub struct ProjectPanelStory; diff --git a/crates/ui2/src/to_extract/recent_projects.rs b/crates/ui2/src/to_extract/recent_projects.rs index d5a9dd1b22..3d4f551490 100644 --- a/crates/ui2/src/to_extract/recent_projects.rs +++ b/crates/ui2/src/to_extract/recent_projects.rs @@ -36,7 +36,7 @@ pub use stories::*; mod stories { use super::*; use crate::Story; - use gpui2::{Div, Render}; + use gpui::{Div, Render}; pub struct RecentProjectsStory; diff --git a/crates/ui2/src/to_extract/status_bar.rs b/crates/ui2/src/to_extract/status_bar.rs index 136472f605..34a5993e69 100644 --- a/crates/ui2/src/to_extract/status_bar.rs +++ b/crates/ui2/src/to_extract/status_bar.rs @@ -93,7 +93,7 @@ impl StatusBar { .items_center() .justify_between() .w_full() - .bg(cx.theme().colors().status_bar) + .bg(cx.theme().colors().status_bar_background) .child(self.left_tools(view, cx)) .child(self.right_tools(view, cx)) } diff --git a/crates/ui2/src/to_extract/tab_bar.rs b/crates/ui2/src/to_extract/tab_bar.rs index bb7fca1153..aff095c639 100644 --- a/crates/ui2/src/to_extract/tab_bar.rs +++ b/crates/ui2/src/to_extract/tab_bar.rs @@ -31,7 +31,7 @@ impl TabBar { .id(self.id.clone()) .w_full() .flex() - .bg(cx.theme().colors().tab_bar) + .bg(cx.theme().colors().tab_bar_background) // Left Side .child( div() @@ -92,7 +92,7 @@ impl TabBar { } } -use gpui2::ElementId; +use gpui::ElementId; #[cfg(feature = "stories")] pub use stories::*; @@ -100,7 +100,7 @@ pub use stories::*; mod stories { use super::*; use crate::Story; - use gpui2::{Div, Render}; + use gpui::{Div, Render}; pub struct TabBarStory; diff --git a/crates/ui2/src/to_extract/terminal.rs b/crates/ui2/src/to_extract/terminal.rs index 051ebf7315..6c36f35152 100644 --- a/crates/ui2/src/to_extract/terminal.rs +++ b/crates/ui2/src/to_extract/terminal.rs @@ -1,4 +1,4 @@ -use gpui2::{relative, rems, Size}; +use gpui::{relative, rems, Size}; use crate::prelude::*; use crate::{Icon, IconButton, Pane, Tab}; @@ -24,7 +24,7 @@ impl Terminal { div() .w_full() .flex() - .bg(cx.theme().colors().surface) + .bg(cx.theme().colors().surface_background) .child( div().px_1().flex().flex_none().gap_2().child( div() @@ -83,7 +83,7 @@ pub use stories::*; mod stories { use super::*; use crate::Story; - use gpui2::{Div, Render}; + use gpui::{Div, Render}; pub struct TerminalStory; impl Render for TerminalStory { diff --git a/crates/ui2/src/to_extract/theme_selector.rs b/crates/ui2/src/to_extract/theme_selector.rs index 5c67f1cd3e..7f911b50bf 100644 --- a/crates/ui2/src/to_extract/theme_selector.rs +++ b/crates/ui2/src/to_extract/theme_selector.rs @@ -39,7 +39,7 @@ pub use stories::*; #[cfg(feature = "stories")] mod stories { - use gpui2::{Div, Render}; + use gpui::{Div, Render}; use crate::Story; diff --git a/crates/ui2/src/to_extract/title_bar.rs b/crates/ui2/src/to_extract/title_bar.rs index 2fa201440a..87d7dd4146 100644 --- a/crates/ui2/src/to_extract/title_bar.rs +++ b/crates/ui2/src/to_extract/title_bar.rs @@ -1,7 +1,7 @@ use std::sync::atomic::AtomicBool; use std::sync::Arc; -use gpui2::{Div, Render, View, VisualContext}; +use gpui::{Div, Render, View, VisualContext}; use crate::prelude::*; use crate::settings::user_settings; diff --git a/crates/ui2/src/to_extract/toolbar.rs b/crates/ui2/src/to_extract/toolbar.rs index 05a5c991d6..81918f34a7 100644 --- a/crates/ui2/src/to_extract/toolbar.rs +++ b/crates/ui2/src/to_extract/toolbar.rs @@ -1,4 +1,4 @@ -use gpui2::AnyElement; +use gpui::AnyElement; use smallvec::SmallVec; use crate::prelude::*; @@ -56,7 +56,7 @@ impl Toolbar { fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { div() - .bg(cx.theme().colors().toolbar) + .bg(cx.theme().colors().toolbar_background) .p_2() .flex() .justify_between() @@ -73,7 +73,7 @@ mod stories { use std::path::PathBuf; use std::str::FromStr; - use gpui2::{Div, Render}; + use gpui::{Div, Render}; use crate::{Breadcrumb, HighlightedText, Icon, IconButton, Story, Symbol}; diff --git a/crates/ui2/src/to_extract/traffic_lights.rs b/crates/ui2/src/to_extract/traffic_lights.rs index 9080276cdd..245ff377f2 100644 --- a/crates/ui2/src/to_extract/traffic_lights.rs +++ b/crates/ui2/src/to_extract/traffic_lights.rs @@ -28,7 +28,7 @@ impl TrafficLight { (true, TrafficLightColor::Red) => system_colors.mac_os_traffic_light_red, (true, TrafficLightColor::Yellow) => system_colors.mac_os_traffic_light_yellow, (true, TrafficLightColor::Green) => system_colors.mac_os_traffic_light_green, - (false, _) => cx.theme().colors().element, + (false, _) => cx.theme().colors().element_background, }; div().w_3().h_3().rounded_full().bg(fill) @@ -77,7 +77,7 @@ pub use stories::*; #[cfg(feature = "stories")] mod stories { - use gpui2::{Div, Render}; + use gpui::{Div, Render}; use crate::Story; diff --git a/crates/ui2/src/to_extract/workspace.rs b/crates/ui2/src/to_extract/workspace.rs index 97570a33e3..d6de8a8288 100644 --- a/crates/ui2/src/to_extract/workspace.rs +++ b/crates/ui2/src/to_extract/workspace.rs @@ -1,14 +1,14 @@ use std::sync::Arc; use chrono::DateTime; -use gpui2::{px, relative, Div, Render, Size, View, VisualContext}; +use gpui::{px, relative, Div, Render, Size, View, VisualContext}; use settings2::Settings; use theme2::ThemeSettings; use crate::prelude::*; use crate::{ - static_livestream, v_stack, AssistantPanel, Button, ChatMessage, ChatPanel, CollabPanel, - EditorPane, Label, LanguageSelector, NotificationsPanel, Pane, PaneGroup, Panel, + static_livestream, v_stack, AssistantPanel, Button, ChatMessage, ChatPanel, Checkbox, + CollabPanel, EditorPane, Label, LanguageSelector, NotificationsPanel, Pane, PaneGroup, Panel, PanelAllowedSides, PanelSide, ProjectPanel, SplitDirection, StatusBar, Terminal, TitleBar, Toast, ToastOrigin, }; @@ -42,6 +42,7 @@ pub struct Workspace { show_terminal: bool, show_debug: bool, show_language_selector: bool, + test_checkbox_selection: Selection, debug: Gpui2UiDebug, } @@ -58,6 +59,7 @@ impl Workspace { show_language_selector: false, show_debug: false, show_notifications_panel: true, + test_checkbox_selection: Selection::Unselected, debug: Gpui2UiDebug::default(), } } @@ -217,6 +219,23 @@ impl Render for Workspace { .text_color(cx.theme().colors().text) .bg(cx.theme().colors().background) .child(self.title_bar.clone()) + .child( + div() + .absolute() + .top_12() + .left_12() + .z_index(99) + .bg(cx.theme().colors().background) + .child( + Checkbox::new("test_checkbox", self.test_checkbox_selection).on_click( + |selection, workspace: &mut Workspace, cx| { + workspace.test_checkbox_selection = selection; + + cx.notify(); + }, + ), + ), + ) .child( div() .flex_1() @@ -354,7 +373,7 @@ pub use stories::*; #[cfg(feature = "stories")] mod stories { use super::*; - use gpui2::VisualContext; + use gpui::VisualContext; pub struct WorkspaceStory { workspace: View, diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index acc41deba8..5af5514da4 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -26,7 +26,7 @@ use std::{ }, }; use ui::v_stack; -use ui::{prelude::*, Icon, IconButton, IconColor, IconElement}; +use ui::{prelude::*, Icon, IconButton, IconColor, IconElement, TextTooltip}; use util::truncate_and_remove_front; #[derive(PartialEq, Clone, Copy, Deserialize, Debug)] @@ -1359,16 +1359,31 @@ impl Pane { cx: &mut ViewContext<'_, Pane>, ) -> impl Component { let label = item.tab_content(Some(detail), cx); - let close_icon = || IconElement::new(Icon::Close).color(IconColor::Muted); + let close_icon = || { + let id = item.id(); - let (tab_bg, tab_hover_bg, tab_active_bg) = match ix == self.active_item_index { + div() + .id(item.id()) + .invisible() + .group_hover("", |style| style.visible()) + .child(IconButton::new("close_tab", Icon::Close).on_click( + move |pane: &mut Self, cx| { + pane.close_item_by_id(id, SaveIntent::Close, cx) + .detach_and_log_err(cx); + }, + )) + }; + + let (text_color, tab_bg, tab_hover_bg, tab_active_bg) = match ix == self.active_item_index { false => ( - cx.theme().colors().tab_inactive, + cx.theme().colors().text_muted, + cx.theme().colors().tab_inactive_background, cx.theme().colors().ghost_element_hover, cx.theme().colors().ghost_element_active, ), true => ( - cx.theme().colors().tab_active, + cx.theme().colors().text, + cx.theme().colors().tab_active_background, cx.theme().colors().element_hover, cx.theme().colors().element_active, ), @@ -1377,7 +1392,12 @@ impl Pane { let close_right = ItemSettings::get_global(cx).close_position.right(); div() + .group("") .id(item.id()) + .cursor_pointer() + .when_some(item.tab_tooltip_text(cx), |div, text| { + div.tooltip(move |_, cx| cx.build_view(|cx| TextTooltip::new(text.clone()))) + }) // .on_drag(move |pane, cx| pane.render_tab(ix, item.boxed_clone(), detail, cx)) // .drag_over::(|d| d.bg(cx.theme().colors().element_drop_target)) // .on_drop(|_view, state: View, cx| { @@ -1397,6 +1417,7 @@ impl Pane { .flex() .items_center() .gap_1p5() + .text_color(text_color) .children(if item.has_conflict(cx) { Some( IconElement::new(Icon::ExclamationTriangle) @@ -1432,7 +1453,7 @@ impl Pane { .id("tab_bar") .w_full() .flex() - .bg(cx.theme().colors().tab_bar) + .bg(cx.theme().colors().tab_bar_background) // Left Side .child( div() @@ -1457,7 +1478,7 @@ impl Pane { ), ) .child( - div().w_0().flex_1().h_full().child( + div().flex_1().h_full().child( div().id("tabs").flex().overflow_x_scroll().children( self.items .iter() @@ -1888,13 +1909,14 @@ impl Render for Pane { fn render(&mut self, cx: &mut ViewContext) -> Self::Element { v_stack() + .size_full() .child(self.render_tab_bar(cx)) - .child(div() /* toolbar */) + .child(div() /* todo!(toolbar) */) .child(if let Some(item) = self.active_item() { - item.to_any().render() + div().flex_1().child(item.to_any()) } else { // todo!() - div().child("Empty Pane").render() + div().child("Empty Pane") }) // enum MouseNavigationHandler {} diff --git a/crates/workspace2/src/pane_group.rs b/crates/workspace2/src/pane_group.rs index 441aef21f5..a9e95b8c29 100644 --- a/crates/workspace2/src/pane_group.rs +++ b/crates/workspace2/src/pane_group.rs @@ -201,7 +201,7 @@ impl Member { // Some(pane) // }; - div().child(pane.clone()).render() + div().size_full().child(pane.clone()).render() // Stack::new() // .with_child(pane_element.contained().with_border(leader_border)) diff --git a/crates/workspace2/src/status_bar.rs b/crates/workspace2/src/status_bar.rs index ca4ebcdb13..fcf6ac3b61 100644 --- a/crates/workspace2/src/status_bar.rs +++ b/crates/workspace2/src/status_bar.rs @@ -44,7 +44,8 @@ impl Render for StatusBar { .items_center() .justify_between() .w_full() - .bg(cx.theme().colors().status_bar) + .h_8() + .bg(cx.theme().colors().status_bar_background) .child(self.render_left_tools(cx)) .child(self.render_right_tools(cx)) } diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 6e4c9e6cfd..7561c903d3 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -2697,7 +2697,7 @@ impl Workspace { fn render_titlebar(&self, cx: &mut ViewContext) -> impl Component { div() - .bg(cx.theme().colors().title_bar) + .bg(cx.theme().colors().title_bar_background) .when( !matches!(cx.window_bounds(), WindowBounds::Fullscreen), |s| s.pl_20(), diff --git a/crates/zed/src/languages/elixir.rs b/crates/zed/src/languages/elixir.rs index df438d89ee..e2c79570bc 100644 --- a/crates/zed/src/languages/elixir.rs +++ b/crates/zed/src/languages/elixir.rs @@ -140,8 +140,8 @@ impl LspAdapter for ElixirLspAdapter { ) -> Result { let version = version.downcast::().unwrap(); let zip_path = container_dir.join(format!("elixir-ls_{}.zip", version.name)); - let version_dir = container_dir.join(format!("elixir-ls_{}", version.name)); - let binary_path = version_dir.join("language_server.sh"); + let folder_path = container_dir.join("elixir-ls"); + let binary_path = folder_path.join("language_server.sh"); if fs::metadata(&binary_path).await.is_err() { let mut response = delegate @@ -160,13 +160,13 @@ impl LspAdapter for ElixirLspAdapter { } futures::io::copy(response.body_mut(), &mut file).await?; - fs::create_dir_all(&version_dir) + fs::create_dir_all(&folder_path) .await - .with_context(|| format!("failed to create directory {}", version_dir.display()))?; + .with_context(|| format!("failed to create directory {}", folder_path.display()))?; let unzip_status = smol::process::Command::new("unzip") .arg(&zip_path) .arg("-d") - .arg(&version_dir) + .arg(&folder_path) .output() .await? .status; @@ -174,7 +174,7 @@ impl LspAdapter for ElixirLspAdapter { Err(anyhow!("failed to unzip elixir-ls archive"))?; } - remove_matching(&container_dir, |entry| entry != version_dir).await; + remove_matching(&container_dir, |entry| entry != folder_path).await; } Ok(LanguageServerBinary { @@ -285,20 +285,16 @@ impl LspAdapter for ElixirLspAdapter { async fn get_cached_server_binary_elixir_ls( container_dir: PathBuf, ) -> Option { - (|| async move { - let mut last = None; - let mut entries = fs::read_dir(&container_dir).await?; - while let Some(entry) = entries.next().await { - last = Some(entry?.path()); - } - last.map(|path| LanguageServerBinary { - path, + let server_path = container_dir.join("elixir-ls/language_server.sh"); + if server_path.exists() { + Some(LanguageServerBinary { + path: server_path, arguments: vec![], }) - .ok_or_else(|| anyhow!("no cached binary")) - })() - .await - .log_err() + } else { + log::error!("missing executable in directory {:?}", server_path); + None + } } pub struct NextLspAdapter; diff --git a/crates/zed2/src/languages/elixir.rs b/crates/zed2/src/languages/elixir.rs index bd38377c99..90352c78b4 100644 --- a/crates/zed2/src/languages/elixir.rs +++ b/crates/zed2/src/languages/elixir.rs @@ -140,8 +140,8 @@ impl LspAdapter for ElixirLspAdapter { ) -> Result { let version = version.downcast::().unwrap(); let zip_path = container_dir.join(format!("elixir-ls_{}.zip", version.name)); - let version_dir = container_dir.join(format!("elixir-ls_{}", version.name)); - let binary_path = version_dir.join("language_server.sh"); + let folder_path = container_dir.join("elixir-ls"); + let binary_path = folder_path.join("language_server.sh"); if fs::metadata(&binary_path).await.is_err() { let mut response = delegate @@ -160,13 +160,13 @@ impl LspAdapter for ElixirLspAdapter { } futures::io::copy(response.body_mut(), &mut file).await?; - fs::create_dir_all(&version_dir) + fs::create_dir_all(&folder_path) .await - .with_context(|| format!("failed to create directory {}", version_dir.display()))?; + .with_context(|| format!("failed to create directory {}", folder_path.display()))?; let unzip_status = smol::process::Command::new("unzip") .arg(&zip_path) .arg("-d") - .arg(&version_dir) + .arg(&folder_path) .output() .await? .status; @@ -174,7 +174,7 @@ impl LspAdapter for ElixirLspAdapter { Err(anyhow!("failed to unzip elixir-ls archive"))?; } - remove_matching(&container_dir, |entry| entry != version_dir).await; + remove_matching(&container_dir, |entry| entry != folder_path).await; } Ok(LanguageServerBinary { @@ -285,20 +285,16 @@ impl LspAdapter for ElixirLspAdapter { async fn get_cached_server_binary_elixir_ls( container_dir: PathBuf, ) -> Option { - (|| async move { - let mut last = None; - let mut entries = fs::read_dir(&container_dir).await?; - while let Some(entry) = entries.next().await { - last = Some(entry?.path()); - } - last.map(|path| LanguageServerBinary { - path, + let server_path = container_dir.join("elixir-ls/language_server.sh"); + if server_path.exists() { + Some(LanguageServerBinary { + path: server_path, arguments: vec![], }) - .ok_or_else(|| anyhow!("no cached binary")) - })() - .await - .log_err() + } else { + log::error!("missing executable in directory {:?}", server_path); + None + } } pub struct NextLspAdapter; diff --git a/crates/zed2/src/languages/language_plugin.rs b/crates/zed2/src/languages/language_plugin.rs index a160cca228..968cc819fd 100644 --- a/crates/zed2/src/languages/language_plugin.rs +++ b/crates/zed2/src/languages/language_plugin.rs @@ -2,7 +2,7 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use collections::HashMap; use futures::lock::Mutex; -use gpui2::executor::Background; +use gpui::executor::Background; use language2::{LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp2::LanguageServerBinary; use plugin_runtime::{Plugin, PluginBinary, PluginBuilder, WasiFn}; diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index 79ba132e4f..52eb512eb4 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -208,7 +208,6 @@ fn main() { if stdout_is_a_pty() { cx.activate(true); let urls = collect_url_args(); - dbg!(&urls); if !urls.is_empty() { listener.open_urls(urls) } diff --git a/test.rs b/test.rs index 6553e633c8..4a7b9faa8f 100644 --- a/test.rs +++ b/test.rs @@ -410,7 +410,7 @@ mod components { themes::rose_pine, }; use gpui::{platform::MouseButton, ViewContext}; - use gpui2_macros::Element; + use gpui_macros::Element; use std::{marker::PhantomData, rc::Rc}; struct ButtonHandlers { click: Option)>>, @@ -535,7 +535,7 @@ mod element { platform::{MouseButton, MouseButtonEvent}, EngineLayout, EventContext, RenderContext, ViewContext, }; - use gpui2_macros::tailwind_lengths; + use gpui_macros::tailwind_lengths; use std::{ any::{Any, TypeId}, cell::Cell, @@ -4572,7 +4572,7 @@ mod frame { }; use anyhow::{anyhow, Result}; use gpui::LayoutNodeId; - use gpui2_macros::IntoElement; + use gpui_macros::IntoElement; #[element_crate = "crate"] pub struct Frame { style: OptionalStyle,