Merge pull request #921 from zed-industries/new-status-bar-design
Style the status bar according to the latest design
This commit is contained in:
@@ -35,6 +35,7 @@ const CONTEXT_LINE_COUNT: u32 = 1;
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_action(ProjectDiagnosticsEditor::deploy);
|
||||
items::init(cx);
|
||||
}
|
||||
|
||||
type Event = editor::Event;
|
||||
|
||||
+182
-31
@@ -1,30 +1,38 @@
|
||||
use crate::render_summary;
|
||||
use editor::{Editor, GoToNextDiagnostic};
|
||||
use gpui::{
|
||||
elements::*, platform::CursorStyle, serde_json, Entity, ModelHandle, RenderContext, View,
|
||||
ViewContext,
|
||||
elements::*, platform::CursorStyle, serde_json, Entity, ModelHandle, MutableAppContext,
|
||||
RenderContext, Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use language::Diagnostic;
|
||||
use project::Project;
|
||||
use settings::Settings;
|
||||
use workspace::StatusItemView;
|
||||
|
||||
pub struct DiagnosticSummary {
|
||||
pub struct DiagnosticIndicator {
|
||||
summary: project::DiagnosticSummary,
|
||||
in_progress: bool,
|
||||
active_editor: Option<WeakViewHandle<Editor>>,
|
||||
current_diagnostic: Option<Diagnostic>,
|
||||
check_in_progress: bool,
|
||||
_observe_active_editor: Option<Subscription>,
|
||||
}
|
||||
|
||||
impl DiagnosticSummary {
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_action(DiagnosticIndicator::go_to_next_diagnostic);
|
||||
}
|
||||
|
||||
impl DiagnosticIndicator {
|
||||
pub fn new(project: &ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
|
||||
cx.subscribe(project, |this, project, event, cx| match event {
|
||||
project::Event::DiskBasedDiagnosticsUpdated => {
|
||||
cx.notify();
|
||||
}
|
||||
project::Event::DiskBasedDiagnosticsStarted => {
|
||||
this.in_progress = true;
|
||||
this.check_in_progress = true;
|
||||
cx.notify();
|
||||
}
|
||||
project::Event::DiskBasedDiagnosticsFinished => {
|
||||
this.summary = project.read(cx).diagnostic_summary(cx);
|
||||
this.in_progress = false;
|
||||
this.check_in_progress = false;
|
||||
cx.notify();
|
||||
}
|
||||
_ => {}
|
||||
@@ -32,41 +40,174 @@ impl DiagnosticSummary {
|
||||
.detach();
|
||||
Self {
|
||||
summary: project.read(cx).diagnostic_summary(cx),
|
||||
in_progress: project.read(cx).is_running_disk_based_diagnostics(),
|
||||
check_in_progress: project.read(cx).is_running_disk_based_diagnostics(),
|
||||
active_editor: None,
|
||||
current_diagnostic: None,
|
||||
_observe_active_editor: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn go_to_next_diagnostic(&mut self, _: &GoToNextDiagnostic, cx: &mut ViewContext<Self>) {
|
||||
if let Some(editor) = self.active_editor.as_ref().and_then(|e| e.upgrade(cx)) {
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.go_to_diagnostic(editor::Direction::Next, cx);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) {
|
||||
let editor = editor.read(cx);
|
||||
let buffer = editor.buffer().read(cx);
|
||||
let cursor_position = editor
|
||||
.newest_selection_with_snapshot::<usize>(&buffer.read(cx))
|
||||
.head();
|
||||
let new_diagnostic = buffer
|
||||
.read(cx)
|
||||
.diagnostics_in_range::<_, usize>(cursor_position..cursor_position, false)
|
||||
.filter(|entry| !entry.range.is_empty())
|
||||
.min_by_key(|entry| (entry.diagnostic.severity, entry.range.len()))
|
||||
.map(|entry| entry.diagnostic);
|
||||
if new_diagnostic != self.current_diagnostic {
|
||||
self.current_diagnostic = new_diagnostic;
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Entity for DiagnosticSummary {
|
||||
impl Entity for DiagnosticIndicator {
|
||||
type Event = ();
|
||||
}
|
||||
|
||||
impl View for DiagnosticSummary {
|
||||
impl View for DiagnosticIndicator {
|
||||
fn ui_name() -> &'static str {
|
||||
"DiagnosticSummary"
|
||||
"DiagnosticIndicator"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
enum Tag {}
|
||||
enum Summary {}
|
||||
enum Message {}
|
||||
|
||||
let in_progress = self.in_progress;
|
||||
MouseEventHandler::new::<Tag, _, _>(0, cx, |_, cx| {
|
||||
let theme = &cx.global::<Settings>().theme.project_diagnostics;
|
||||
if in_progress {
|
||||
let in_progress = self.check_in_progress;
|
||||
let mut element = Flex::row().with_child(
|
||||
MouseEventHandler::new::<Summary, _, _>(0, cx, |state, cx| {
|
||||
let style = &cx
|
||||
.global::<Settings>()
|
||||
.theme
|
||||
.workspace
|
||||
.status_bar
|
||||
.diagnostic_summary;
|
||||
let style = if state.hovered {
|
||||
style.hover()
|
||||
} else {
|
||||
&style.default
|
||||
};
|
||||
|
||||
let mut summary_row = Flex::row();
|
||||
if self.summary.error_count > 0 {
|
||||
summary_row.add_children([
|
||||
Svg::new("icons/error-solid-14.svg")
|
||||
.with_color(style.icon_color_error)
|
||||
.constrained()
|
||||
.with_width(style.icon_width)
|
||||
.aligned()
|
||||
.contained()
|
||||
.with_margin_right(style.icon_spacing)
|
||||
.named("error-icon"),
|
||||
Label::new(self.summary.error_count.to_string(), style.text.clone())
|
||||
.aligned()
|
||||
.boxed(),
|
||||
]);
|
||||
}
|
||||
|
||||
if self.summary.warning_count > 0 {
|
||||
summary_row.add_children([
|
||||
Svg::new("icons/warning-solid-14.svg")
|
||||
.with_color(style.icon_color_warning)
|
||||
.constrained()
|
||||
.with_width(style.icon_width)
|
||||
.aligned()
|
||||
.contained()
|
||||
.with_margin_right(style.icon_spacing)
|
||||
.with_margin_left(if self.summary.error_count > 0 {
|
||||
style.summary_spacing
|
||||
} else {
|
||||
0.
|
||||
})
|
||||
.named("warning-icon"),
|
||||
Label::new(self.summary.warning_count.to_string(), style.text.clone())
|
||||
.aligned()
|
||||
.boxed(),
|
||||
]);
|
||||
}
|
||||
|
||||
if self.summary.error_count == 0 && self.summary.warning_count == 0 {
|
||||
summary_row.add_child(
|
||||
Svg::new("icons/no-error-solid-14.svg")
|
||||
.with_color(style.icon_color_ok)
|
||||
.constrained()
|
||||
.with_width(style.icon_width)
|
||||
.aligned()
|
||||
.named("ok-icon"),
|
||||
);
|
||||
}
|
||||
|
||||
summary_row
|
||||
.constrained()
|
||||
.with_height(style.height)
|
||||
.contained()
|
||||
.with_style(if self.summary.error_count > 0 {
|
||||
style.container_error
|
||||
} else if self.summary.warning_count > 0 {
|
||||
style.container_warning
|
||||
} else {
|
||||
style.container_ok
|
||||
})
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(|cx| cx.dispatch_action(crate::Deploy))
|
||||
.aligned()
|
||||
.boxed(),
|
||||
);
|
||||
|
||||
let style = &cx.global::<Settings>().theme.workspace.status_bar;
|
||||
let item_spacing = style.item_spacing;
|
||||
|
||||
if in_progress {
|
||||
element.add_child(
|
||||
Label::new(
|
||||
"Checking... ".to_string(),
|
||||
theme.status_bar_item.text.clone(),
|
||||
"checking…".into(),
|
||||
style.diagnostic_message.default.text.clone(),
|
||||
)
|
||||
.aligned()
|
||||
.contained()
|
||||
.with_style(theme.status_bar_item.container)
|
||||
.boxed()
|
||||
} else {
|
||||
render_summary(&self.summary, &theme.status_bar_item.text, &theme)
|
||||
}
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(|cx| cx.dispatch_action(crate::Deploy))
|
||||
.boxed()
|
||||
.with_margin_left(item_spacing)
|
||||
.boxed(),
|
||||
);
|
||||
} else if let Some(diagnostic) = &self.current_diagnostic {
|
||||
let message_style = style.diagnostic_message.clone();
|
||||
element.add_child(
|
||||
MouseEventHandler::new::<Message, _, _>(1, cx, |state, _| {
|
||||
Label::new(
|
||||
diagnostic.message.split('\n').next().unwrap().to_string(),
|
||||
if state.hovered {
|
||||
message_style.hover().text.clone()
|
||||
} else {
|
||||
message_style.default.text.clone()
|
||||
},
|
||||
)
|
||||
.aligned()
|
||||
.contained()
|
||||
.with_margin_left(item_spacing)
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(|cx| cx.dispatch_action(GoToNextDiagnostic))
|
||||
.boxed(),
|
||||
);
|
||||
}
|
||||
|
||||
element.named("diagnostic indicator")
|
||||
}
|
||||
|
||||
fn debug_json(&self, _: &gpui::AppContext) -> serde_json::Value {
|
||||
@@ -74,11 +215,21 @@ impl View for DiagnosticSummary {
|
||||
}
|
||||
}
|
||||
|
||||
impl StatusItemView for DiagnosticSummary {
|
||||
impl StatusItemView for DiagnosticIndicator {
|
||||
fn set_active_pane_item(
|
||||
&mut self,
|
||||
_: Option<&dyn workspace::ItemHandle>,
|
||||
_: &mut ViewContext<Self>,
|
||||
active_pane_item: Option<&dyn workspace::ItemHandle>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
|
||||
self.active_editor = Some(editor.downgrade());
|
||||
self._observe_active_editor = Some(cx.observe(&editor, Self::update));
|
||||
self.update(editor, cx);
|
||||
} else {
|
||||
self.active_editor = None;
|
||||
self.current_diagnostic = None;
|
||||
self._observe_active_editor = None;
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use gpui::{
|
||||
elements::*, geometry::vector::vec2f, AppContext, Entity, ModelHandle, MutableAppContext,
|
||||
RenderContext, Subscription, Task, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use language::{Bias, Buffer, Diagnostic, File as _, SelectionGoal};
|
||||
use language::{Bias, Buffer, File as _, SelectionGoal};
|
||||
use project::{File, Project, ProjectEntryId, ProjectPath};
|
||||
use rpc::proto::{self, update_view};
|
||||
use settings::Settings;
|
||||
@@ -507,75 +507,3 @@ impl StatusItemView for CursorPosition {
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DiagnosticMessage {
|
||||
diagnostic: Option<Diagnostic>,
|
||||
_observe_active_editor: Option<Subscription>,
|
||||
}
|
||||
|
||||
impl DiagnosticMessage {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
diagnostic: None,
|
||||
_observe_active_editor: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) {
|
||||
let editor = editor.read(cx);
|
||||
let buffer = editor.buffer().read(cx);
|
||||
let cursor_position = editor
|
||||
.newest_selection_with_snapshot::<usize>(&buffer.read(cx))
|
||||
.head();
|
||||
let new_diagnostic = buffer
|
||||
.read(cx)
|
||||
.diagnostics_in_range::<_, usize>(cursor_position..cursor_position, false)
|
||||
.filter(|entry| !entry.range.is_empty())
|
||||
.min_by_key(|entry| (entry.diagnostic.severity, entry.range.len()))
|
||||
.map(|entry| entry.diagnostic);
|
||||
if new_diagnostic != self.diagnostic {
|
||||
self.diagnostic = new_diagnostic;
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Entity for DiagnosticMessage {
|
||||
type Event = ();
|
||||
}
|
||||
|
||||
impl View for DiagnosticMessage {
|
||||
fn ui_name() -> &'static str {
|
||||
"DiagnosticMessage"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
if let Some(diagnostic) = &self.diagnostic {
|
||||
let theme = &cx.global::<Settings>().theme.workspace.status_bar;
|
||||
Label::new(
|
||||
diagnostic.message.split('\n').next().unwrap().to_string(),
|
||||
theme.diagnostic_message.clone(),
|
||||
)
|
||||
.boxed()
|
||||
} else {
|
||||
Empty::new().boxed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StatusItemView for DiagnosticMessage {
|
||||
fn set_active_pane_item(
|
||||
&mut self,
|
||||
active_pane_item: Option<&dyn ItemHandle>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
|
||||
self._observe_active_editor = Some(cx.observe(&editor, Self::update));
|
||||
self.update(editor, cx);
|
||||
} else {
|
||||
self.diagnostic = Default::default();
|
||||
self._observe_active_editor = None;
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
+126
-29
@@ -6,7 +6,8 @@ use gpui::{
|
||||
fonts::{HighlightStyle, TextStyle},
|
||||
Border,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use serde::{de::DeserializeOwned, Deserialize};
|
||||
use serde_json::Value;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
pub use theme_registry::*;
|
||||
@@ -38,8 +39,7 @@ pub struct Workspace {
|
||||
pub pane_divider: Border,
|
||||
pub leader_border_opacity: f32,
|
||||
pub leader_border_width: f32,
|
||||
pub left_sidebar: Sidebar,
|
||||
pub right_sidebar: Sidebar,
|
||||
pub sidebar_resize_handle: ContainerStyle,
|
||||
pub status_bar: StatusBar,
|
||||
pub toolbar: Toolbar,
|
||||
pub disconnected_overlay: ContainedText,
|
||||
@@ -55,13 +55,9 @@ pub struct Titlebar {
|
||||
pub avatar_width: f32,
|
||||
pub avatar_ribbon: AvatarRibbon,
|
||||
pub offline_icon: OfflineIcon,
|
||||
pub share_icon: ShareIcon,
|
||||
pub hovered_share_icon: ShareIcon,
|
||||
pub active_share_icon: ShareIcon,
|
||||
pub hovered_active_share_icon: ShareIcon,
|
||||
pub share_icon: Interactive<ShareIcon>,
|
||||
pub avatar: ImageStyle,
|
||||
pub sign_in_prompt: ContainedText,
|
||||
pub hovered_sign_in_prompt: ContainedText,
|
||||
pub sign_in_prompt: Interactive<ContainedText>,
|
||||
pub outdated_warning: ContainedText,
|
||||
}
|
||||
|
||||
@@ -137,23 +133,6 @@ pub struct FindEditor {
|
||||
pub max_width: f32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
pub struct Sidebar {
|
||||
#[serde(flatten)]
|
||||
pub container: ContainerStyle,
|
||||
pub width: f32,
|
||||
pub item: SidebarItem,
|
||||
pub active_item: SidebarItem,
|
||||
pub resize_handle: ContainerStyle,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
pub struct SidebarItem {
|
||||
pub icon_color: Color,
|
||||
pub icon_size: f32,
|
||||
pub height: f32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
pub struct StatusBar {
|
||||
#[serde(flatten)]
|
||||
@@ -161,10 +140,58 @@ pub struct StatusBar {
|
||||
pub height: f32,
|
||||
pub item_spacing: f32,
|
||||
pub cursor_position: TextStyle,
|
||||
pub diagnostic_message: TextStyle,
|
||||
pub lsp_message: TextStyle,
|
||||
pub auto_update_progress_message: TextStyle,
|
||||
pub auto_update_done_message: TextStyle,
|
||||
pub lsp_status: Interactive<StatusBarLspStatus>,
|
||||
pub sidebar_buttons: StatusBarSidebarButtons,
|
||||
pub diagnostic_summary: Interactive<StatusBarDiagnosticSummary>,
|
||||
pub diagnostic_message: Interactive<ContainedText>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
pub struct StatusBarSidebarButtons {
|
||||
pub group_left: ContainerStyle,
|
||||
pub group_right: ContainerStyle,
|
||||
pub item: Interactive<SidebarItem>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
pub struct StatusBarDiagnosticSummary {
|
||||
pub container_ok: ContainerStyle,
|
||||
pub container_warning: ContainerStyle,
|
||||
pub container_error: ContainerStyle,
|
||||
pub text: TextStyle,
|
||||
pub icon_color_ok: Color,
|
||||
pub icon_color_warning: Color,
|
||||
pub icon_color_error: Color,
|
||||
pub height: f32,
|
||||
pub icon_width: f32,
|
||||
pub icon_spacing: f32,
|
||||
pub summary_spacing: f32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
pub struct StatusBarLspStatus {
|
||||
#[serde(flatten)]
|
||||
pub container: ContainerStyle,
|
||||
pub height: f32,
|
||||
pub icon_spacing: f32,
|
||||
pub icon_color: Color,
|
||||
pub icon_width: f32,
|
||||
pub message: TextStyle,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
pub struct Sidebar {
|
||||
pub resize_handle: ContainerStyle,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Deserialize, Default)]
|
||||
pub struct SidebarItem {
|
||||
#[serde(flatten)]
|
||||
pub container: ContainerStyle,
|
||||
pub icon_color: Color,
|
||||
pub icon_size: f32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
@@ -291,7 +318,6 @@ pub struct ProjectDiagnostics {
|
||||
#[serde(flatten)]
|
||||
pub container: ContainerStyle,
|
||||
pub empty_message: TextStyle,
|
||||
pub status_bar_item: ContainedText,
|
||||
pub tab_icon_width: f32,
|
||||
pub tab_icon_spacing: f32,
|
||||
pub tab_summary_spacing: f32,
|
||||
@@ -384,6 +410,77 @@ pub struct FieldEditor {
|
||||
pub selection: SelectionStyle,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Copy)]
|
||||
pub struct Interactive<T> {
|
||||
pub default: T,
|
||||
pub hover: Option<T>,
|
||||
pub active: Option<T>,
|
||||
pub active_hover: Option<T>,
|
||||
}
|
||||
|
||||
impl<T> Interactive<T> {
|
||||
pub fn active(&self) -> &T {
|
||||
self.active.as_ref().unwrap_or(&self.default)
|
||||
}
|
||||
|
||||
pub fn hover(&self) -> &T {
|
||||
self.hover.as_ref().unwrap_or(&self.default)
|
||||
}
|
||||
|
||||
pub fn active_hover(&self) -> &T {
|
||||
self.active_hover.as_ref().unwrap_or(self.active())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T: DeserializeOwned> Deserialize<'de> for Interactive<T> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
struct Helper {
|
||||
#[serde(flatten)]
|
||||
default: Value,
|
||||
hover: Option<Value>,
|
||||
active: Option<Value>,
|
||||
active_hover: Option<Value>,
|
||||
}
|
||||
|
||||
let json = Helper::deserialize(deserializer)?;
|
||||
|
||||
let deserialize_state = |state_json: Option<Value>| -> Result<Option<T>, D::Error> {
|
||||
if let Some(mut state_json) = state_json {
|
||||
if let Value::Object(state_json) = &mut state_json {
|
||||
if let Value::Object(default) = &json.default {
|
||||
for (key, value) in default {
|
||||
if !state_json.contains_key(key) {
|
||||
state_json.insert(key.clone(), value.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Some(
|
||||
serde_json::from_value::<T>(state_json).map_err(serde::de::Error::custom)?,
|
||||
))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
};
|
||||
|
||||
let hover = deserialize_state(json.hover)?;
|
||||
let active = deserialize_state(json.active)?;
|
||||
let active_hover = deserialize_state(json.active_hover)?;
|
||||
let default = serde_json::from_value(json.default).map_err(serde::de::Error::custom)?;
|
||||
|
||||
Ok(Interactive {
|
||||
default,
|
||||
hover,
|
||||
active,
|
||||
active_hover,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Editor {
|
||||
pub fn replica_selection_style(&self, replica_id: u16) -> &SelectionStyle {
|
||||
let style_ix = replica_id as usize % (self.guest_selections.len() + 1);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{ItemHandle, StatusItemView};
|
||||
use futures::StreamExt;
|
||||
use gpui::{actions, AppContext};
|
||||
use gpui::{actions, AppContext, EventContext};
|
||||
use gpui::{
|
||||
elements::*, platform::CursorStyle, Entity, ModelHandle, MutableAppContext, RenderContext,
|
||||
View, ViewContext,
|
||||
@@ -117,11 +117,13 @@ impl View for LspStatus {
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
let theme = &cx.global::<Settings>().theme;
|
||||
let mut message;
|
||||
let mut icon = None;
|
||||
let mut handler = None;
|
||||
|
||||
let mut pending_work = self.pending_language_server_work(cx);
|
||||
if let Some((lang_server_name, progress_token, progress)) = pending_work.next() {
|
||||
let mut message = lang_server_name.to_string();
|
||||
message = lang_server_name.to_string();
|
||||
|
||||
message.push_str(": ");
|
||||
if let Some(progress_message) = progress.message.as_ref() {
|
||||
@@ -138,21 +140,19 @@ impl View for LspStatus {
|
||||
if additional_work_count > 0 {
|
||||
write!(&mut message, " + {} more", additional_work_count).unwrap();
|
||||
}
|
||||
} else {
|
||||
drop(pending_work);
|
||||
|
||||
Label::new(message, theme.workspace.status_bar.lsp_message.clone()).boxed()
|
||||
} else if !self.downloading.is_empty() {
|
||||
Label::new(
|
||||
format!(
|
||||
if !self.downloading.is_empty() {
|
||||
icon = Some("icons/download-solid-14.svg");
|
||||
message = format!(
|
||||
"Downloading {} language server{}...",
|
||||
self.downloading.join(", "),
|
||||
if self.downloading.len() > 1 { "s" } else { "" }
|
||||
),
|
||||
theme.workspace.status_bar.lsp_message.clone(),
|
||||
)
|
||||
.boxed()
|
||||
} else if !self.checking_for_update.is_empty() {
|
||||
Label::new(
|
||||
format!(
|
||||
);
|
||||
} else if !self.checking_for_update.is_empty() {
|
||||
icon = Some("icons/download-solid-14.svg");
|
||||
message = format!(
|
||||
"Checking for updates to {} language server{}...",
|
||||
self.checking_for_update.join(", "),
|
||||
if self.checking_for_update.len() > 1 {
|
||||
@@ -160,30 +160,59 @@ impl View for LspStatus {
|
||||
} else {
|
||||
""
|
||||
}
|
||||
),
|
||||
theme.workspace.status_bar.lsp_message.clone(),
|
||||
)
|
||||
.boxed()
|
||||
} else if !self.failed.is_empty() {
|
||||
drop(pending_work);
|
||||
MouseEventHandler::new::<Self, _, _>(0, cx, |_, cx| {
|
||||
let theme = &cx.global::<Settings>().theme;
|
||||
Label::new(
|
||||
format!(
|
||||
"Failed to download {} language server{}. Click to dismiss.",
|
||||
self.failed.join(", "),
|
||||
if self.failed.len() > 1 { "s" } else { "" }
|
||||
),
|
||||
theme.workspace.status_bar.lsp_message.clone(),
|
||||
)
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(|cx| cx.dispatch_action(DismissErrorMessage))
|
||||
.boxed()
|
||||
} else {
|
||||
Empty::new().boxed()
|
||||
);
|
||||
} else if !self.failed.is_empty() {
|
||||
icon = Some("icons/warning-solid-14.svg");
|
||||
message = format!(
|
||||
"Failed to download {} language server{}. Click to dismiss.",
|
||||
self.failed.join(", "),
|
||||
if self.failed.len() > 1 { "s" } else { "" }
|
||||
);
|
||||
handler = Some(|cx: &mut EventContext| cx.dispatch_action(DismissErrorMessage));
|
||||
} else {
|
||||
return Empty::new().boxed();
|
||||
}
|
||||
}
|
||||
|
||||
let mut element = MouseEventHandler::new::<Self, _, _>(0, cx, |state, cx| {
|
||||
let theme = &cx
|
||||
.global::<Settings>()
|
||||
.theme
|
||||
.workspace
|
||||
.status_bar
|
||||
.lsp_status;
|
||||
let style = if state.hovered && handler.is_some() {
|
||||
theme.hover.as_ref().unwrap_or(&theme.default)
|
||||
} else {
|
||||
&theme.default
|
||||
};
|
||||
Flex::row()
|
||||
.with_children(icon.map(|path| {
|
||||
Svg::new(path)
|
||||
.with_color(style.icon_color)
|
||||
.constrained()
|
||||
.with_width(style.icon_width)
|
||||
.contained()
|
||||
.with_margin_right(style.icon_spacing)
|
||||
.aligned()
|
||||
.named("warning-icon")
|
||||
}))
|
||||
.with_child(Label::new(message, style.message.clone()).aligned().boxed())
|
||||
.constrained()
|
||||
.with_height(style.height)
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
.aligned()
|
||||
.boxed()
|
||||
});
|
||||
|
||||
if let Some(handler) = handler {
|
||||
element = element
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(handler);
|
||||
}
|
||||
|
||||
element.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+152
-106
@@ -1,9 +1,14 @@
|
||||
use super::Workspace;
|
||||
use gpui::{elements::*, impl_actions, platform::CursorStyle, AnyViewHandle, RenderContext};
|
||||
use gpui::{
|
||||
elements::*, impl_actions, platform::CursorStyle, AnyViewHandle, Entity, RenderContext, View,
|
||||
ViewContext, ViewHandle,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use settings::Settings;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use theme::Theme;
|
||||
|
||||
use crate::StatusItemView;
|
||||
|
||||
pub struct Sidebar {
|
||||
side: Side,
|
||||
items: Vec<Item>,
|
||||
@@ -12,31 +17,36 @@ pub struct Sidebar {
|
||||
custom_width: Rc<RefCell<f32>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Deserialize)]
|
||||
#[derive(Clone, Copy, Debug, Deserialize)]
|
||||
pub enum Side {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Item {
|
||||
icon_path: &'static str,
|
||||
view: AnyViewHandle,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct ToggleSidebarItem(pub SidebarItemId);
|
||||
pub struct SidebarButtons {
|
||||
sidebar: ViewHandle<Sidebar>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct ToggleSidebarItemFocus(pub SidebarItemId);
|
||||
|
||||
impl_actions!(workspace, [ToggleSidebarItem, ToggleSidebarItemFocus]);
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct SidebarItemId {
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct ToggleSidebarItem {
|
||||
pub side: Side,
|
||||
pub item_index: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct ToggleSidebarItemFocus {
|
||||
pub side: Side,
|
||||
pub item_index: usize,
|
||||
}
|
||||
|
||||
impl_actions!(workspace, [ToggleSidebarItem, ToggleSidebarItemFocus]);
|
||||
|
||||
impl Sidebar {
|
||||
pub fn new(side: Side) -> Self {
|
||||
Self {
|
||||
@@ -48,20 +58,28 @@ impl Sidebar {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_item(&mut self, icon_path: &'static str, view: AnyViewHandle) {
|
||||
pub fn add_item(
|
||||
&mut self,
|
||||
icon_path: &'static str,
|
||||
view: AnyViewHandle,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.items.push(Item { icon_path, view });
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
pub fn activate_item(&mut self, item_ix: usize) {
|
||||
pub fn activate_item(&mut self, item_ix: usize, cx: &mut ViewContext<Self>) {
|
||||
self.active_item_ix = Some(item_ix);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn toggle_item(&mut self, item_ix: usize) {
|
||||
pub fn toggle_item(&mut self, item_ix: usize, cx: &mut ViewContext<Self>) {
|
||||
if self.active_item_ix == Some(item_ix) {
|
||||
self.active_item_ix = None;
|
||||
} else {
|
||||
self.active_item_ix = Some(item_ix);
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn active_item(&self) -> Option<&AnyViewHandle> {
|
||||
@@ -70,101 +88,14 @@ impl Sidebar {
|
||||
.map(|item| &item.view)
|
||||
}
|
||||
|
||||
fn theme<'a>(&self, theme: &'a Theme) -> &'a theme::Sidebar {
|
||||
match self.side {
|
||||
Side::Left => &theme.workspace.left_sidebar,
|
||||
Side::Right => &theme.workspace.right_sidebar,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&self, theme: &Theme, cx: &mut RenderContext<Workspace>) -> ElementBox {
|
||||
let side = self.side;
|
||||
let theme = self.theme(theme);
|
||||
|
||||
ConstrainedBox::new(
|
||||
Container::new(
|
||||
Flex::column()
|
||||
.with_children(self.items.iter().enumerate().map(|(item_index, item)| {
|
||||
let theme = if Some(item_index) == self.active_item_ix {
|
||||
&theme.active_item
|
||||
} else {
|
||||
&theme.item
|
||||
};
|
||||
enum SidebarButton {}
|
||||
MouseEventHandler::new::<SidebarButton, _, _>(item.view.id(), cx, |_, _| {
|
||||
ConstrainedBox::new(
|
||||
Align::new(
|
||||
ConstrainedBox::new(
|
||||
Svg::new(item.icon_path)
|
||||
.with_color(theme.icon_color)
|
||||
.boxed(),
|
||||
)
|
||||
.with_height(theme.icon_size)
|
||||
.boxed(),
|
||||
)
|
||||
.boxed(),
|
||||
)
|
||||
.with_height(theme.height)
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_mouse_down(move |cx| {
|
||||
cx.dispatch_action(ToggleSidebarItem(SidebarItemId {
|
||||
side,
|
||||
item_index,
|
||||
}))
|
||||
})
|
||||
.boxed()
|
||||
}))
|
||||
.boxed(),
|
||||
)
|
||||
.with_style(theme.container)
|
||||
.boxed(),
|
||||
)
|
||||
.with_width(theme.width)
|
||||
.boxed()
|
||||
}
|
||||
|
||||
pub fn render_active_item(
|
||||
&self,
|
||||
theme: &Theme,
|
||||
cx: &mut RenderContext<Workspace>,
|
||||
) -> Option<ElementBox> {
|
||||
if let Some(active_item) = self.active_item() {
|
||||
let mut container = Flex::row();
|
||||
if matches!(self.side, Side::Right) {
|
||||
container.add_child(self.render_resize_handle(theme, cx));
|
||||
}
|
||||
|
||||
container.add_child(
|
||||
Hook::new(
|
||||
ConstrainedBox::new(ChildView::new(active_item).boxed())
|
||||
.with_max_width(*self.custom_width.borrow())
|
||||
.boxed(),
|
||||
)
|
||||
.on_after_layout({
|
||||
let actual_width = self.actual_width.clone();
|
||||
move |size, _| *actual_width.borrow_mut() = size.x()
|
||||
})
|
||||
.flex(1., false)
|
||||
.boxed(),
|
||||
);
|
||||
if matches!(self.side, Side::Left) {
|
||||
container.add_child(self.render_resize_handle(theme, cx));
|
||||
}
|
||||
Some(container.boxed())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn render_resize_handle(&self, theme: &Theme, cx: &mut RenderContext<Workspace>) -> ElementBox {
|
||||
fn render_resize_handle(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
let actual_width = self.actual_width.clone();
|
||||
let custom_width = self.custom_width.clone();
|
||||
let side = self.side;
|
||||
MouseEventHandler::new::<Self, _, _>(side as usize, cx, |_, _| {
|
||||
Container::new(Empty::new().boxed())
|
||||
.with_style(self.theme(theme).resize_handle)
|
||||
Empty::new()
|
||||
.contained()
|
||||
.with_style(theme.workspace.sidebar_resize_handle)
|
||||
.boxed()
|
||||
})
|
||||
.with_padding(Padding {
|
||||
@@ -185,3 +116,118 @@ impl Sidebar {
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
impl Entity for Sidebar {
|
||||
type Event = ();
|
||||
}
|
||||
|
||||
impl View for Sidebar {
|
||||
fn ui_name() -> &'static str {
|
||||
"Sidebar"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
if let Some(active_item) = self.active_item() {
|
||||
let mut container = Flex::row();
|
||||
if matches!(self.side, Side::Right) {
|
||||
container.add_child(self.render_resize_handle(&theme, cx));
|
||||
}
|
||||
|
||||
container.add_child(
|
||||
Hook::new(
|
||||
ChildView::new(active_item)
|
||||
.constrained()
|
||||
.with_max_width(*self.custom_width.borrow())
|
||||
.boxed(),
|
||||
)
|
||||
.on_after_layout({
|
||||
let actual_width = self.actual_width.clone();
|
||||
move |size, _| *actual_width.borrow_mut() = size.x()
|
||||
})
|
||||
.flex(1., false)
|
||||
.boxed(),
|
||||
);
|
||||
if matches!(self.side, Side::Left) {
|
||||
container.add_child(self.render_resize_handle(&theme, cx));
|
||||
}
|
||||
container.boxed()
|
||||
} else {
|
||||
Empty::new().boxed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SidebarButtons {
|
||||
pub fn new(sidebar: ViewHandle<Sidebar>, cx: &mut ViewContext<Self>) -> Self {
|
||||
cx.observe(&sidebar, |_, _, cx| cx.notify()).detach();
|
||||
Self { sidebar }
|
||||
}
|
||||
}
|
||||
|
||||
impl Entity for SidebarButtons {
|
||||
type Event = ();
|
||||
}
|
||||
|
||||
impl View for SidebarButtons {
|
||||
fn ui_name() -> &'static str {
|
||||
"SidebarToggleButton"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
let theme = &cx
|
||||
.global::<Settings>()
|
||||
.theme
|
||||
.workspace
|
||||
.status_bar
|
||||
.sidebar_buttons;
|
||||
let sidebar = self.sidebar.read(cx);
|
||||
let item_style = theme.item;
|
||||
let active_ix = sidebar.active_item_ix;
|
||||
let side = sidebar.side;
|
||||
let group_style = match side {
|
||||
Side::Left => theme.group_left,
|
||||
Side::Right => theme.group_right,
|
||||
};
|
||||
let items = sidebar.items.clone();
|
||||
Flex::row()
|
||||
.with_children(items.iter().enumerate().map(|(ix, item)| {
|
||||
MouseEventHandler::new::<Self, _, _>(ix, cx, move |state, _| {
|
||||
let style = if Some(ix) == active_ix {
|
||||
item_style.active()
|
||||
} else if state.hovered {
|
||||
item_style.hover()
|
||||
} else {
|
||||
&item_style.default
|
||||
};
|
||||
Svg::new(item.icon_path)
|
||||
.with_color(style.icon_color)
|
||||
.constrained()
|
||||
.with_height(style.icon_size)
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(move |cx| {
|
||||
cx.dispatch_action(ToggleSidebarItem {
|
||||
side,
|
||||
item_index: ix,
|
||||
})
|
||||
})
|
||||
.boxed()
|
||||
}))
|
||||
.contained()
|
||||
.with_style(group_style)
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
impl StatusItemView for SidebarButtons {
|
||||
fn set_active_pane_item(
|
||||
&mut self,
|
||||
_: Option<&dyn crate::ItemHandle>,
|
||||
_: &mut ViewContext<Self>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::{ItemHandle, Pane};
|
||||
use settings::Settings;
|
||||
use gpui::{
|
||||
elements::*, AnyViewHandle, ElementBox, Entity, MutableAppContext, RenderContext, Subscription,
|
||||
View, ViewContext, ViewHandle,
|
||||
};
|
||||
use settings::Settings;
|
||||
|
||||
pub trait StatusItemView: View {
|
||||
fn set_active_pane_item(
|
||||
@@ -48,7 +48,7 @@ impl View for StatusBar {
|
||||
.with_margin_right(theme.item_spacing)
|
||||
.boxed()
|
||||
}))
|
||||
.with_children(self.right_items.iter().map(|i| {
|
||||
.with_children(self.right_items.iter().rev().map(|i| {
|
||||
ChildView::new(i.as_ref())
|
||||
.aligned()
|
||||
.contained()
|
||||
|
||||
@@ -31,7 +31,7 @@ pub use pane_group::*;
|
||||
use postage::prelude::Stream;
|
||||
use project::{fs, Fs, Project, ProjectEntryId, ProjectPath, Worktree};
|
||||
use settings::Settings;
|
||||
use sidebar::{Side, Sidebar, SidebarItemId, ToggleSidebarItem, ToggleSidebarItemFocus};
|
||||
use sidebar::{Side, Sidebar, SidebarButtons, ToggleSidebarItem, ToggleSidebarItemFocus};
|
||||
use status_bar::StatusBar;
|
||||
pub use status_bar::StatusItemView;
|
||||
use std::{
|
||||
@@ -678,8 +678,8 @@ pub struct Workspace {
|
||||
themes: Arc<ThemeRegistry>,
|
||||
modal: Option<AnyViewHandle>,
|
||||
center: PaneGroup,
|
||||
left_sidebar: Sidebar,
|
||||
right_sidebar: Sidebar,
|
||||
left_sidebar: ViewHandle<Sidebar>,
|
||||
right_sidebar: ViewHandle<Sidebar>,
|
||||
panes: Vec<ViewHandle<Pane>>,
|
||||
active_pane: ViewHandle<Pane>,
|
||||
status_bar: ViewHandle<StatusBar>,
|
||||
@@ -751,7 +751,6 @@ impl Workspace {
|
||||
cx.focus(&pane);
|
||||
cx.emit(Event::PaneAdded(pane.clone()));
|
||||
|
||||
let status_bar = cx.add_view(|cx| StatusBar::new(&pane, cx));
|
||||
let mut current_user = params.user_store.read(cx).watch_current_user().clone();
|
||||
let mut connection_status = params.client.status().clone();
|
||||
let _observe_current_user = cx.spawn_weak(|this, mut cx| async move {
|
||||
@@ -773,6 +772,18 @@ impl Workspace {
|
||||
|
||||
cx.emit_global(WorkspaceCreated(weak_self.clone()));
|
||||
|
||||
let left_sidebar = cx.add_view(|_| Sidebar::new(Side::Left));
|
||||
let right_sidebar = cx.add_view(|_| Sidebar::new(Side::Right));
|
||||
let left_sidebar_buttons = cx.add_view(|cx| SidebarButtons::new(left_sidebar.clone(), cx));
|
||||
let right_sidebar_buttons =
|
||||
cx.add_view(|cx| SidebarButtons::new(right_sidebar.clone(), cx));
|
||||
let status_bar = cx.add_view(|cx| {
|
||||
let mut status_bar = StatusBar::new(&pane.clone(), cx);
|
||||
status_bar.add_left_item(left_sidebar_buttons, cx);
|
||||
status_bar.add_right_item(right_sidebar_buttons, cx);
|
||||
status_bar
|
||||
});
|
||||
|
||||
let mut this = Workspace {
|
||||
modal: None,
|
||||
weak_self,
|
||||
@@ -785,8 +796,8 @@ impl Workspace {
|
||||
user_store: params.user_store.clone(),
|
||||
fs: params.fs.clone(),
|
||||
themes: params.themes.clone(),
|
||||
left_sidebar: Sidebar::new(Side::Left),
|
||||
right_sidebar: Sidebar::new(Side::Right),
|
||||
left_sidebar,
|
||||
right_sidebar,
|
||||
project: params.project.clone(),
|
||||
leader_state: Default::default(),
|
||||
follower_states_by_leader: Default::default(),
|
||||
@@ -801,12 +812,12 @@ impl Workspace {
|
||||
self.weak_self.clone()
|
||||
}
|
||||
|
||||
pub fn left_sidebar_mut(&mut self) -> &mut Sidebar {
|
||||
&mut self.left_sidebar
|
||||
pub fn left_sidebar(&self) -> &ViewHandle<Sidebar> {
|
||||
&self.left_sidebar
|
||||
}
|
||||
|
||||
pub fn right_sidebar_mut(&mut self) -> &mut Sidebar {
|
||||
&mut self.right_sidebar
|
||||
pub fn right_sidebar(&self) -> &ViewHandle<Sidebar> {
|
||||
&self.right_sidebar
|
||||
}
|
||||
|
||||
pub fn status_bar(&self) -> &ViewHandle<StatusBar> {
|
||||
@@ -1028,12 +1039,15 @@ impl Workspace {
|
||||
}
|
||||
|
||||
pub fn toggle_sidebar_item(&mut self, action: &ToggleSidebarItem, cx: &mut ViewContext<Self>) {
|
||||
let sidebar = match action.0.side {
|
||||
let sidebar = match action.side {
|
||||
Side::Left => &mut self.left_sidebar,
|
||||
Side::Right => &mut self.right_sidebar,
|
||||
};
|
||||
sidebar.toggle_item(action.0.item_index);
|
||||
if let Some(active_item) = sidebar.active_item() {
|
||||
let active_item = sidebar.update(cx, |sidebar, cx| {
|
||||
sidebar.toggle_item(action.item_index, cx);
|
||||
sidebar.active_item().cloned()
|
||||
});
|
||||
if let Some(active_item) = active_item {
|
||||
cx.focus(active_item);
|
||||
} else {
|
||||
cx.focus_self();
|
||||
@@ -1046,12 +1060,15 @@ impl Workspace {
|
||||
action: &ToggleSidebarItemFocus,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let sidebar = match action.0.side {
|
||||
let sidebar = match action.side {
|
||||
Side::Left => &mut self.left_sidebar,
|
||||
Side::Right => &mut self.right_sidebar,
|
||||
};
|
||||
sidebar.activate_item(action.0.item_index);
|
||||
if let Some(active_item) = sidebar.active_item() {
|
||||
let active_item = sidebar.update(cx, |sidebar, cx| {
|
||||
sidebar.toggle_item(action.item_index, cx);
|
||||
sidebar.active_item().cloned()
|
||||
});
|
||||
if let Some(active_item) = active_item {
|
||||
if active_item.is_focused(cx) {
|
||||
cx.focus_self();
|
||||
} else {
|
||||
@@ -1558,9 +1575,9 @@ impl Workspace {
|
||||
Some(
|
||||
MouseEventHandler::new::<Authenticate, _, _>(0, cx, |state, _| {
|
||||
let style = if state.hovered {
|
||||
&theme.workspace.titlebar.hovered_sign_in_prompt
|
||||
&theme.workspace.titlebar.sign_in_prompt.hover()
|
||||
} else {
|
||||
&theme.workspace.titlebar.sign_in_prompt
|
||||
&theme.workspace.titlebar.sign_in_prompt.default
|
||||
};
|
||||
Label::new("Sign in".to_string(), style.text.clone())
|
||||
.contained()
|
||||
@@ -1610,7 +1627,7 @@ impl Workspace {
|
||||
.boxed(),
|
||||
)
|
||||
.constrained()
|
||||
.with_width(theme.workspace.right_sidebar.width)
|
||||
.with_width(theme.workspace.titlebar.avatar_width)
|
||||
.contained()
|
||||
.with_margin_left(2.)
|
||||
.boxed();
|
||||
@@ -1632,18 +1649,17 @@ impl Workspace {
|
||||
{
|
||||
Some(
|
||||
MouseEventHandler::new::<ToggleShare, _, _>(0, cx, |state, cx| {
|
||||
let style = &theme.workspace.titlebar.share_icon;
|
||||
let style = if self.project().read(cx).is_shared() {
|
||||
if state.hovered {
|
||||
&theme.workspace.titlebar.hovered_active_share_icon
|
||||
style.active_hover()
|
||||
} else {
|
||||
&theme.workspace.titlebar.active_share_icon
|
||||
&style.active()
|
||||
}
|
||||
} else if state.hovered {
|
||||
&style.active()
|
||||
} else {
|
||||
if state.hovered {
|
||||
&theme.workspace.titlebar.hovered_share_icon
|
||||
} else {
|
||||
&theme.workspace.titlebar.share_icon
|
||||
}
|
||||
&style.default
|
||||
};
|
||||
Svg::new("icons/share.svg")
|
||||
.with_color(style.color)
|
||||
@@ -1656,7 +1672,7 @@ impl Workspace {
|
||||
.with_width(24.)
|
||||
.aligned()
|
||||
.constrained()
|
||||
.with_width(theme.workspace.right_sidebar.width)
|
||||
.with_width(24.)
|
||||
.aligned()
|
||||
.boxed()
|
||||
})
|
||||
@@ -1983,37 +1999,39 @@ impl View for Workspace {
|
||||
.with_child(
|
||||
Stack::new()
|
||||
.with_child({
|
||||
let mut content = Flex::row();
|
||||
content.add_child(self.left_sidebar.render(&theme, cx));
|
||||
if let Some(element) =
|
||||
self.left_sidebar.render_active_item(&theme, cx)
|
||||
{
|
||||
content
|
||||
.add_child(FlexItem::new(element).flex(0.8, false).boxed());
|
||||
}
|
||||
content.add_child(
|
||||
Flex::column()
|
||||
.with_child(
|
||||
FlexItem::new(self.center.render(
|
||||
&theme,
|
||||
&self.follower_states_by_leader,
|
||||
self.project.read(cx).collaborators(),
|
||||
))
|
||||
.flex(1., true)
|
||||
.boxed(),
|
||||
)
|
||||
.with_child(ChildView::new(&self.status_bar).boxed())
|
||||
Flex::row()
|
||||
.with_children(
|
||||
if self.left_sidebar.read(cx).active_item().is_some() {
|
||||
Some(
|
||||
ChildView::new(&self.left_sidebar)
|
||||
.flex(0.8, false)
|
||||
.boxed(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)
|
||||
.with_child(
|
||||
FlexItem::new(self.center.render(
|
||||
&theme,
|
||||
&self.follower_states_by_leader,
|
||||
self.project.read(cx).collaborators(),
|
||||
))
|
||||
.flex(1., true)
|
||||
.boxed(),
|
||||
);
|
||||
if let Some(element) =
|
||||
self.right_sidebar.render_active_item(&theme, cx)
|
||||
{
|
||||
content
|
||||
.add_child(FlexItem::new(element).flex(0.8, false).boxed());
|
||||
}
|
||||
content.add_child(self.right_sidebar.render(&theme, cx));
|
||||
content.boxed()
|
||||
)
|
||||
.with_children(
|
||||
if self.right_sidebar.read(cx).active_item().is_some() {
|
||||
Some(
|
||||
ChildView::new(&self.right_sidebar)
|
||||
.flex(0.8, false)
|
||||
.boxed(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)
|
||||
.boxed()
|
||||
})
|
||||
.with_children(self.modal.as_ref().map(|m| {
|
||||
ChildView::new(m)
|
||||
@@ -2026,6 +2044,7 @@ impl View for Workspace {
|
||||
.flex(1.0, true)
|
||||
.boxed(),
|
||||
)
|
||||
.with_child(ChildView::new(&self.status_bar).boxed())
|
||||
.contained()
|
||||
.with_background_color(theme.workspace.background)
|
||||
.boxed(),
|
||||
@@ -2202,10 +2221,10 @@ pub fn open_paths(
|
||||
let mut workspace = (app_state.build_workspace)(project, &app_state, cx);
|
||||
if contains_directory {
|
||||
workspace.toggle_sidebar_item(
|
||||
&ToggleSidebarItem(SidebarItemId {
|
||||
&ToggleSidebarItem {
|
||||
side: Side::Left,
|
||||
item_index: 0,
|
||||
}),
|
||||
},
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
+13
-23
@@ -6,7 +6,6 @@ pub mod test;
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use breadcrumbs::Breadcrumbs;
|
||||
use chat_panel::ChatPanel;
|
||||
pub use client;
|
||||
pub use contacts_panel;
|
||||
use contacts_panel::ContactsPanel;
|
||||
@@ -147,7 +146,7 @@ pub fn build_workspace(
|
||||
user_store: app_state.user_store.clone(),
|
||||
channel_list: app_state.channel_list.clone(),
|
||||
};
|
||||
let mut workspace = Workspace::new(&workspace_params, cx);
|
||||
let workspace = Workspace::new(&workspace_params, cx);
|
||||
let project = workspace.project().clone();
|
||||
|
||||
let theme_names = app_state.themes.list().collect();
|
||||
@@ -171,26 +170,18 @@ pub fn build_workspace(
|
||||
}));
|
||||
});
|
||||
|
||||
workspace.left_sidebar_mut().add_item(
|
||||
"icons/folder-tree-16.svg",
|
||||
ProjectPanel::new(project, cx).into(),
|
||||
);
|
||||
workspace.right_sidebar_mut().add_item(
|
||||
"icons/user-16.svg",
|
||||
cx.add_view(|cx| ContactsPanel::new(app_state.clone(), cx))
|
||||
.into(),
|
||||
);
|
||||
workspace.right_sidebar_mut().add_item(
|
||||
"icons/comment-16.svg",
|
||||
cx.add_view(|cx| {
|
||||
ChatPanel::new(app_state.client.clone(), app_state.channel_list.clone(), cx)
|
||||
})
|
||||
.into(),
|
||||
);
|
||||
let project_panel = ProjectPanel::new(project, cx);
|
||||
let contact_panel = cx.add_view(|cx| ContactsPanel::new(app_state.clone(), cx));
|
||||
|
||||
workspace.left_sidebar().update(cx, |sidebar, cx| {
|
||||
sidebar.add_item("icons/folder-tree-solid-14.svg", project_panel.into(), cx)
|
||||
});
|
||||
workspace.right_sidebar().update(cx, |sidebar, cx| {
|
||||
sidebar.add_item("icons/contacts-solid-14.svg", contact_panel.into(), cx)
|
||||
});
|
||||
|
||||
let diagnostic_message = cx.add_view(|_| editor::items::DiagnosticMessage::new());
|
||||
let diagnostic_summary =
|
||||
cx.add_view(|cx| diagnostics::items::DiagnosticSummary::new(workspace.project(), cx));
|
||||
cx.add_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace.project(), cx));
|
||||
let lsp_status = cx.add_view(|cx| {
|
||||
workspace::lsp_status::LspStatus::new(workspace.project(), app_state.languages.clone(), cx)
|
||||
});
|
||||
@@ -198,10 +189,9 @@ pub fn build_workspace(
|
||||
let auto_update = cx.add_view(|cx| auto_update::AutoUpdateIndicator::new(cx));
|
||||
workspace.status_bar().update(cx, |status_bar, cx| {
|
||||
status_bar.add_left_item(diagnostic_summary, cx);
|
||||
status_bar.add_left_item(diagnostic_message, cx);
|
||||
status_bar.add_left_item(lsp_status, cx);
|
||||
status_bar.add_right_item(auto_update, cx);
|
||||
status_bar.add_right_item(cursor_position, cx);
|
||||
status_bar.add_right_item(auto_update, cx);
|
||||
});
|
||||
|
||||
workspace
|
||||
@@ -362,7 +352,7 @@ mod tests {
|
||||
let workspace_1 = cx.root_view::<Workspace>(cx.window_ids()[0]).unwrap();
|
||||
workspace_1.update(cx, |workspace, cx| {
|
||||
assert_eq!(workspace.worktrees(cx).count(), 2);
|
||||
assert!(workspace.left_sidebar_mut().active_item().is_some());
|
||||
assert!(workspace.left_sidebar().read(cx).active_item().is_some());
|
||||
assert!(workspace.active_pane().is_focused(cx));
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user