Merge branch 'main' into add-collab-tests

This commit is contained in:
Mikayla 2023-11-08 09:41:57 -08:00
commit 3050c440f4
No known key found for this signature in database
106 changed files with 1503 additions and 950 deletions

3
Cargo.lock generated
View File

@ -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",

View File

@ -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" }

1
assets/icons/dash.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-minus"><path d="M5 12h14"/></svg>

After

Width:  |  Height:  |  Size: 229 B

View File

@ -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

View File

@ -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<MultiBuffer>,
path_states: Vec<PathState>,
paths_to_update: BTreeSet<(ProjectPath, LanguageServerId)>,
paths_to_update: HashMap<LanguageServerId, HashSet<ProjectPath>>,
current_diagnostics: HashMap<LanguageServerId, HashSet<ProjectPath>>,
include_warnings: bool,
_subscriptions: Vec<Subscription>,
}
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::<Vec<_>>(),
"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::<Vec<_>>())
).collect::<HashMap<_, _>>(),
"current_diagnostics": self.current_diagnostics.iter().map(|(server_id, paths)|
(server_id.0, paths.into_iter().map(|path| path.path.to_string_lossy()).collect::<Vec<_>>())
).collect::<HashMap<_, _>>(),
"paths_states": self.path_states.iter().map(|state|
json!({
"path": state.path.path.to_string_lossy(),
@ -149,25 +156,30 @@ impl ProjectDiagnosticsEditor {
workspace: WeakViewHandle<Workspace>,
cx: &mut ViewContext<Self>,
) -> 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::<usize>(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::<ProjectDiagnosticsSettings>(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>) {
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<Self>,
) {
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<LanguageServerId, HashSet<ProjectPath>> = 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>) {
self.summary = self.project.read(cx).diagnostic_summary(cx);
cx.emit(Event::TitleChanged);
}
}
impl Item for ProjectDiagnosticsEditor {

View File

@ -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>,
// ) {
// 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>,
) {
self.soft_wrap_mode_override = Some(mode);
cx.notify();
}
// pub fn set_wrap_width(&self, width: Option<f32>, 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<Pixels>, 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<Self>) {
// if self.soft_wrap_mode_override.is_some() {
@ -9321,11 +9322,14 @@ impl EventEmitter for Editor {
}
impl Render for Editor {
type Element = Div<Self>;
type Element = EditorElement;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
// todo!()
div()
EditorElement::new(EditorStyle {
text: cx.text_style(),
line_height_scalar: 1.,
theme_id: 0,
})
}
}

View File

@ -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<Editor>) -> f32 {
// let style = &self.style;
fn column_pixels(&self, column: usize, cx: &ViewContext<Editor>) -> 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<Editor>) -> 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<Editor>) -> 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<Editor> for EditorElement {
element_state: &mut Self::ElementState,
cx: &mut gpui::ViewContext<Editor>,
) -> 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<Editor> for EditorElement {
fn paint(
&mut self,
bounds: Bounds<gpui::Pixels>,
view_state: &mut Editor,
editor: &mut Editor,
element_state: &mut Self::ElementState,
cx: &mut gpui::ViewContext<Editor>,
) {
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::<SmallVec<[_; 2]>>();
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();
}
}

View File

