Add blank pane experience

This commit is contained in:
Mikayla Maki 2023-03-08 17:56:39 -08:00
parent dad66eb3fb
commit 152755b043
21 changed files with 454 additions and 339 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

119
crates/theme/src/ui.rs Normal file
View File

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

View File

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

View File

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

View File

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

View File

@ -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(&center_pane, move |this, _, event, cx| {
this.handle_pane_event(pane_id, event, cx)
@ -610,7 +614,7 @@ impl Workspace {
.detach();
cx.focus(&center_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| {

View File

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

View File

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

View File

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

View File

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

View File

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