Detailed changes
@@ -198,6 +198,7 @@ impl TestServer {
build_window_options: |_, _, _| Default::default(),
initialize_workspace: |_, _, _| unimplemented!(),
dock_default_item_factory: |_, _| unimplemented!(),
+ background_actions: || unimplemented!(),
});
Project::init(&client);
@@ -434,15 +435,7 @@ impl TestClient {
cx: &mut TestAppContext,
) -> ViewHandle<Workspace> {
let (_, root_view) = cx.add_window(|_| EmptyView);
- cx.add_view(&root_view, |cx| {
- Workspace::new(
- Default::default(),
- 0,
- project.clone(),
- |_, _| unimplemented!(),
- cx,
- )
- })
+ cx.add_view(&root_view, |cx| Workspace::test_new(project.clone(), cx))
}
fn create_new_root_dir(&mut self) -> PathBuf {
@@ -1449,15 +1449,7 @@ async fn test_host_disconnect(
deterministic.run_until_parked();
assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
- let (_, workspace_b) = cx_b.add_window(|cx| {
- Workspace::new(
- Default::default(),
- 0,
- project_b.clone(),
- |_, _| unimplemented!(),
- cx,
- )
- });
+ let (_, workspace_b) = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "b.txt"), None, true, cx)
@@ -4706,15 +4698,7 @@ async fn test_collaborating_with_code_actions(
// Join the project as client B.
let project_b = client_b.build_remote_project(project_id, cx_b).await;
- let (_window_b, workspace_b) = cx_b.add_window(|cx| {
- Workspace::new(
- Default::default(),
- 0,
- project_b.clone(),
- |_, _| unimplemented!(),
- cx,
- )
- });
+ let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
@@ -4937,15 +4921,7 @@ async fn test_collaborating_with_renames(
.unwrap();
let project_b = client_b.build_remote_project(project_id, cx_b).await;
- let (_window_b, workspace_b) = cx_b.add_window(|cx| {
- Workspace::new(
- Default::default(),
- 0,
- project_b.clone(),
- |_, _| unimplemented!(),
- cx,
- )
- });
+ let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "one.rs"), None, true, cx)
@@ -86,6 +86,7 @@ fn join_project(action: &JoinProject, app_state: Arc<AppState>, cx: &mut Mutable
0,
project,
app_state.dock_default_item_factory,
+ app_state.background_actions,
cx,
);
(app_state.initialize_workspace)(&mut workspace, &app_state, cx);
@@ -352,9 +352,7 @@ mod tests {
});
let project = Project::test(app_state.fs.clone(), [], cx).await;
- let (_, workspace) = cx.add_window(|cx| {
- Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
- });
+ let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let editor = cx.add_view(&workspace, |cx| {
let mut editor = Editor::single_line(None, cx);
editor.set_text("abc", cx);
@@ -805,15 +805,7 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/test".as_ref()], cx).await;
- let (_, workspace) = cx.add_window(|cx| {
- Workspace::new(
- Default::default(),
- 0,
- project.clone(),
- |_, _| unimplemented!(),
- cx,
- )
- });
+ let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
// Create some diagnostics
project.update(cx, |project, cx| {
@@ -484,7 +484,9 @@ fn test_navigation_history(cx: &mut gpui::MutableAppContext) {
cx.set_global(Settings::test(cx));
cx.set_global(DragAndDrop::<Workspace>::default());
use workspace::item::Item;
- let (_, pane) = cx.add_window(Default::default(), |cx| Pane::new(None, cx));
+ let (_, pane) = cx.add_window(Default::default(), |cx| {
+ Pane::new(None, || unimplemented!(), cx)
+ });
let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
cx.add_view(&pane, |cx| {
@@ -2354,10 +2356,10 @@ async fn test_clipboard(cx: &mut gpui::TestAppContext) {
e.handle_input(") ", cx);
});
cx.assert_editor_state(indoc! {"
- ( oneβ
- three
- five ) Λtwo oneβ
four three six five ( oneβ
- three
+ ( oneβ
+ three
+ five ) Λtwo oneβ
four three six five ( oneβ
+ three
five ) Λ"});
// Cut with three selections, one of which is full-line.
@@ -5562,7 +5564,7 @@ async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
Settings::test_async(cx);
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
- let (_, pane) = cx.add_window(|cx| Pane::new(None, cx));
+ let (_, pane) = cx.add_window(|cx| Pane::new(None, || unimplemented!(), cx));
let leader = pane.update(cx, |_, cx| {
let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
@@ -5831,11 +5833,11 @@ async fn go_to_hunk(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppCon
cx.assert_editor_state(
&r#"
Λuse some::modified;
-
-
+
+
fn main() {
println!("hello there");
-
+
println!("around the");
println!("world");
}
@@ -65,15 +65,7 @@ impl<'a> EditorLspTestContext<'a> {
.insert_tree("/root", json!({ "dir": { file_name.clone(): "" }}))
.await;
- let (window_id, workspace) = cx.add_window(|cx| {
- Workspace::new(
- Default::default(),
- 0,
- project.clone(),
- |_, _| unimplemented!(),
- cx,
- )
- });
+ let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
project
.update(cx, |project, cx| {
project.find_or_create_local_worktree("/root", true, cx)
@@ -134,7 +126,7 @@ impl<'a> EditorLspTestContext<'a> {
(let_chain)
(await_expression)
] @indent
-
+
(_ "[" "]" @end) @indent
(_ "<" ">" @end) @indent
(_ "{" "}" @end) @indent
@@ -329,9 +329,7 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| {
- Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
- });
+ let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
cx.dispatch_action(window_id, Toggle);
let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
@@ -385,9 +383,7 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/dir".as_ref()], cx).await;
- let (_, workspace) = cx.add_window(|cx| {
- Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
- });
+ let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let (_, finder) =
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx));
@@ -461,9 +457,7 @@ mod tests {
cx,
)
.await;
- let (_, workspace) = cx.add_window(|cx| {
- Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
- });
+ let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let (_, finder) =
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx));
finder
@@ -487,9 +481,7 @@ mod tests {
cx,
)
.await;
- let (_, workspace) = cx.add_window(|cx| {
- Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
- });
+ let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let (_, finder) =
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx));
@@ -541,9 +533,7 @@ mod tests {
cx,
)
.await;
- let (_, workspace) = cx.add_window(|cx| {
- Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
- });
+ let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let (_, finder) =
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx));
@@ -585,9 +575,7 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
- let (_, workspace) = cx.add_window(|cx| {
- Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
- });
+ let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
// When workspace has an active item, sort items which are closer to that item
// first when they have the same name. In this case, b.txt is closer to dir2's a.txt
@@ -624,9 +612,7 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
- let (_, workspace) = cx.add_window(|cx| {
- Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
- });
+ let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let (_, finder) =
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx));
finder
@@ -6,15 +6,14 @@ use gpui::{
actions,
anyhow::{anyhow, Result},
elements::{
- AnchorCorner, ChildView, ConstrainedBox, Container, ContainerStyle, Empty, Flex,
- KeystrokeLabel, Label, MouseEventHandler, ParentElement, ScrollTarget, Stack, Svg,
- UniformList, UniformListState,
+ AnchorCorner, ChildView, ConstrainedBox, ContainerStyle, Empty, Flex, Label,
+ MouseEventHandler, ParentElement, ScrollTarget, Stack, Svg, UniformList, UniformListState,
},
geometry::vector::Vector2F,
impl_internal_actions,
keymap_matcher::KeymapContext,
platform::CursorStyle,
- Action, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, MouseButton,
+ AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, MouseButton,
MutableAppContext, PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle,
};
use menu::{Confirm, SelectNext, SelectPrev};
@@ -28,7 +27,7 @@ use std::{
path::{Path, PathBuf},
sync::Arc,
};
-use theme::{ContainedText, ProjectPanelEntry};
+use theme::ProjectPanelEntry;
use unicase::UniCase;
use workspace::Workspace;
@@ -1315,7 +1314,6 @@ impl View for ProjectPanel {
.with_child(ChildView::new(&self.context_menu, cx).boxed())
.boxed()
} else {
- let parent_view_id = cx.handle().id();
Flex::column()
.with_child(
MouseEventHandler::<Self>::new(2, cx, {
@@ -1327,12 +1325,11 @@ impl View for ProjectPanel {
let context_menu_item =
context_menu_item_style.style_for(state, true).clone();
- keystroke_label(
- parent_view_id,
+ theme::ui::keystroke_label(
"Open a project",
&button_style,
- context_menu_item.keystroke,
- workspace::Open,
+ &context_menu_item.keystroke,
+ Box::new(workspace::Open),
cx,
)
.boxed()
@@ -1357,38 +1354,6 @@ impl View for ProjectPanel {
}
}
-fn keystroke_label<A>(
- view_id: usize,
- label_text: &'static str,
- label_style: &ContainedText,
- keystroke_style: ContainedText,
- action: A,
- cx: &mut RenderContext<ProjectPanel>,
-) -> Container
-where
- A: Action,
-{
- Flex::row()
- .with_child(
- Label::new(label_text, label_style.text.clone())
- .contained()
- .boxed(),
- )
- .with_child({
- KeystrokeLabel::new(
- cx.window_id(),
- view_id,
- Box::new(action),
- keystroke_style.container,
- keystroke_style.text.clone(),
- )
- .flex_float()
- .boxed()
- })
- .contained()
- .with_style(label_style.container)
-}
-
impl Entity for ProjectPanel {
type Event = Event;
}
@@ -1474,15 +1439,7 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
- let (_, workspace) = cx.add_window(|cx| {
- Workspace::new(
- Default::default(),
- 0,
- project.clone(),
- |_, _| unimplemented!(),
- cx,
- )
- });
+ let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let panel = workspace.update(cx, |_, cx| ProjectPanel::new(project, cx));
assert_eq!(
visible_entries_as_strings(&panel, 0..50, cx),
@@ -1574,15 +1531,7 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
- let (_, workspace) = cx.add_window(|cx| {
- Workspace::new(
- Default::default(),
- 0,
- project.clone(),
- |_, _| unimplemented!(),
- cx,
- )
- });
+ let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let panel = workspace.update(cx, |_, cx| ProjectPanel::new(project, cx));
select_path(&panel, "root1", cx);
@@ -970,15 +970,7 @@ mod tests {
let params = cx.update(AppState::test);
let project = Project::test(params.fs.clone(), [], cx).await;
- let (_, workspace) = cx.add_window(|cx| {
- Workspace::new(
- Default::default(),
- 0,
- project.clone(),
- |_, _| unimplemented!(),
- cx,
- )
- });
+ let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
(project, workspace)
}
@@ -9,6 +9,9 @@ use gpui::{
use serde::{de::DeserializeOwned, Deserialize};
use serde_json::Value;
use std::{collections::HashMap, sync::Arc};
+use ui::{CheckboxStyle, IconStyle};
+
+pub mod ui;
pub use theme_registry::*;
@@ -50,6 +53,7 @@ pub struct ThemeMeta {
#[derive(Deserialize, Default)]
pub struct Workspace {
pub background: Color,
+ pub blank_pane: BlankPaneStyle,
pub titlebar: Titlebar,
pub tab_bar: TabBar,
pub pane_divider: Border,
@@ -69,6 +73,14 @@ pub struct Workspace {
pub drop_target_overlay_color: Color,
}
+#[derive(Clone, Deserialize, Default)]
+pub struct BlankPaneStyle {
+ pub logo: IconStyle,
+ pub keyboard_hints: ContainerStyle,
+ pub keyboard_hint: Interactive<ContainedText>,
+ pub keyboard_hint_width: f32,
+}
+
#[derive(Clone, Deserialize, Default)]
pub struct Titlebar {
#[serde(flatten)]
@@ -858,46 +870,18 @@ pub struct WelcomeStyle {
pub logo: IconStyle,
pub logo_subheading: ContainedText,
pub checkbox: CheckboxStyle,
+ pub checkbox_container: ContainerStyle,
pub button: Interactive<ContainedText>,
pub button_group: ContainerStyle,
pub heading_group: ContainerStyle,
pub checkbox_group: ContainerStyle,
}
-#[derive(Clone, Deserialize, Default)]
-pub struct IconStyle {
- pub color: Color,
- pub icon: String,
- pub dimensions: Dimensions,
-}
-
-#[derive(Clone, Deserialize, Default)]
-pub struct Dimensions {
- pub width: f32,
- pub height: f32,
-}
-
-#[derive(Clone, Deserialize, Default)]
-pub struct CheckboxStyle {
- pub check_icon: String,
- pub check_icon_color: Color,
- pub label: ContainedText,
- pub container: ContainerStyle,
- pub width: f32,
- pub height: f32,
- pub default: ContainerStyle,
- pub checked: ContainerStyle,
- pub hovered: ContainerStyle,
- pub hovered_and_checked: ContainerStyle,
-}
-
#[derive(Clone, Deserialize, Default)]
pub struct ColorScheme {
pub name: String,
pub is_light: bool,
-
pub ramps: RampSet,
-
pub lowest: Layer,
pub middle: Layer,
pub highest: Layer,
@@ -0,0 +1,119 @@
+use gpui::{
+ color::Color,
+ elements::{
+ ConstrainedBox, Container, ContainerStyle, Empty, Flex, KeystrokeLabel, Label,
+ MouseEventHandler, ParentElement, Svg,
+ },
+ Action, Element, EventContext, RenderContext, View,
+};
+use serde::Deserialize;
+
+use crate::ContainedText;
+
+#[derive(Clone, Deserialize, Default)]
+pub struct CheckboxStyle {
+ pub icon: IconStyle,
+ pub label: ContainedText,
+ pub default: ContainerStyle,
+ pub checked: ContainerStyle,
+ pub hovered: ContainerStyle,
+ pub hovered_and_checked: ContainerStyle,
+}
+
+pub fn checkbox<T: 'static, V: View>(
+ label: &'static str,
+ style: &CheckboxStyle,
+ checked: bool,
+ cx: &mut RenderContext<V>,
+ change: fn(checked: bool, cx: &mut EventContext) -> (),
+) -> MouseEventHandler<T> {
+ MouseEventHandler::<T>::new(0, cx, |state, _| {
+ let indicator = if checked {
+ icon(&style.icon)
+ } else {
+ Empty::new()
+ .constrained()
+ .with_width(style.icon.dimensions.width)
+ .with_height(style.icon.dimensions.height)
+ };
+
+ Flex::row()
+ .with_children([
+ indicator
+ .contained()
+ .with_style(if checked {
+ if state.hovered() {
+ style.hovered_and_checked
+ } else {
+ style.checked
+ }
+ } else {
+ if state.hovered() {
+ style.hovered
+ } else {
+ style.default
+ }
+ })
+ .boxed(),
+ Label::new(label, style.label.text.clone())
+ .contained()
+ .with_style(style.label.container)
+ .boxed(),
+ ])
+ .align_children_center()
+ .boxed()
+ })
+ .on_click(gpui::MouseButton::Left, move |_, cx| change(!checked, cx))
+ .with_cursor_style(gpui::CursorStyle::PointingHand)
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct IconStyle {
+ pub color: Color,
+ pub icon: String,
+ pub dimensions: Dimensions,
+}
+
+#[derive(Clone, Deserialize, Default)]
+pub struct Dimensions {
+ pub width: f32,
+ pub height: f32,
+}
+
+pub fn icon(style: &IconStyle) -> ConstrainedBox {
+ Svg::new(style.icon.clone())
+ .with_color(style.color)
+ .constrained()
+ .with_width(style.dimensions.width)
+ .with_height(style.dimensions.height)
+}
+
+pub fn keystroke_label<V: View>(
+ label_text: &'static str,
+ label_style: &ContainedText,
+ keystroke_style: &ContainedText,
+ action: Box<dyn Action>,
+ cx: &mut RenderContext<V>,
+) -> Container {
+ // FIXME: Put the theme in it's own global so we can
+ // query the keystroke style on our own
+ Flex::row()
+ .with_child(
+ Label::new(label_text, label_style.text.clone())
+ .contained()
+ .boxed(),
+ )
+ .with_child({
+ KeystrokeLabel::new(
+ cx.window_id(),
+ cx.handle().id(),
+ action,
+ keystroke_style.container,
+ keystroke_style.text.clone(),
+ )
+ .flex_float()
+ .boxed()
+ })
+ .contained()
+ .with_style(label_style.container)
+}
@@ -4,12 +4,12 @@ use std::{borrow::Cow, sync::Arc};
use db::kvp::KEY_VALUE_STORE;
use gpui::{
- elements::{Empty, Flex, Label, MouseEventHandler, ParentElement, Svg},
+ elements::{Flex, Label, MouseEventHandler, ParentElement},
Action, Element, ElementBox, Entity, MouseButton, MutableAppContext, RenderContext,
Subscription, View, ViewContext,
};
-use settings::{settings_file::SettingsFile, Settings, SettingsFileContent};
-use theme::CheckboxStyle;
+use settings::{settings_file::SettingsFile, Settings};
+
use workspace::{
item::Item, open_new, sidebar::SidebarSide, AppState, PaneBackdrop, Welcome, Workspace,
WorkspaceId,
@@ -77,11 +77,7 @@ impl View for WelcomePage {
.with_children([
Flex::column()
.with_children([
- Svg::new(theme.welcome.logo.icon.clone())
- .with_color(theme.welcome.logo.color)
- .constrained()
- .with_width(theme.welcome.logo.dimensions.width)
- .with_height(theme.welcome.logo.dimensions.height)
+ theme::ui::icon(&theme.welcome.logo)
.aligned()
.contained()
.aligned()
@@ -128,20 +124,34 @@ impl View for WelcomePage {
.boxed(),
Flex::column()
.with_children([
- self.render_settings_checkbox::<Metrics>(
+ theme::ui::checkbox::<Metrics, Self>(
"Do you want to send telemetry?",
&theme.welcome.checkbox,
metrics,
cx,
- |content, checked| content.telemetry.set_metrics(checked),
- ),
- self.render_settings_checkbox::<Diagnostics>(
+ |checked, cx| {
+ SettingsFile::update(cx, move |file| {
+ file.telemetry.set_metrics(checked)
+ })
+ },
+ )
+ .contained()
+ .with_style(theme.welcome.checkbox_container)
+ .boxed(),
+ theme::ui::checkbox::<Diagnostics, Self>(
"Send crash reports",
&theme.welcome.checkbox,
diagnostics,
cx,
- |content, checked| content.telemetry.set_diagnostics(checked),
- ),
+ |checked, cx| {
+ SettingsFile::update(cx, move |file| {
+ file.telemetry.set_diagnostics(checked)
+ })
+ },
+ )
+ .contained()
+ .with_style(theme.welcome.checkbox_container)
+ .boxed(),
])
.contained()
.with_style(theme.welcome.checkbox_group)
@@ -204,59 +214,59 @@ impl WelcomePage {
.boxed()
}
- fn render_settings_checkbox<T: 'static>(
- &self,
- label: &'static str,
- style: &CheckboxStyle,
- checked: bool,
- cx: &mut RenderContext<Self>,
- set_value: fn(&mut SettingsFileContent, checked: bool) -> (),
- ) -> ElementBox {
- MouseEventHandler::<T>::new(0, cx, |state, _| {
- let indicator = if checked {
- Svg::new(style.check_icon.clone())
- .with_color(style.check_icon_color)
- .constrained()
- } else {
- Empty::new().constrained()
- };
+ // fn render_settings_checkbox<T: 'static>(
+ // &self,
+ // label: &'static str,
+ // style: &CheckboxStyle,
+ // checked: bool,
+ // cx: &mut RenderContext<Self>,
+ // set_value: fn(&mut SettingsFileContent, checked: bool) -> (),
+ // ) -> ElementBox {
+ // MouseEventHandler::<T>::new(0, cx, |state, _| {
+ // let indicator = if checked {
+ // Svg::new(style.check_icon.clone())
+ // .with_color(style.check_icon_color)
+ // .constrained()
+ // } else {
+ // Empty::new().constrained()
+ // };
- Flex::row()
- .with_children([
- indicator
- .with_width(style.width)
- .with_height(style.height)
- .contained()
- .with_style(if checked {
- if state.hovered() {
- style.hovered_and_checked
- } else {
- style.checked
- }
- } else {
- if state.hovered() {
- style.hovered
- } else {
- style.default
- }
- })
- .boxed(),
- Label::new(label, style.label.text.clone())
- .contained()
- .with_style(style.label.container)
- .boxed(),
- ])
- .align_children_center()
- .boxed()
- })
- .on_click(gpui::MouseButton::Left, move |_, cx| {
- SettingsFile::update(cx, move |content| set_value(content, !checked))
- })
- .with_cursor_style(gpui::CursorStyle::PointingHand)
- .contained()
- .with_style(style.container)
- .boxed()
- }
+ // Flex::row()
+ // .with_children([
+ // indicator
+ // .with_width(style.width)
+ // .with_height(style.height)
+ // .contained()
+ // .with_style(if checked {
+ // if state.hovered() {
+ // style.hovered_and_checked
+ // } else {
+ // style.checked
+ // }
+ // } else {
+ // if state.hovered() {
+ // style.hovered
+ // } else {
+ // style.default
+ // }
+ // })
+ // .boxed(),
+ // Label::new(label, style.label.text.clone())
+ // .contained()
+ // .with_style(style.label.container)
+ // .boxed(),
+ // ])
+ // .align_children_center()
+ // .boxed()
+ // })
+ // .on_click(gpui::MouseButton::Left, move |_, cx| {
+ // SettingsFile::update(cx, move |content| set_value(content, !checked))
+ // })
+ // .with_cursor_style(gpui::CursorStyle::PointingHand)
+ // .contained()
+ // .with_style(style.container)
+ // .boxed()
+ // }
}
impl Item for WelcomePage {
@@ -13,7 +13,7 @@ use gpui::{
use settings::{DockAnchor, Settings};
use theme::Theme;
-use crate::{sidebar::SidebarSide, ItemHandle, Pane, Workspace};
+use crate::{sidebar::SidebarSide, BackgroundActions, ItemHandle, Pane, Workspace};
pub use toggle_dock_button::ToggleDockButton;
#[derive(PartialEq, Clone, Deserialize)]
@@ -182,11 +182,12 @@ pub struct Dock {
impl Dock {
pub fn new(
default_item_factory: DockDefaultItemFactory,
+ background_actions: BackgroundActions,
cx: &mut ViewContext<Workspace>,
) -> Self {
let position = DockPosition::Hidden(cx.global::<Settings>().default_dock_anchor);
- let pane = cx.add_view(|cx| Pane::new(Some(position.anchor()), cx));
+ let pane = cx.add_view(|cx| Pane::new(Some(position.anchor()), background_actions, cx));
pane.update(cx, |pane, cx| {
pane.set_active(false, cx);
});
@@ -492,6 +493,7 @@ mod tests {
0,
project.clone(),
default_item_factory,
+ || unimplemented!(),
cx,
)
});
@@ -620,7 +622,14 @@ mod tests {
cx.update(|cx| init(cx));
let project = Project::test(fs, [], cx).await;
let (window_id, workspace) = cx.add_window(|cx| {
- Workspace::new(Default::default(), 0, project, default_item_factory, cx)
+ Workspace::new(
+ Default::default(),
+ 0,
+ project,
+ default_item_factory,
+ || unimplemented!(),
+ cx,
+ )
});
workspace.update(cx, |workspace, cx| {
@@ -110,6 +110,8 @@ impl_internal_actions!(
const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
+pub type BackgroundActions = fn() -> &'static [(&'static str, &'static dyn Action)];
+
pub fn init(cx: &mut MutableAppContext) {
cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| {
pane.activate_item(action.0, true, true, cx);
@@ -215,6 +217,7 @@ pub struct Pane {
toolbar: ViewHandle<Toolbar>,
tab_bar_context_menu: ViewHandle<ContextMenu>,
docked: Option<DockAnchor>,
+ background_actions: BackgroundActions,
}
pub struct ItemNavHistory {
@@ -271,7 +274,11 @@ enum ItemType {
}
impl Pane {
- pub fn new(docked: Option<DockAnchor>, cx: &mut ViewContext<Self>) -> Self {
+ pub fn new(
+ docked: Option<DockAnchor>,
+ background_actions: BackgroundActions,
+ cx: &mut ViewContext<Self>,
+ ) -> Self {
let handle = cx.weak_handle();
let context_menu = cx.add_view(ContextMenu::new);
Self {
@@ -292,6 +299,7 @@ impl Pane {
toolbar: cx.add_view(|_| Toolbar::new(handle)),
tab_bar_context_menu: context_menu,
docked,
+ background_actions,
}
}
@@ -1415,6 +1423,64 @@ impl Pane {
.flex(1., false)
.boxed()
}
+
+ fn render_blank_pane(&mut self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
+ let background = theme.workspace.background;
+ let keystroke_style = &theme.context_menu.item;
+ let theme = &theme.workspace.blank_pane;
+ Stack::new()
+ .with_children([
+ Empty::new()
+ .contained()
+ .with_background_color(background)
+ .boxed(),
+ Flex::column()
+ .align_children_center()
+ .with_children([
+ theme::ui::icon(&theme.logo).aligned().boxed(),
+ Flex::column()
+ .with_children({
+ enum KeyboardHint {}
+ let keyboard_hint = &theme.keyboard_hint;
+ (self.background_actions)().into_iter().enumerate().map(
+ move |(idx, (text, action))| {
+ let hint_action = action.boxed_clone();
+ MouseEventHandler::<KeyboardHint>::new(
+ idx,
+ cx,
+ move |state, cx| {
+ theme::ui::keystroke_label(
+ text,
+ &keyboard_hint.style_for(state, false),
+ &keystroke_style
+ .style_for(state, false)
+ .keystroke,
+ hint_action,
+ cx,
+ )
+ .boxed()
+ },
+ )
+ .on_click(MouseButton::Left, move |_, cx| {
+ cx.dispatch_any_action(action.boxed_clone())
+ })
+ .with_cursor_style(CursorStyle::PointingHand)
+ .boxed()
+ },
+ )
+ })
+ .contained()
+ .with_style(theme.keyboard_hints)
+ .constrained()
+ .with_max_width(theme.keyboard_hint_width)
+ .aligned()
+ .boxed(),
+ ])
+ .aligned()
+ .boxed(),
+ ])
+ .boxed()
+ }
}
impl Entity for Pane {
@@ -1508,11 +1574,8 @@ impl View for Pane {
enum EmptyPane {}
let theme = cx.global::<Settings>().theme.clone();
- dragged_item_receiver::<EmptyPane, _>(0, 0, false, None, cx, |_, _| {
- Empty::new()
- .contained()
- .with_background_color(theme.workspace.background)
- .boxed()
+ dragged_item_receiver::<EmptyPane, _>(0, 0, false, None, cx, |_, cx| {
+ self.render_blank_pane(&theme, cx)
})
.on_down(MouseButton::Left, |_, cx| {
cx.focus_parent_view();
@@ -1809,9 +1872,7 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
- let (_, workspace) = cx.add_window(|cx| {
- Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
- });
+ let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
// 1. Add with a destination index
@@ -1899,9 +1960,7 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
- let (_, workspace) = cx.add_window(|cx| {
- Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
- });
+ let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
// 1. Add with a destination index
@@ -1977,9 +2036,7 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
- let (_, workspace) = cx.add_window(|cx| {
- Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
- });
+ let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
// singleton view
@@ -2088,8 +2145,7 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
- let (_, workspace) =
- cx.add_window(|cx| Workspace::new(None, 0, project, |_, _| unimplemented!(), cx));
+ let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
add_labled_item(&workspace, &pane, "A", cx);
@@ -432,6 +432,7 @@ pub struct AppState {
fn(Option<WindowBounds>, Option<uuid::Uuid>, &dyn Platform) -> WindowOptions<'static>,
pub initialize_workspace: fn(&mut Workspace, &Arc<AppState>, &mut ViewContext<Workspace>),
pub dock_default_item_factory: DockDefaultItemFactory,
+ pub background_actions: BackgroundActions,
}
impl AppState {
@@ -455,6 +456,7 @@ impl AppState {
initialize_workspace: |_, _, _| {},
build_window_options: |_, _, _| Default::default(),
dock_default_item_factory: |_, _| unimplemented!(),
+ background_actions: || unimplemented!(),
})
}
}
@@ -542,6 +544,7 @@ pub struct Workspace {
active_call: Option<(ModelHandle<ActiveCall>, Vec<gpui::Subscription>)>,
leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
database_id: WorkspaceId,
+ background_actions: BackgroundActions,
_window_subscriptions: [Subscription; 3],
_apply_leader_updates: Task<Result<()>>,
_observe_current_user: Task<()>,
@@ -572,6 +575,7 @@ impl Workspace {
workspace_id: WorkspaceId,
project: ModelHandle<Project>,
dock_default_factory: DockDefaultItemFactory,
+ background_actions: BackgroundActions,
cx: &mut ViewContext<Self>,
) -> Self {
cx.observe(&project, |_, _, cx| cx.notify()).detach();
@@ -602,7 +606,7 @@ impl Workspace {
})
.detach();
- let center_pane = cx.add_view(|cx| Pane::new(None, cx));
+ let center_pane = cx.add_view(|cx| Pane::new(None, background_actions, cx));
let pane_id = center_pane.id();
cx.subscribe(¢er_pane, move |this, _, event, cx| {
this.handle_pane_event(pane_id, event, cx)
@@ -610,7 +614,7 @@ impl Workspace {
.detach();
cx.focus(¢er_pane);
cx.emit(Event::PaneAdded(center_pane.clone()));
- let dock = Dock::new(dock_default_factory, cx);
+ let dock = Dock::new(dock_default_factory, background_actions, cx);
let dock_pane = dock.pane().clone();
let fs = project.read(cx).fs().clone();
@@ -730,6 +734,7 @@ impl Workspace {
window_edited: false,
active_call,
database_id: workspace_id,
+ background_actions,
_observe_current_user,
_apply_leader_updates,
leader_updates_tx,
@@ -818,6 +823,7 @@ impl Workspace {
workspace_id,
project_handle,
app_state.dock_default_item_factory,
+ app_state.background_actions,
cx,
);
(app_state.initialize_workspace)(&mut workspace, &app_state, cx);
@@ -1432,7 +1438,7 @@ impl Workspace {
}
fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
- let pane = cx.add_view(|cx| Pane::new(None, cx));
+ let pane = cx.add_view(|cx| Pane::new(None, self.background_actions, cx));
let pane_id = pane.id();
cx.subscribe(&pane, move |this, _, event, cx| {
this.handle_pane_event(pane_id, event, cx)
@@ -2648,6 +2654,11 @@ impl Workspace {
})
.detach();
}
+
+ #[cfg(any(test, feature = "test-support"))]
+ pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
+ Self::new(None, 0, project, |_, _| None, || &[], cx)
+ }
}
fn notify_if_database_failed(workspace: &ViewHandle<Workspace>, cx: &mut AsyncAppContext) {
@@ -2988,17 +2999,10 @@ mod tests {
use super::*;
use fs::FakeFs;
- use gpui::{executor::Deterministic, TestAppContext, ViewContext};
+ use gpui::{executor::Deterministic, TestAppContext};
use project::{Project, ProjectEntryId};
use serde_json::json;
- pub fn default_item_factory(
- _workspace: &mut Workspace,
- _cx: &mut ViewContext<Workspace>,
- ) -> Option<Box<dyn ItemHandle>> {
- unimplemented!()
- }
-
#[gpui::test]
async fn test_tab_disambiguation(cx: &mut TestAppContext) {
cx.foreground().forbid_parking();
@@ -3011,7 +3015,8 @@ mod tests {
Default::default(),
0,
project.clone(),
- default_item_factory,
+ |_, _| unimplemented!(),
+ || unimplemented!(),
cx,
)
});
@@ -3083,7 +3088,8 @@ mod tests {
Default::default(),
0,
project.clone(),
- default_item_factory,
+ |_, _| unimplemented!(),
+ || unimplemented!(),
cx,
)
});
@@ -3183,7 +3189,8 @@ mod tests {
Default::default(),
0,
project.clone(),
- default_item_factory,
+ |_, _| unimplemented!(),
+ || unimplemented!(),
cx,
)
});
@@ -3222,7 +3229,14 @@ mod tests {
let project = Project::test(fs, None, cx).await;
let (window_id, workspace) = cx.add_window(|cx| {
- Workspace::new(Default::default(), 0, project, default_item_factory, cx)
+ Workspace::new(
+ Default::default(),
+ 0,
+ project,
+ |_, _| unimplemented!(),
+ || unimplemented!(),
+ cx,
+ )
});
let item1 = cx.add_view(&workspace, |cx| {
@@ -3331,7 +3345,14 @@ mod tests {
let project = Project::test(fs, [], cx).await;
let (window_id, workspace) = cx.add_window(|cx| {
- Workspace::new(Default::default(), 0, project, default_item_factory, cx)
+ Workspace::new(
+ Default::default(),
+ 0,
+ project,
+ |_, _| unimplemented!(),
+ || unimplemented!(),
+ cx,
+ )
});
// Create several workspace items with single project entries, and two
@@ -3440,7 +3461,14 @@ mod tests {
let project = Project::test(fs, [], cx).await;
let (window_id, workspace) = cx.add_window(|cx| {
- Workspace::new(Default::default(), 0, project, default_item_factory, cx)
+ Workspace::new(
+ Default::default(),
+ 0,
+ project,
+ |_, _| unimplemented!(),
+ || unimplemented!(),
+ cx,
+ )
});
let item = cx.add_view(&workspace, |cx| {
@@ -3559,7 +3587,14 @@ mod tests {
let project = Project::test(fs, [], cx).await;
let (_, workspace) = cx.add_window(|cx| {
- Workspace::new(Default::default(), 0, project, default_item_factory, cx)
+ Workspace::new(
+ Default::default(),
+ 0,
+ project,
+ |_, _| unimplemented!(),
+ || unimplemented!(),
+ cx,
+ )
});
let item = cx.add_view(&workspace, |cx| {
@@ -18,7 +18,7 @@ use futures::{
channel::{mpsc, oneshot},
FutureExt, SinkExt, StreamExt,
};
-use gpui::{App, AssetSource, AsyncAppContext, MutableAppContext, Task, ViewContext};
+use gpui::{Action, App, AssetSource, AsyncAppContext, MutableAppContext, Task, ViewContext};
use isahc::{config::Configurable, Request};
use language::LanguageRegistry;
use log::LevelFilter;
@@ -45,9 +45,10 @@ use theme::ThemeRegistry;
use util::StaffMode;
use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt};
use workspace::{
- self, item::ItemHandle, notifications::NotifyResultExt, AppState, NewFile, OpenPaths, Workspace,
+ self, dock::FocusDock, item::ItemHandle, notifications::NotifyResultExt, AppState, NewFile,
+ OpenPaths, Workspace,
};
-use zed::{self, build_window_options, initialize_workspace, languages, menus};
+use zed::{self, build_window_options, initialize_workspace, languages, menus, OpenSettings};
fn main() {
let http = http::client();
@@ -186,6 +187,7 @@ fn main() {
build_window_options,
initialize_workspace,
dock_default_item_factory,
+ background_actions,
});
auto_update::init(http, client::ZED_SERVER_URL.clone(), cx);
@@ -703,3 +705,13 @@ pub fn dock_default_item_factory(
Some(Box::new(terminal_view))
}
+
+pub fn background_actions() -> &'static [(&'static str, &'static dyn Action)] {
+ &[
+ ("Go to file", &file_finder::Toggle),
+ ("Open the command palette", &command_palette::Toggle),
+ ("Focus the dock", &FocusDock),
+ ("Open recent projects", &recent_projects::OpenRecent),
+ ("Change your settings", &OpenSettings),
+ ]
+}
@@ -889,9 +889,7 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
- let (_, workspace) = cx.add_window(|cx| {
- Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
- });
+ let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let entries = cx.read(|cx| workspace.file_project_paths(cx));
let file1 = entries[0].clone();
@@ -1010,9 +1008,7 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/dir1".as_ref()], cx).await;
- let (_, workspace) = cx.add_window(|cx| {
- Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
- });
+ let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
// Open a file within an existing worktree.
cx.update(|cx| {
@@ -1171,9 +1167,7 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| {
- Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
- });
+ let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
// Open a file within an existing worktree.
cx.update(|cx| {
@@ -1215,9 +1209,7 @@ mod tests {
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
project.update(cx, |project, _| project.languages().add(rust_lang()));
- let (window_id, workspace) = cx.add_window(|cx| {
- Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
- });
+ let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let worktree = cx.read(|cx| workspace.read(cx).worktrees(cx).next().unwrap());
// Create a new untitled buffer
@@ -1306,9 +1298,7 @@ mod tests {
let project = Project::test(app_state.fs.clone(), [], cx).await;
project.update(cx, |project, _| project.languages().add(rust_lang()));
- let (window_id, workspace) = cx.add_window(|cx| {
- Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
- });
+ let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
// Create a new untitled buffer
cx.dispatch_action(window_id, NewFile);
@@ -1361,9 +1351,7 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| {
- Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
- });
+ let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let entries = cx.read(|cx| workspace.file_project_paths(cx));
let file1 = entries[0].clone();
@@ -1437,15 +1425,7 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
- let (_, workspace) = cx.add_window(|cx| {
- Workspace::new(
- Default::default(),
- 0,
- project.clone(),
- |_, _| unimplemented!(),
- cx,
- )
- });
+ let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let entries = cx.read(|cx| workspace.file_project_paths(cx));
let file1 = entries[0].clone();
@@ -1709,15 +1689,7 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
- let (_, workspace) = cx.add_window(|cx| {
- Workspace::new(
- Default::default(),
- 0,
- project.clone(),
- |_, _| unimplemented!(),
- cx,
- )
- });
+ let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
let entries = cx.read(|cx| workspace.file_project_paths(cx));
@@ -26,14 +26,19 @@ export default function contextMenu(colorScheme: ColorScheme) {
hover: {
background: background(layer, "hovered"),
label: text(layer, "sans", "hovered", { size: "sm" }),
+ keystroke: {
+ ...text(layer, "sans", "hovered", {
+ size: "sm",
+ weight: "bold",
+ }),
+ padding: { left: 3, right: 3 },
+ },
},
active: {
background: background(layer, "active"),
- label: text(layer, "sans", "active", { size: "sm" }),
},
activeHover: {
background: background(layer, "active"),
- label: text(layer, "sans", "active", { size: "sm" }),
},
},
separator: {
@@ -86,20 +86,24 @@ export default function welcome(colorScheme: ColorScheme) {
border: border(layer, "active"),
},
},
+ checkboxContainer: {
+ margin: {
+ top: 4,
+ },
+ },
checkbox: {
label: {
...text(layer, "sans", interactive_text_size),
// Also supports margin, container, border, etc.
},
- container: {
- margin: {
- top: 4,
- },
+ icon: {
+ color: foreground(layer, "on"),
+ icon: "icons/check_12.svg",
+ dimensions: {
+ width: 12,
+ height: 12,
+ }
},
- width: 12,
- height: 12,
- checkIcon: "icons/check_12.svg",
- checkIconColor: foreground(layer, "on"),
default: {
...checkboxBase,
background: background(layer, "default"),
@@ -41,6 +41,34 @@ export default function workspace(colorScheme: ColorScheme) {
return {
background: background(layer),
+ blankPane: {
+ logo: {
+ color: background(layer, "on"),
+ icon: "icons/logo_96.svg",
+ dimensions: {
+ width: 240,
+ height: 240,
+ }
+ },
+ keyboardHints: {
+ margin: {
+ top: 32
+ },
+ padding: {
+ bottom: -8.
+ }
+ },
+ keyboardHint: {
+ ...text(colorScheme.lowest, "sans", "variant", { size: "sm" }),
+ margin: {
+ bottom: 8
+ },
+ hover: {
+ ...text(colorScheme.lowest, "sans", "hovered", { size: "sm" }),
+ }
+ },
+ keyboardHintWidth: 240,
+ },
joiningProjectAvatar: {
cornerRadius: 40,
width: 80,