@ -578,18 +578,24 @@ impl Item for Editor {
fn tab_content<T: 'static>(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<T> {
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<Self>) {
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<Self>) {

View File

@ -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<Self>) {
// 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<Self>) {
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,

View File

@ -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<Editor>,
) -> 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 { .. }) {

View File

@ -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")

View File

@ -165,6 +165,7 @@ pub struct AppContext {
flushing_effects: bool,
pending_updates: usize,
pub(crate) active_drag: Option<AnyDrag>,
pub(crate) active_tooltip: Option<AnyTooltip>,
pub(crate) next_frame_callbacks: HashMap<DisplayId, Vec<FrameCallback>>,
pub(crate) frame_consumers: HashMap<DisplayId, Task<()>>,
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<Pixels>,
}
#[derive(Clone)]
pub(crate) struct AnyTooltip {
pub view: AnyView,
pub cursor_offset: Point<Pixels>,
}

View File

@ -212,6 +212,19 @@ pub trait Component<V> {
{
self.map(|this| if condition { then(this) } else { this })
}
fn when_some<T>(self, option: Option<T>, then: impl FnOnce(Self, T) -> Self) -> Self
where
Self: Sized,
{
self.map(|this| {
if let Some(value) = option {
then(this, value)
} else {
this
}
})
}
}
impl<V> Component<V> for AnyElement<V> {

View File

@ -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<V>,
) {
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);

View File

@ -21,7 +21,7 @@ pub fn point<T: Clone + Debug + Default>(x: T, y: T) -> Point<T> {
}
impl<T: Clone + Debug + Default> Point<T> {
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<Pixels> for u32 {
}
}
impl From<u32> for Pixels {
fn from(pixels: u32) -> Self {
Pixels(pixels as f32)
}
}
impl From<Pixels> for usize {
fn from(pixels: Pixels) -> Self {
pixels.0 as usize

View File

@ -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<Pixels> = Point::new(px(10.0), px(8.0));
pub trait StatelessInteractive<V: 'static>: Element<V> {
fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V>;
@ -333,6 +336,37 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
}));
self
}
fn on_hover(mut self, listener: impl 'static + Fn(&mut V, bool, &mut ViewContext<V>)) -> 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<W>(
mut self,
build_tooltip: impl Fn(&mut V, &mut ViewContext<V>) -> View<W> + '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<V: 'static>: 'static {
@ -568,6 +602,77 @@ pub trait ElementInteraction<V: 'static>: '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<V> {
active_style: StyleRefinement,
group_active_style: Option<GroupStyle>,
drag_listener: Option<DragListener<V>>,
hover_listener: Option<HoverListener<V>>,
tooltip_builder: Option<TooltipBuilder<V>>,
}
impl<V: 'static> ElementInteraction<V> for StatefulInteraction<V> {
@ -666,6 +773,8 @@ impl<V> From<ElementId> for StatefulInteraction<V> {
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<V> StatelessInteraction<V> {
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<Mutex<ActiveState>>,
hover_state: Arc<Mutex<bool>>,
pending_mouse_down: Arc<Mutex<Option<MouseDownEvent>>>,
scroll_offset: Option<Arc<Mutex<Point<Pixels>>>>,
active_tooltip: Arc<Mutex<Option<ActiveTooltip>>>,
}
struct ActiveTooltip {
#[allow(unused)] // used to drop the task
waiting: Option<Task<()>>,
tooltip: Option<AnyTooltip>,
}
impl InteractiveElementState {
@ -1097,6 +1216,10 @@ pub type ClickListener<V> = Box<dyn Fn(&mut V, &ClickEvent, &mut ViewContext<V>)
pub(crate) type DragListener<V> =
Box<dyn Fn(&mut V, Point<Pixels>, &mut ViewContext<V>) -> AnyDrag + 'static>;
pub(crate) type HoverListener<V> = Box<dyn Fn(&mut V, bool, &mut ViewContext<V>) + 'static>;
pub(crate) type TooltipBuilder<V> = Arc<dyn Fn(&mut V, &mut ViewContext<V>) -> AnyView + 'static>;
pub type KeyListener<V> = Box<
dyn Fn(
&mut V,

View File

@ -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,

View File

@ -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<CursorStyle>,
pub z_index: Option<u32>,
}
@ -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,
}
}

View File

@ -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

View File

@ -14,7 +14,7 @@ impl<EmitterKey, Callback> Clone for SubscriberSet<EmitterKey, Callback> {
}
struct SubscriberSetState<EmitterKey, Callback> {
subscribers: BTreeMap<EmitterKey, BTreeMap<usize, Callback>>,
subscribers: BTreeMap<EmitterKey, Option<BTreeMap<usize, Callback>>>,
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<Item = Callback> {
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<F>(&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));
}
}
}

View File

@ -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<RwLock<SlotMap<FocusId, AtomicUsize>>>,
default_prevented: bool,
mouse_position: Point<Pixels>,
requested_cursor_style: Option<CursorStyle>,
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<R>(&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;
}

View File

@ -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()
}
}

View File

@ -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::<Vec<_>>();
@ -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<gpui2::#length_type>) -> Self where Self: std::marker::Sized {
fn #method_name(mut self, length: impl std::clone::Clone + Into<gpui::#length_type>) -> Self where Self: std::marker::Sized {
let style = self.style();
#(#field_assignments)*
self

View File

@ -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

View File

@ -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"

View File

@ -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();
}

View File

@ -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;

View File

@ -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"] }

View File

@ -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)]

View File

@ -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()

View File

@ -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<Self, StatefulInteraction<Self>, FocusEnabled<Self>>;
fn render(&mut self, cx: &mut gpui2::ViewContext<Self>) -> Self::Element {
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
let theme = cx.theme();
let color_1 = theme.styles.git.created;
let color_2 = theme.styles.git.modified;

View File

@ -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::*;

View File

@ -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<Self, StatefulInteraction<Self>>;
fn render(&mut self, cx: &mut gpui2::ViewContext<Self>) -> Self::Element {
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
let theme = cx.theme();
let color_1 = theme.styles.git.created;
let color_2 = theme.styles.git.modified;

View File

@ -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<Self>;
fn render(&mut self, cx: &mut gpui2::ViewContext<Self>) -> Self::Element {
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> 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. ",

View File

@ -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;

View File

@ -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(),

View File

@ -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 {

View File

@ -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

View File

@ -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)]

View File

@ -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(),
}
}
}

View File

@ -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,

View File

View File

@ -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" }

View File

@ -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.

View File

@ -40,12 +40,12 @@ impl<V: 'static> TodoList<V> {
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<V: 'static> TodoList<V> {
// ...
@ -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<V: 'static> TodoList<V> {
Now we have access to the complete set of colors defined in the theme.
~~~rust
use gpui2::hsla
use gpui::hsla
impl<V: 'static> TodoList<V> {
// ...
@ -113,7 +113,7 @@ impl<V: 'static> TodoList<V> {
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<V: 'static> TodoList<V> {
// ...

View File

@ -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::*;

View File

@ -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;

View File

@ -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<V: 'static> {
Button(Button<V>),
IconButton(IconButton<V>),
}
impl<V: 'static> From<Button<V>> for ButtonOrIconButton<V> {
fn from(value: Button<V>) -> Self {
Self::Button(value)
}
}
impl<V: 'static> From<IconButton<V>> for ButtonOrIconButton<V> {
fn from(value: IconButton<V>) -> 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<S> = Arc<dyn Fn(&mut S, &mut ViewContext<S>) + Send + Sync>;
pub type ClickHandler<V> = Arc<dyn Fn(&mut V, &mut ViewContext<V>) + Send + Sync>;
struct ButtonHandlers<V: 'static> {
click: Option<ClickHandler<V>>,
@ -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;

View File

@ -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<V> = Arc<dyn Fn(Selection, &mut V, &mut ViewContext<V>) + 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<V: 'static> {
id: ElementId,
checked: Selection,
disabled: bool,
on_click: Option<CheckHandler<V>>,
}
impl<V: 'static> Checkbox<V> {
pub fn new(id: impl Into<ElementId>, 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<V>) + Send + Sync,
) -> Self {
self.on_click = Some(Arc::new(handler));
self
}
pub fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
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<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
Story::container(cx)
.child(Story::title_for::<_, Checkbox<Self>>(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),
),
)
}
}
}

View File

@ -8,7 +8,7 @@ pub enum ContextMenuItem {
}
impl ContextMenuItem {
fn to_list_item<V: 'static>(self) -> ListItem<V> {
fn to_list_item<V: 'static>(self) -> ListItem {
match self {
ContextMenuItem::Header(label) => ListSubHeader::new(label).into(),
ContextMenuItem::Entry(label) => {
@ -46,18 +46,15 @@ impl ContextMenu {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
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::<V>)
.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;

View File

@ -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;

View File

@ -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;

View File

@ -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<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
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;

View File

@ -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<V: 'static> IconButton<V> {
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,
),

View File

@ -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)

View File

@ -59,12 +59,12 @@ impl Input {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
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;

View File

@ -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;

View File

@ -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;

View File

@ -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<Icon>,
meta: Option<ListHeaderMeta>,
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<V: 'static>(&self) -> Div<V> {
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<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
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<V: 'static> {
pub enum ListItem {
Entry(ListEntry),
Details(ListDetailsEntry<V>),
Separator(ListSeparator),
Header(ListSubHeader),
}
impl<V: 'static> From<ListEntry> for ListItem<V> {
impl From<ListEntry> for ListItem {
fn from(entry: ListEntry) -> Self {
Self::Entry(entry)
}
}
impl<V: 'static> From<ListDetailsEntry<V>> for ListItem<V> {
fn from(entry: ListDetailsEntry<V>) -> Self {
Self::Details(entry)
}
}
impl<V: 'static> From<ListSeparator> for ListItem<V> {
impl From<ListSeparator> for ListItem {
fn from(entry: ListSeparator) -> Self {
Self::Separator(entry)
}
}
impl<V: 'static> From<ListSubHeader> for ListItem<V> {
impl From<ListSubHeader> for ListItem {
fn from(entry: ListSubHeader) -> Self {
Self::Header(entry)
}
}
impl<V: 'static> ListItem<V> {
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
impl ListItem {
fn render<V: 'static>(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
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<V: 'static> ListItem<V> {
#[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<LeftContent>,
variant: ListItemVariant,
size: ListEntrySize,
state: InteractionState,
toggle: Option<ToggleState>,
left_slot: Option<GraphicSlot>,
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<SharedString>) -> 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<V: 'static>(
&mut self,
cx: &mut ViewContext<V>,
) -> Option<impl Component<V>> {
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<V: 'static>(mut self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
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<V: 'static> {
click: Option<ClickHandler<V>>,
}
impl<V: 'static> Default for ListDetailsEntryHandlers<V> {
fn default() -> Self {
Self { click: None }
}
}
#[derive(Component)]
pub struct ListDetailsEntry<V: 'static> {
label: SharedString,
meta: Option<SharedString>,
left_content: Option<LeftContent>,
handlers: ListDetailsEntryHandlers<V>,
actions: Option<Vec<Button<V>>>,
// TODO: make this more generic instead of
// specifically for notifications
seen: bool,
}
impl<V: 'static> ListDetailsEntry<V> {
pub fn new(label: impl Into<SharedString>) -> 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<SharedString>) -> 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<V>) -> Self {
self.handlers.click = Some(handler);
self
}
pub fn actions(mut self, actions: Vec<Button<V>>) -> Self {
self.actions = Some(actions);
self
}
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
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<V: 'static> {
items: Vec<ListItem<V>>,
pub struct List {
items: Vec<ListItem>,
/// Message to display when the list is empty
/// Defaults to "No items"
empty_message: SharedString,
header: Option<ListHeader>,
toggleable: Toggleable,
toggle: Toggle,
}
impl<V: 'static> List<V> {
pub fn new(items: Vec<ListItem<V>>) -> Self {
impl List {
pub fn new(items: Vec<ListItem>) -> Self {
Self {
items,
empty_message: "No items".into(),
header: None,
toggleable: Toggleable::default(),
toggle: Toggle::NotToggleable,
}
}
@ -591,19 +380,16 @@ impl<V: 'static> List<V> {
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<V>) -> impl Component<V> {
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<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
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<V: 'static> List<V> {
v_stack()
.w_full()
.py_1()
.children(self.header.map(|header| header.toggleable(self.toggleable)))
.children(self.header.map(|header| header))
.child(list_content)
}
}

View File

@ -1,4 +1,4 @@
use gpui2::AnyElement;
use gpui::AnyElement;
use smallvec::SmallVec;
use crate::{h_stack, prelude::*, v_stack, Button, Icon, IconButton, Label};

View File

@ -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()))
}
}

View File

@ -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};

View File

@ -1,4 +1,4 @@
use gpui2::{AbsoluteLength, AnyElement};
use gpui::{AbsoluteLength, AnyElement};
use smallvec::SmallVec;
use crate::prelude::*;
@ -107,7 +107,7 @@ impl<V: 'static> Panel<V> {
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;

View File

@ -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<SharedString>, avatar: impl Into<SharedString>) -> 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]

View File

@ -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),
}

View File

@ -1,4 +1,4 @@
use gpui2::{div, Div};
use gpui::{div, Div};
use crate::prelude::*;

View File

@ -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,
),

View File

@ -1,4 +1,4 @@
use gpui2::AnyElement;
use gpui::AnyElement;
use smallvec::SmallVec;
use crate::prelude::*;
@ -54,7 +54,7 @@ impl<V: 'static> Toast<V> {
.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};

View File

@ -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<bool> for Toggle {
fn from(toggled: bool) -> Self {
Toggle::Toggled(toggled)
}
}
pub fn disclosure_control<V: 'static>(toggle: Toggle) -> impl Component<V> {
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),
),
}
}

View File

@ -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<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> 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())
}
}

View File

@ -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<SharedString>, avatar: impl Into<SharedString>) -> 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<ToggleState> 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<Toggleable> for ToggleState {
fn from(toggleable: Toggleable) -> Self {
match toggleable {
Toggleable::Toggleable(state) => state,
Toggleable::NotToggleable => ToggleState::NotToggled,
}
}
}
impl From<bool> for ToggleState {
fn from(toggled: bool) -> Self {
if toggled {
ToggleState::Toggled
} else {
ToggleState::NotToggled
Self::Unselected | Self::Indeterminate => Self::Selected,
Self::Selected => Self::Unselected,
}
}
}

View File

@ -1,6 +1,6 @@
use std::ops::Deref;
use gpui2::{rems, AbsoluteLength, AppContext, WindowContext};
use gpui::{rems, AbsoluteLength, AppContext, WindowContext};
use crate::prelude::*;

View File

@ -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<Tab> {
@ -345,7 +345,7 @@ pub fn static_new_notification_items_2<V: 'static>() -> Vec<Notification<V>> {
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<V: 'static>() -> Vec<Notification<V>> {
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<V: 'static>() -> Vec<Notification<V>> {
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<V: 'static>() -> Vec<Notification<V>> {
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<V: 'static>() -> Vec<Notification<V>> {
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<V: 'static>() -> Vec<Notification<V>> {
]
}
pub fn static_new_notification_items<V: 'static>() -> Vec<ListItem<V>> {
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<V: 'static>() -> Vec<ListItem<V>> {
pub fn static_project_panel_project_items() -> Vec<ListItem> {
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<V: 'static>() -> Vec<ListItem<V>> {
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<V: 'static>() -> Vec<ListItem<V>> {
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<V: 'static>() -> Vec<ListItem<V>> {
.collect()
}
pub fn static_project_panel_single_items<V: 'static>() -> Vec<ListItem<V>> {
pub fn static_project_panel_single_items() -> Vec<ListItem> {
vec![
ListEntry::new(Label::new("todo.md"))
.left_icon(Icon::FileDoc.into())
@ -699,7 +622,7 @@ pub fn static_project_panel_single_items<V: 'static>() -> Vec<ListItem<V>> {
.collect()
}
pub fn static_collab_panel_current_call<V: 'static>() -> Vec<ListItem<V>> {
pub fn static_collab_panel_current_call() -> Vec<ListItem> {
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<V: 'static>() -> Vec<ListItem<V>> {
.collect()
}
pub fn static_collab_panel_channels<V: 'static>() -> Vec<ListItem<V>> {
pub fn static_collab_panel_channels() -> Vec<ListItem> {
vec![
ListEntry::new(Label::new("zed"))
.left_icon(Icon::Hash.into())

View File

@ -1,4 +1,4 @@
use gpui2::Div;
use gpui::Div;
use crate::prelude::*;

View File

@ -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 {

View File

@ -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<HighlightedText>);
@ -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;

View File

@ -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;

View File

@ -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<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {
h_stack().bg(cx.theme().colors().toolbar).p_2().child(
h_stack().child(Input::new("Search")).child(
IconButton::<Self>::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::<Self>::new("replace", Icon::Replace)
.when(self.is_replace_open, |this| this.color(IconColor::Accent))
.on_click(|buffer_search, cx| {
buffer_search.toggle_replace(cx);
}),
),
)
}
}

View File

@ -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};

View File

@ -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;

View File

@ -27,7 +27,7 @@ pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use gpui2::{Div, Render};
use gpui::{Div, Render};
use crate::Story;

View File

@ -25,7 +25,7 @@ pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use gpui2::{Div, Render};
use gpui::{Div, Render};
use crate::Story;

View File

@ -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::{

View File

@ -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;

View File

@ -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;

View File

@ -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<V: 'static> {
Button(Button<V>),
IconButton(IconButton<V>),
}
impl<V: 'static> From<Button<V>> for ButtonOrIconButton<V> {
fn from(value: Button<V>) -> Self {
Self::Button(value)
}
}
impl<V: 'static> From<IconButton<V>> for ButtonOrIconButton<V> {
fn from(value: IconButton<V>) -> Self {
Self::IconButton(value)
}
}
pub struct NotificationAction<V: 'static> {
button: ButtonOrIconButton<V>,
tooltip: SharedString,
@ -102,7 +85,7 @@ impl<V: 'static> NotificationAction<V> {
}
pub enum ActorOrIcon {
Actor(PublicActor),
Actor(PublicPlayer),
Icon(Icon),
}
@ -171,7 +154,7 @@ impl<V> Notification<V> {
id: impl Into<ElementId>,
message: impl Into<SharedString>,
date_received: NaiveDateTime,
actor: PublicActor,
actor: PublicPlayer,
click_action: ClickHandler<V>,
) -> Self {
Self::new(
@ -210,7 +193,7 @@ impl<V> Notification<V> {
id: impl Into<ElementId>,
message: impl Into<SharedString>,
date_received: NaiveDateTime,
actor: PublicActor,
actor: PublicPlayer,
actions: [NotificationAction<V>; 2],
) -> Self {
Self::new(
@ -362,7 +345,7 @@ impl<V> Notification<V> {
}
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;

View File

@ -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<V: 'static> PaneGroup<V> {
.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 {

View File

@ -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;

View File

@ -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;

View File

@ -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))
}

View File

@ -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;

View File

@ -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 {

View File

@ -39,7 +39,7 @@ pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use gpui2::{Div, Render};
use gpui::{Div, Render};
use crate::Story;

View File

@ -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;

View File

@ -1,4 +1,4 @@
use gpui2::AnyElement;
use gpui::AnyElement;
use smallvec::SmallVec;
use crate::prelude::*;
@ -56,7 +56,7 @@ impl<V: 'static> Toolbar<V> {
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
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};

View File

@ -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;

View File

@ -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<Workspace>,

View File

@ -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<Self> {
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::<DraggedTab>(|d| d.bg(cx.theme().colors().element_drop_target))
// .on_drop(|_view, state: View<DraggedTab>, 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>) -> 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 {}

View File

@ -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))

View File

@ -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))
}

Some files were not shown because too many files have changed in this diff Show More