Merge branch 'main' into welcome2

This commit is contained in:
Mikayla 2023-11-28 15:38:51 -08:00
commit a41c857855
No known key found for this signature in database
88 changed files with 2768 additions and 2094 deletions

14
Cargo.lock generated
View file

@ -9487,6 +9487,7 @@ dependencies = [
"settings2", "settings2",
"smol", "smol",
"theme2", "theme2",
"ui2",
"util", "util",
"workspace2", "workspace2",
] ]
@ -10198,6 +10199,15 @@ dependencies = [
"tree-sitter", "tree-sitter",
] ]
[[package]]
name = "tree-sitter-uiua"
version = "0.3.3"
source = "git+https://github.com/shnarazk/tree-sitter-uiua?rev=9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2#9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2"
dependencies = [
"cc",
"tree-sitter",
]
[[package]] [[package]]
name = "tree-sitter-vue" name = "tree-sitter-vue"
version = "0.0.1" version = "0.0.1"
@ -11085,6 +11095,7 @@ dependencies = [
"settings2", "settings2",
"theme2", "theme2",
"theme_selector2", "theme_selector2",
"ui2",
"util", "util",
"workspace2", "workspace2",
] ]
@ -11660,6 +11671,7 @@ dependencies = [
"tree-sitter-svelte", "tree-sitter-svelte",
"tree-sitter-toml", "tree-sitter-toml",
"tree-sitter-typescript", "tree-sitter-typescript",
"tree-sitter-uiua",
"tree-sitter-vue", "tree-sitter-vue",
"tree-sitter-yaml", "tree-sitter-yaml",
"unindent", "unindent",
@ -11695,6 +11707,7 @@ dependencies = [
"auto_update2", "auto_update2",
"backtrace", "backtrace",
"call2", "call2",
"channel2",
"chrono", "chrono",
"cli", "cli",
"client2", "client2",
@ -11783,6 +11796,7 @@ dependencies = [
"tree-sitter-svelte", "tree-sitter-svelte",
"tree-sitter-toml", "tree-sitter-toml",
"tree-sitter-typescript", "tree-sitter-typescript",
"tree-sitter-uiua",
"tree-sitter-vue", "tree-sitter-vue",
"tree-sitter-yaml", "tree-sitter-yaml",
"unindent", "unindent",

View file

@ -197,6 +197,7 @@ tree-sitter-lua = "0.0.14"
tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" } tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" }
tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "786689b0562b9799ce53e824cb45a1a2a04dc673"} tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "786689b0562b9799ce53e824cb45a1a2a04dc673"}
tree-sitter-vue = {git = "https://github.com/zed-industries/tree-sitter-vue", rev = "9b6cb221ccb8d0b956fcb17e9a1efac2feefeb58"} tree-sitter-vue = {git = "https://github.com/zed-industries/tree-sitter-vue", rev = "9b6cb221ccb8d0b956fcb17e9a1efac2feefeb58"}
tree-sitter-uiua = {git = "https://github.com/shnarazk/tree-sitter-uiua", rev = "9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2"}
[patch.crates-io] [patch.crates-io]
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "3b0159d25559b603af566ade3c83d930bf466db1" } tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "3b0159d25559b603af566ade3c83d930bf466db1" }

View file

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.00001 12L3.5 7.50001M8.00001 12L12.5 7.50001M8.00001 12L8.00001 3.00001" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 272 B

View file

@ -1,3 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.125 6.99344L6.35938 3.63281M3.125 6.99344L6.35938 10.3672M3.125 6.99344H11" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/> <path d="M3.5 7.50001L8 3M3.5 7.50001L8 12M3.5 7.50001H12.5" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 275 B

After

Width:  |  Height:  |  Size: 248 B

Before After
Before After

View file

@ -1,3 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.8906 7.00125L7.64062 3.64062M10.8906 7.00125L7.64062 10.375M10.8906 7.00125H3" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/> <path d="M12.5 7.5L8 12M12.5 7.5L8 3M12.5 7.5L3.5 7.5" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 279 B

After

Width:  |  Height:  |  Size: 242 B

Before After
Before After

View file

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.99999 3.00001L12.5 7.50001M7.99999 3.00001L3.49999 7.50001M7.99999 3.00001L7.99999 12" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 286 B

3
assets/icons/command.svg Normal file
View file

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 3.625C11 2.86561 11.6156 2.25 12.375 2.25C13.1344 2.25 13.75 2.86561 13.75 3.625C13.75 4.38401 13.135 4.99939 12.3761 5C12.3758 5 12.3754 5 12.375 5H11V3.625ZM9.75 5V3.625C9.75 2.17525 10.9253 1 12.375 1C13.8247 1 15 2.17525 15 3.625C15 4.98872 13.9601 6.10955 12.63 6.23777V6.25H12.3766C12.376 6.25 12.3755 6.25 12.375 6.25H11V9.75H12.375C13.8247 9.75 15 10.9253 15 12.375C15 13.8247 13.8247 15 12.375 15C11.0113 15 9.89045 13.9601 9.76223 12.63H9.75V12.3773L9.75 12.375V11H6.25V12.375C6.25 13.8247 5.07475 15 3.625 15C2.17525 15 1 13.8247 1 12.375C1 11.0113 2.03991 9.89045 3.37 9.76223V9.75H3.62274L3.625 9.75H5L5 6.25H3.625C2.17525 6.25 1 5.07475 1 3.625C1 2.17525 2.17525 1 3.625 1C4.98872 1 6.10955 2.03991 6.23777 3.37H6.25L6.25 5L9.75 5ZM9.75 6.25L6.25 6.25L6.25 9.75H9.75V6.25ZM3.625 11H5V12.375C5 13.1344 4.38439 13.75 3.625 13.75C2.86561 13.75 2.25 13.1344 2.25 12.375C2.25 11.6162 2.86472 11.0009 3.62336 11L3.625 11ZM11 12.3766C11.0009 13.1353 11.6162 13.75 12.375 13.75C13.1344 13.75 13.75 13.1344 13.75 12.375C13.75 11.6156 13.1344 11 12.375 11H11V12.375L11 12.3766ZM3.625 5C2.86561 5 2.25 4.38439 2.25 3.625C2.25 2.86561 2.86561 2.25 3.625 2.25C4.38439 2.25 5 2.86561 5 3.625V5H3.625Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

3
assets/icons/control.svg Normal file
View file

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.5 6.12488L7.64656 1.97853C7.84183 1.78328 8.1584 1.78329 8.35366 1.97854L12.5 6.12488" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 262 B

3
assets/icons/option.svg Normal file
View file

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.35606 1.005H1.62545C1.28002 1.005 1 1.28502 1 1.63044C1 1.97587 1.28002 2.25589 1.62545 2.25589L5.35606 2.25589C5.62311 2.25589 5.8607 2.42545 5.94752 2.67799L9.75029 13.7387C10.0108 14.4963 10.7235 15.005 11.5247 15.005H14.3746C14.72 15.005 15 14.725 15 14.3796C15 14.0341 14.72 13.7541 14.3746 13.7541H11.5247C11.2576 13.7541 11.02 13.5845 10.9332 13.332L7.13046 2.27128C6.86998 1.51366 6.15721 1.005 5.35606 1.005ZM14.3745 1.005H9.75125C9.40582 1.005 9.1258 1.28502 9.1258 1.63044C9.1258 1.97587 9.40582 2.25589 9.75125 2.25589L14.3745 2.25589C14.72 2.25589 15 1.97587 15 1.63044C15 1.28502 14.72 1.005 14.3745 1.005Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 792 B

3
assets/icons/return.svg Normal file
View file

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.375 1.63C8.375 1.28482 8.65482 1.005 9 1.005H12.375C13.8247 1.005 15 2.18025 15 3.63V7.625C15 9.07474 13.8247 10.25 12.375 10.25H3.13388L6.07194 13.1881C6.31602 13.4321 6.31602 13.8279 6.07194 14.0719C5.82786 14.316 5.43214 14.316 5.18806 14.0719L1.18306 10.0669C0.938981 9.82286 0.938981 9.42714 1.18306 9.18306L5.18306 5.18306C5.42714 4.93898 5.82286 4.93898 6.06694 5.18306C6.31102 5.42714 6.31102 5.82286 6.06694 6.06694L3.13388 9H12.375C13.1344 9 13.75 8.38439 13.75 7.625V3.63C13.75 2.87061 13.1344 2.255 12.375 2.255H9C8.65482 2.255 8.375 1.97518 8.375 1.63Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 737 B

3
assets/icons/shift.svg Normal file
View file

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.46475 7.99652L7.85304 2.15921C7.93223 2.07342 8.06777 2.07341 8.14696 2.15921L13.5352 7.99652C13.7126 8.18869 13.5763 8.5 13.3148 8.5H10.5V13.7C10.5 13.8657 10.3657 14 10.2 14H5.8C5.63431 14 5.5 13.8657 5.5 13.7V8.5H2.6852C2.42367 8.5 2.28737 8.18869 2.46475 7.99652Z" stroke="black" stroke-width="1.25"/>
</svg>

After

Width:  |  Height:  |  Size: 421 B

View file

@ -84,8 +84,8 @@ impl Settings for AutoUpdateSetting {
pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppContext) { pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppContext) {
AutoUpdateSetting::register(cx); AutoUpdateSetting::register(cx);
cx.observe_new_views(|wokrspace: &mut Workspace, _cx| { cx.observe_new_views(|workspace: &mut Workspace, _cx| {
wokrspace workspace
.register_action(|_, action: &Check, cx| check(action, cx)) .register_action(|_, action: &Check, cx| check(action, cx))
.register_action(|_, _action: &CheckThatAutoUpdaterWorks, cx| { .register_action(|_, _action: &CheckThatAutoUpdaterWorks, cx| {
let prompt = cx.prompt(gpui::PromptLevel::Info, "It does!", &["Ok"]); let prompt = cx.prompt(gpui::PromptLevel::Info, "It does!", &["Ok"]);
@ -94,6 +94,11 @@ pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppCo
}) })
.detach(); .detach();
}); });
// @nate - code to trigger update notification on launch
// workspace.show_notification(0, _cx, |cx| {
// cx.build_view(|_| UpdateNotification::new(SemanticVersion::from_str("1.1.1").unwrap()))
// });
}) })
.detach(); .detach();
@ -131,7 +136,7 @@ pub fn check(_: &Check, cx: &mut AppContext) {
} }
} }
fn _view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) { pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) {
if let Some(auto_updater) = AutoUpdater::get(cx) { if let Some(auto_updater) = AutoUpdater::get(cx) {
let auto_updater = auto_updater.read(cx); let auto_updater = auto_updater.read(cx);
let server_url = &auto_updater.server_url; let server_url = &auto_updater.server_url;

View file

@ -1,10 +1,12 @@
use gpui::{ use gpui::{
div, DismissEvent, Div, EventEmitter, ParentElement, Render, SemanticVersion, ViewContext, div, DismissEvent, Div, EventEmitter, InteractiveElement, ParentElement, Render,
SemanticVersion, StatefulInteractiveElement, Styled, ViewContext,
}; };
use menu::Cancel; use util::channel::ReleaseChannel;
use workspace::ui::{h_stack, v_stack, Icon, IconElement, Label, StyledExt};
pub struct UpdateNotification { pub struct UpdateNotification {
_version: SemanticVersion, version: SemanticVersion,
} }
impl EventEmitter<DismissEvent> for UpdateNotification {} impl EventEmitter<DismissEvent> for UpdateNotification {}
@ -12,77 +14,43 @@ impl EventEmitter<DismissEvent> for UpdateNotification {}
impl Render for UpdateNotification { impl Render for UpdateNotification {
type Element = Div; type Element = Div;
fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
div().child("Updated zed!") let app_name = cx.global::<ReleaseChannel>().display_name();
// let theme = theme::current(cx).clone();
// let theme = &theme.update_notification;
// let app_name = cx.global::<ReleaseChannel>().display_name(); v_stack()
.elevation_3(cx)
// MouseEventHandler::new::<ViewReleaseNotes, _>(0, cx, |state, cx| { .p_4()
// Flex::column() .child(
// .with_child( h_stack()
// Flex::row() .justify_between()
// .with_child( .child(Label::new(format!(
// Text::new( "Updated to {app_name} {}",
// format!("Updated to {app_name} {}", self.version), self.version
// theme.message.text.clone(), )))
// ) .child(
// .contained() div()
// .with_style(theme.message.container) .id("cancel")
// .aligned() .child(IconElement::new(Icon::Close))
// .top() .cursor_pointer()
// .left() .on_click(cx.listener(|this, _, cx| this.dismiss(cx))),
// .flex(1., true), ),
// ) )
// .with_child( .child(
// MouseEventHandler::new::<Cancel, _>(0, cx, |state, _| { div()
// let style = theme.dismiss_button.style_for(state); .id("notes")
// Svg::new("icons/x.svg") .child(Label::new("View the release notes"))
// .with_color(style.color) .cursor_pointer()
// .constrained() .on_click(|_, cx| crate::view_release_notes(&Default::default(), cx)),
// .with_width(style.icon_width) )
// .aligned()
// .contained()
// .with_style(style.container)
// .constrained()
// .with_width(style.button_width)
// .with_height(style.button_width)
// })
// .with_padding(Padding::uniform(5.))
// .on_click(MouseButton::Left, move |_, this, cx| {
// this.dismiss(&Default::default(), cx)
// })
// .aligned()
// .constrained()
// .with_height(cx.font_cache().line_height(theme.message.text.font_size))
// .aligned()
// .top()
// .flex_float(),
// ),
// )
// .with_child({
// let style = theme.action_message.style_for(state);
// Text::new("View the release notes", style.text.clone())
// .contained()
// .with_style(style.container)
// })
// .contained()
// })
// .with_cursor_style(CursorStyle::PointingHand)
// .on_click(MouseButton::Left, |_, _, cx| {
// crate::view_release_notes(&Default::default(), cx)
// })
// .into_any_named("update notification")
} }
} }
impl UpdateNotification { impl UpdateNotification {
pub fn new(version: SemanticVersion) -> Self { pub fn new(version: SemanticVersion) -> Self {
Self { _version: version } Self { version }
} }
pub fn _dismiss(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) { pub fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
cx.emit(DismissEvent::Dismiss); cx.emit(DismissEvent::Dismiss);
} }
} }

View file

@ -660,9 +660,12 @@ impl CallHandler for Call {
self.active_call.as_ref().map(|call| { self.active_call.as_ref().map(|call| {
call.0.update(cx, |this, cx| { call.0.update(cx, |this, cx| {
this.room().map(|room| { this.room().map(|room| {
room.update(cx, |this, cx| { let room = room.clone();
this.toggle_mute(cx).log_err(); cx.spawn(|_, mut cx| async move {
room.update(&mut cx, |this, cx| this.toggle_mute(cx))??
.await
}) })
.detach_and_log_err(cx);
}) })
}) })
}); });

View file

@ -1,4 +1,7 @@
use crate::participant::{LocalParticipant, ParticipantLocation, RemoteParticipant}; use crate::{
call_settings::CallSettings,
participant::{LocalParticipant, ParticipantLocation, RemoteParticipant},
};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use audio::{Audio, Sound}; use audio::{Audio, Sound};
use client::{ use client::{
@ -18,6 +21,7 @@ use live_kit_client::{
}; };
use postage::{sink::Sink, stream::Stream, watch}; use postage::{sink::Sink, stream::Stream, watch};
use project::Project; use project::Project;
use settings::Settings as _;
use std::{future::Future, mem, sync::Arc, time::Duration}; use std::{future::Future, mem, sync::Arc, time::Duration};
use util::{post_inc, ResultExt, TryFutureExt}; use util::{post_inc, ResultExt, TryFutureExt};
@ -328,10 +332,8 @@ impl Room {
} }
} }
pub fn mute_on_join(_cx: &AppContext) -> bool { pub fn mute_on_join(cx: &AppContext) -> bool {
// todo!() po: This should be uncommented, though then unmuting does not work CallSettings::get_global(cx).mute_on_join || client::IMPERSONATE_LOGIN.is_some()
false
//CallSettings::get_global(cx).mute_on_join || client::IMPERSONATE_LOGIN.is_some()
} }
fn from_join_response( fn from_join_response(
@ -1265,7 +1267,6 @@ impl Room {
.ok_or_else(|| anyhow!("live-kit was not initialized"))? .ok_or_else(|| anyhow!("live-kit was not initialized"))?
.await .await
}; };
let publication = publish_track.await; let publication = publish_track.await;
this.upgrade() this.upgrade()
.ok_or_else(|| anyhow!("room was dropped"))? .ok_or_else(|| anyhow!("room was dropped"))?

File diff suppressed because it is too large Load diff

View file

@ -1,37 +1,34 @@
use client::{ContactRequestStatus, User, UserStore}; use client::{ContactRequestStatus, User, UserStore};
use gpui::{ use gpui::{
elements::*, AppContext, Entity, ModelHandle, MouseState, Task, View, ViewContext, ViewHandle, div, img, svg, AnyElement, AppContext, DismissEvent, Div, Entity, EventEmitter, FocusHandle,
FocusableView, Img, IntoElement, Model, ParentElement as _, Render, Styled, Task, View,
ViewContext, VisualContext, WeakView,
}; };
use picker::{Picker, PickerDelegate, PickerEvent}; use picker::{Picker, PickerDelegate};
use std::sync::Arc; use std::sync::Arc;
use util::TryFutureExt; use theme::ActiveTheme as _;
use workspace::Modal; use ui::{h_stack, v_stack, Label};
use util::{ResultExt as _, TryFutureExt};
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) {
Picker::<ContactFinderDelegate>::init(cx); //Picker::<ContactFinderDelegate>::init(cx);
cx.add_action(ContactFinder::dismiss) //cx.add_action(ContactFinder::dismiss)
} }
pub struct ContactFinder { pub struct ContactFinder {
picker: ViewHandle<Picker<ContactFinderDelegate>>, picker: View<Picker<ContactFinderDelegate>>,
has_focus: bool, has_focus: bool,
} }
impl ContactFinder { impl ContactFinder {
pub fn new(user_store: ModelHandle<UserStore>, cx: &mut ViewContext<Self>) -> Self { pub fn new(user_store: Model<UserStore>, cx: &mut ViewContext<Self>) -> Self {
let picker = cx.add_view(|cx| { let delegate = ContactFinderDelegate {
Picker::new( parent: cx.view().downgrade(),
ContactFinderDelegate {
user_store, user_store,
potential_contacts: Arc::from([]), potential_contacts: Arc::from([]),
selected_index: 0, selected_index: 0,
}, };
cx, let picker = cx.build_view(|cx| Picker::new(delegate, cx));
)
.with_theme(|theme| theme.collab_panel.tabbed_modal.picker.clone())
});
cx.subscribe(&picker, |_, _, e, cx| cx.emit(*e)).detach();
Self { Self {
picker, picker,
@ -41,105 +38,72 @@ impl ContactFinder {
pub fn set_query(&mut self, query: String, cx: &mut ViewContext<Self>) { pub fn set_query(&mut self, query: String, cx: &mut ViewContext<Self>) {
self.picker.update(cx, |picker, cx| { self.picker.update(cx, |picker, cx| {
picker.set_query(query, cx); // todo!()
// picker.set_query(query, cx);
}); });
} }
fn dismiss(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
cx.emit(PickerEvent::Dismiss);
}
} }
impl Entity for ContactFinder { impl Render for ContactFinder {
type Event = PickerEvent; fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
fn render_mode_button(text: &'static str) -> AnyElement {
Label::new(text).into_any_element()
} }
impl View for ContactFinder { v_stack()
fn ui_name() -> &'static str { .child(
"ContactFinder" v_stack()
} .child(Label::new("Contacts"))
.child(h_stack().children([render_mode_button("Invite new contacts")]))
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> { .bg(cx.theme().colors().element_background),
let full_theme = &theme::current(cx);
let theme = &full_theme.collab_panel.tabbed_modal;
fn render_mode_button(
text: &'static str,
theme: &theme::TabbedModal,
_cx: &mut ViewContext<ContactFinder>,
) -> AnyElement<ContactFinder> {
let contained_text = &theme.tab_button.active_state().default;
Label::new(text, contained_text.text.clone())
.contained()
.with_style(contained_text.container.clone())
.into_any()
}
Flex::column()
.with_child(
Flex::column()
.with_child(
Label::new("Contacts", theme.title.text.clone())
.contained()
.with_style(theme.title.container.clone()),
) )
.with_child(Flex::row().with_children([render_mode_button( .child(self.picker.clone())
"Invite new contacts", .w_96()
&theme,
cx,
)]))
.expanded()
.contained()
.with_style(theme.header),
)
.with_child(
ChildView::new(&self.picker, cx)
.contained()
.with_style(theme.body),
)
.constrained()
.with_max_height(theme.max_height)
.with_max_width(theme.max_width)
.contained()
.with_style(theme.modal)
.into_any()
} }
fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) { // fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
self.has_focus = true; // self.has_focus = true;
if cx.is_self_focused() { // if cx.is_self_focused() {
cx.focus(&self.picker) // cx.focus(&self.picker)
} // }
// }
// fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) {
// self.has_focus = false;
// }
type Element = Div;
} }
fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) { // impl Modal for ContactFinder {
self.has_focus = false; // fn has_focus(&self) -> bool {
} // self.has_focus
} // }
impl Modal for ContactFinder { // fn dismiss_on_event(event: &Self::Event) -> bool {
fn has_focus(&self) -> bool { // match event {
self.has_focus // PickerEvent::Dismiss => true,
} // }
// }
fn dismiss_on_event(event: &Self::Event) -> bool { // }
match event {
PickerEvent::Dismiss => true,
}
}
}
pub struct ContactFinderDelegate { pub struct ContactFinderDelegate {
parent: WeakView<ContactFinder>,
potential_contacts: Arc<[Arc<User>]>, potential_contacts: Arc<[Arc<User>]>,
user_store: ModelHandle<UserStore>, user_store: Model<UserStore>,
selected_index: usize, selected_index: usize,
} }
impl PickerDelegate for ContactFinderDelegate { impl EventEmitter<DismissEvent> for ContactFinder {}
fn placeholder_text(&self) -> Arc<str> {
"Search collaborator by username...".into() impl FocusableView for ContactFinder {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.picker.focus_handle(cx)
}
} }
impl PickerDelegate for ContactFinderDelegate {
type ListItem = Div;
fn match_count(&self) -> usize { fn match_count(&self) -> usize {
self.potential_contacts.len() self.potential_contacts.len()
} }
@ -152,6 +116,10 @@ impl PickerDelegate for ContactFinderDelegate {
self.selected_index = ix; self.selected_index = ix;
} }
fn placeholder_text(&self) -> Arc<str> {
"Search collaborator by username...".into()
}
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> { fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
let search_users = self let search_users = self
.user_store .user_store
@ -161,7 +129,7 @@ impl PickerDelegate for ContactFinderDelegate {
async { async {
let potential_contacts = search_users.await?; let potential_contacts = search_users.await?;
picker.update(&mut cx, |picker, cx| { picker.update(&mut cx, |picker, cx| {
picker.delegate_mut().potential_contacts = potential_contacts.into(); picker.delegate.potential_contacts = potential_contacts.into();
cx.notify(); cx.notify();
})?; })?;
anyhow::Ok(()) anyhow::Ok(())
@ -191,19 +159,18 @@ impl PickerDelegate for ContactFinderDelegate {
} }
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) { fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
cx.emit(PickerEvent::Dismiss); //cx.emit(PickerEvent::Dismiss);
self.parent
.update(cx, |_, cx| cx.emit(DismissEvent::Dismiss))
.log_err();
} }
fn render_match( fn render_match(
&self, &self,
ix: usize, ix: usize,
mouse_state: &mut MouseState,
selected: bool, selected: bool,
cx: &gpui::AppContext, cx: &mut ViewContext<Picker<Self>>,
) -> AnyElement<Picker<Self>> { ) -> Option<Self::ListItem> {
let full_theme = &theme::current(cx);
let theme = &full_theme.collab_panel.contact_finder;
let tabbed_modal = &full_theme.collab_panel.tabbed_modal;
let user = &self.potential_contacts[ix]; let user = &self.potential_contacts[ix];
let request_status = self.user_store.read(cx).contact_request_status(user); let request_status = self.user_store.read(cx).contact_request_status(user);
@ -214,48 +181,47 @@ impl PickerDelegate for ContactFinderDelegate {
ContactRequestStatus::RequestSent => Some("icons/x.svg"), ContactRequestStatus::RequestSent => Some("icons/x.svg"),
ContactRequestStatus::RequestAccepted => None, ContactRequestStatus::RequestAccepted => None,
}; };
let button_style = if self.user_store.read(cx).is_contact_request_pending(user) { dbg!(icon_path);
&theme.disabled_contact_button Some(
} else { div()
&theme.contact_button .flex_1()
}; .justify_between()
let style = tabbed_modal .children(user.avatar.clone().map(|avatar| img().data(avatar)))
.picker .child(Label::new(user.github_login.clone()))
.item .children(icon_path.map(|icon_path| svg().path(icon_path))),
.in_state(selected)
.style_for(mouse_state);
Flex::row()
.with_children(user.avatar.clone().map(|avatar| {
Image::from_data(avatar)
.with_style(theme.contact_avatar)
.aligned()
.left()
}))
.with_child(
Label::new(user.github_login.clone(), style.label.clone())
.contained()
.with_style(theme.contact_username)
.aligned()
.left(),
) )
.with_children(icon_path.map(|icon_path| { // Flex::row()
Svg::new(icon_path) // .with_children(user.avatar.clone().map(|avatar| {
.with_color(button_style.color) // Image::from_data(avatar)
.constrained() // .with_style(theme.contact_avatar)
.with_width(button_style.icon_width) // .aligned()
.aligned() // .left()
.contained() // }))
.with_style(button_style.container) // .with_child(
.constrained() // Label::new(user.github_login.clone(), style.label.clone())
.with_width(button_style.button_width) // .contained()
.with_height(button_style.button_width) // .with_style(theme.contact_username)
.aligned() // .aligned()
.flex_float() // .left(),
})) // )
.contained() // .with_children(icon_path.map(|icon_path| {
.with_style(style.container) // Svg::new(icon_path)
.constrained() // .with_color(button_style.color)
.with_height(tabbed_modal.row_height) // .constrained()
.into_any() // .with_width(button_style.icon_width)
// .aligned()
// .contained()
// .with_style(button_style.container)
// .constrained()
// .with_width(button_style.button_width)
// .with_height(button_style.button_width)
// .aligned()
// .flex_float()
// }))
// .contained()
// .with_style(style.container)
// .constrained()
// .with_height(tabbed_modal.row_height)
// .into_any()
} }
} }

View file

@ -39,7 +39,7 @@ use project::Project;
use theme::ActiveTheme; use theme::ActiveTheme;
use ui::{h_stack, Avatar, Button, ButtonVariant, Color, IconButton, KeyBinding, Tooltip}; use ui::{h_stack, Avatar, Button, ButtonVariant, Color, IconButton, KeyBinding, Tooltip};
use util::ResultExt; use util::ResultExt;
use workspace::Workspace; use workspace::{notifications::NotifyResultExt, Workspace};
use crate::face_pile::FacePile; use crate::face_pile::FacePile;
@ -290,11 +290,13 @@ impl Render for CollabTitlebarItem {
} else { } else {
this.child(Button::new("Sign in").on_click(move |_, cx| { this.child(Button::new("Sign in").on_click(move |_, cx| {
let client = client.clone(); let client = client.clone();
cx.spawn(move |cx| async move { cx.spawn(move |mut cx| async move {
client.authenticate_and_connect(true, &cx).await?; client
Ok::<(), anyhow::Error>(()) .authenticate_and_connect(true, &cx)
.await
.notify_async_err(&mut cx);
}) })
.detach_and_log_err(cx); .detach();
})) }))
} }
}) })

View file

@ -3,8 +3,8 @@ use gpui::{
}; };
#[derive(Default)] #[derive(Default)]
pub(crate) struct FacePile { pub struct FacePile {
faces: Vec<AnyElement>, pub faces: Vec<AnyElement>,
} }
impl RenderOnce for FacePile { impl RenderOnce for FacePile {

View file

@ -1,17 +1,17 @@
use collections::{CommandPaletteFilter, HashMap};
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
actions, div, prelude::*, Action, AnyElement, AppContext, DismissEvent, Div, EventEmitter,
FocusHandle, FocusableView, Keystroke, ParentElement, Render, Styled, View, ViewContext,
VisualContext, WeakView,
};
use picker::{simple_picker_match, Picker, PickerDelegate};
use std::{ use std::{
cmp::{self, Reverse}, cmp::{self, Reverse},
sync::Arc, sync::Arc,
}; };
use ui::{h_stack, v_stack, HighlightedLabel, KeyBinding}; use collections::{CommandPaletteFilter, HashMap};
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
actions, Action, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView,
Keystroke, ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView,
};
use picker::{Picker, PickerDelegate};
use ui::{h_stack, v_stack, HighlightedLabel, KeyBinding, ListItem};
use util::{ use util::{
channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL}, channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
ResultExt, ResultExt,
@ -81,7 +81,7 @@ impl Render for CommandPalette {
type Element = Div; type Element = Div;
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
v_stack().w_96().child(self.picker.clone()) v_stack().min_w_96().child(self.picker.clone())
} }
} }
@ -141,6 +141,8 @@ impl CommandPaletteDelegate {
} }
impl PickerDelegate for CommandPaletteDelegate { impl PickerDelegate for CommandPaletteDelegate {
type ListItem = ListItem;
fn placeholder_text(&self) -> Arc<str> { fn placeholder_text(&self) -> Arc<str> {
"Execute a command...".into() "Execute a command...".into()
} }
@ -292,24 +294,26 @@ impl PickerDelegate for CommandPaletteDelegate {
ix: usize, ix: usize,
selected: bool, selected: bool,
cx: &mut ViewContext<Picker<Self>>, cx: &mut ViewContext<Picker<Self>>,
) -> AnyElement { ) -> Option<Self::ListItem> {
let Some(r#match) = self.matches.get(ix) else { let Some(r#match) = self.matches.get(ix) else {
return div().into_any(); return None;
}; };
let Some(command) = self.commands.get(r#match.candidate_id) else { let Some(command) = self.commands.get(r#match.candidate_id) else {
return div().into_any(); return None;
}; };
simple_picker_match(selected, cx, |cx| { Some(
ListItem::new(ix).inset(true).selected(selected).child(
h_stack() h_stack()
.w_full()
.justify_between() .justify_between()
.child(HighlightedLabel::new( .child(HighlightedLabel::new(
command.name.clone(), command.name.clone(),
r#match.positions.clone(), r#match.positions.clone(),
)) ))
.children(KeyBinding::for_action(&*command.action, cx)) .children(KeyBinding::for_action(&*command.action, cx)),
.into_any() ),
}) )
} }
} }

View file

@ -1273,6 +1273,13 @@ impl CompletionsMenu {
multiline_docs.map(|div| { multiline_docs.map(|div| {
div.id("multiline_docs") div.id("multiline_docs")
.max_h(max_height) .max_h(max_height)
.flex_1()
.px_1p5()
.py_1()
.min_w(px(260.))
.max_w(px(640.))
.w(px(500.))
.text_ui()
.overflow_y_scroll() .overflow_y_scroll()
// Prevent a mouse down on documentation from being propagated to the editor, // Prevent a mouse down on documentation from being propagated to the editor,
// because that would move the cursor. // because that would move the cursor.
@ -1327,13 +1334,18 @@ impl CompletionsMenu {
div() div()
.id(mat.candidate_id) .id(mat.candidate_id)
.min_w(px(300.)) .min_w(px(220.))
.max_w(px(700.)) .max_w(px(540.))
.whitespace_nowrap() .whitespace_nowrap()
.overflow_hidden() .overflow_hidden()
.bg(gpui::green()) .text_ui()
.hover(|style| style.bg(gpui::blue())) .px_1()
.when(item_ix == selected_item, |div| div.bg(gpui::red())) .rounded(px(4.))
.bg(cx.theme().colors().ghost_element_background)
.hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
.when(item_ix == selected_item, |div| {
div.bg(cx.theme().colors().ghost_element_selected)
})
.on_mouse_down( .on_mouse_down(
MouseButton::Left, MouseButton::Left,
cx.listener(move |editor, event, cx| { cx.listener(move |editor, event, cx| {

View file

@ -22,10 +22,11 @@ use collections::{BTreeMap, HashMap};
use gpui::{ use gpui::{
div, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace, div, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace,
BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element, ElementId, BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element, ElementId,
ElementInputHandler, Entity, EntityId, Hsla, InteractiveElement, IntoElement, LineLayout, ElementInputHandler, Entity, EntityId, Hsla, InteractiveBounds, InteractiveElement,
MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, RenderOnce, IntoElement, LineLayout, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled, ParentElement, Pixels, RenderOnce, ScrollWheelEvent, ShapedLine, SharedString, Size,
TextRun, TextStyle, View, ViewContext, WeakView, WindowContext, WrappedLine, StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, View,
ViewContext, WeakView, WindowContext, WrappedLine,
}; };
use itertools::Itertools; use itertools::Itertools;
use language::language_settings::ShowWhitespaceSetting; use language::language_settings::ShowWhitespaceSetting;
@ -316,6 +317,7 @@ impl EditorElement {
position_map: &PositionMap, position_map: &PositionMap,
text_bounds: Bounds<Pixels>, text_bounds: Bounds<Pixels>,
gutter_bounds: Bounds<Pixels>, gutter_bounds: Bounds<Pixels>,
stacking_order: &StackingOrder,
cx: &mut ViewContext<Editor>, cx: &mut ViewContext<Editor>,
) -> bool { ) -> bool {
let mut click_count = event.click_count; let mut click_count = event.click_count;
@ -326,6 +328,9 @@ impl EditorElement {
} else if !text_bounds.contains_point(&event.position) { } else if !text_bounds.contains_point(&event.position) {
return false; return false;
} }
if !cx.was_top_layer(&event.position, stacking_order) {
return false;
}
let point_for_position = position_map.point_for_position(text_bounds, event.position); let point_for_position = position_map.point_for_position(text_bounds, event.position);
let position = point_for_position.previous_valid; let position = point_for_position.previous_valid;
@ -384,6 +389,7 @@ impl EditorElement {
event: &MouseUpEvent, event: &MouseUpEvent,
position_map: &PositionMap, position_map: &PositionMap,
text_bounds: Bounds<Pixels>, text_bounds: Bounds<Pixels>,
stacking_order: &StackingOrder,
cx: &mut ViewContext<Editor>, cx: &mut ViewContext<Editor>,
) -> bool { ) -> bool {
let end_selection = editor.has_pending_selection(); let end_selection = editor.has_pending_selection();
@ -396,6 +402,7 @@ impl EditorElement {
if !pending_nonempty_selections if !pending_nonempty_selections
&& event.modifiers.command && event.modifiers.command
&& text_bounds.contains_point(&event.position) && text_bounds.contains_point(&event.position)
&& cx.was_top_layer(&event.position, stacking_order)
{ {
let point = position_map.point_for_position(text_bounds, event.position); let point = position_map.point_for_position(text_bounds, event.position);
let could_be_inlay = point.as_valid().is_none(); let could_be_inlay = point.as_valid().is_none();
@ -418,6 +425,7 @@ impl EditorElement {
position_map: &PositionMap, position_map: &PositionMap,
text_bounds: Bounds<Pixels>, text_bounds: Bounds<Pixels>,
gutter_bounds: Bounds<Pixels>, gutter_bounds: Bounds<Pixels>,
stacking_order: &StackingOrder,
cx: &mut ViewContext<Editor>, cx: &mut ViewContext<Editor>,
) -> bool { ) -> bool {
let modifiers = event.modifiers; let modifiers = event.modifiers;
@ -457,10 +465,12 @@ impl EditorElement {
let text_hovered = text_bounds.contains_point(&event.position); let text_hovered = text_bounds.contains_point(&event.position);
let gutter_hovered = gutter_bounds.contains_point(&event.position); let gutter_hovered = gutter_bounds.contains_point(&event.position);
let was_top = cx.was_top_layer(&event.position, stacking_order);
editor.set_gutter_hovered(gutter_hovered, cx); editor.set_gutter_hovered(gutter_hovered, cx);
// Don't trigger hover popover if mouse is hovering over context menu // Don't trigger hover popover if mouse is hovering over context menu
if text_hovered { if text_hovered && was_top {
let point_for_position = position_map.point_for_position(text_bounds, event.position); let point_for_position = position_map.point_for_position(text_bounds, event.position);
match point_for_position.as_valid() { match point_for_position.as_valid() {
@ -490,7 +500,7 @@ impl EditorElement {
} else { } else {
update_go_to_definition_link(editor, None, modifiers.command, modifiers.shift, cx); update_go_to_definition_link(editor, None, modifiers.command, modifiers.shift, cx);
hover_at(editor, None, cx); hover_at(editor, None, cx);
gutter_hovered gutter_hovered && was_top
} }
} }
@ -498,10 +508,10 @@ impl EditorElement {
editor: &mut Editor, editor: &mut Editor,
event: &ScrollWheelEvent, event: &ScrollWheelEvent,
position_map: &PositionMap, position_map: &PositionMap,
bounds: Bounds<Pixels>, bounds: &InteractiveBounds,
cx: &mut ViewContext<Editor>, cx: &mut ViewContext<Editor>,
) -> bool { ) -> bool {
if !bounds.contains_point(&event.position) { if !bounds.visibly_contains(&event.position, cx) {
return false; return false;
} }
@ -2282,10 +2292,15 @@ impl EditorElement {
cx: &mut WindowContext, cx: &mut WindowContext,
) { ) {
let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO); let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
let interactive_bounds = InteractiveBounds {
bounds: bounds.intersect(&cx.content_mask().bounds),
stacking_order: cx.stacking_order().clone(),
};
cx.on_mouse_event({ cx.on_mouse_event({
let position_map = layout.position_map.clone(); let position_map = layout.position_map.clone();
let editor = self.editor.clone(); let editor = self.editor.clone();
let interactive_bounds = interactive_bounds.clone();
move |event: &ScrollWheelEvent, phase, cx| { move |event: &ScrollWheelEvent, phase, cx| {
if phase != DispatchPhase::Bubble { if phase != DispatchPhase::Bubble {
@ -2293,7 +2308,7 @@ impl EditorElement {
} }
let should_cancel = editor.update(cx, |editor, cx| { let should_cancel = editor.update(cx, |editor, cx| {
Self::scroll(editor, event, &position_map, bounds, cx) Self::scroll(editor, event, &position_map, &interactive_bounds, cx)
}); });
if should_cancel { if should_cancel {
cx.stop_propagation(); cx.stop_propagation();
@ -2304,6 +2319,7 @@ impl EditorElement {
cx.on_mouse_event({ cx.on_mouse_event({
let position_map = layout.position_map.clone(); let position_map = layout.position_map.clone();
let editor = self.editor.clone(); let editor = self.editor.clone();
let stacking_order = cx.stacking_order().clone();
move |event: &MouseDownEvent, phase, cx| { move |event: &MouseDownEvent, phase, cx| {
if phase != DispatchPhase::Bubble { if phase != DispatchPhase::Bubble {
@ -2311,7 +2327,15 @@ impl EditorElement {
} }
let should_cancel = editor.update(cx, |editor, cx| { let should_cancel = editor.update(cx, |editor, cx| {
Self::mouse_down(editor, event, &position_map, text_bounds, gutter_bounds, cx) Self::mouse_down(
editor,
event,
&position_map,
text_bounds,
gutter_bounds,
&stacking_order,
cx,
)
}); });
if should_cancel { if should_cancel {
@ -2323,9 +2347,18 @@ impl EditorElement {
cx.on_mouse_event({ cx.on_mouse_event({
let position_map = layout.position_map.clone(); let position_map = layout.position_map.clone();
let editor = self.editor.clone(); let editor = self.editor.clone();
let stacking_order = cx.stacking_order().clone();
move |event: &MouseUpEvent, phase, cx| { move |event: &MouseUpEvent, phase, cx| {
let should_cancel = editor.update(cx, |editor, cx| { let should_cancel = editor.update(cx, |editor, cx| {
Self::mouse_up(editor, event, &position_map, text_bounds, cx) Self::mouse_up(
editor,
event,
&position_map,
text_bounds,
&stacking_order,
cx,
)
}); });
if should_cancel { if should_cancel {
@ -2351,13 +2384,23 @@ impl EditorElement {
cx.on_mouse_event({ cx.on_mouse_event({
let position_map = layout.position_map.clone(); let position_map = layout.position_map.clone();
let editor = self.editor.clone(); let editor = self.editor.clone();
let stacking_order = cx.stacking_order().clone();
move |event: &MouseMoveEvent, phase, cx| { move |event: &MouseMoveEvent, phase, cx| {
if phase != DispatchPhase::Bubble { if phase != DispatchPhase::Bubble {
return; return;
} }
let stop_propogating = editor.update(cx, |editor, cx| { let stop_propogating = editor.update(cx, |editor, cx| {
Self::mouse_moved(editor, event, &position_map, text_bounds, gutter_bounds, cx) Self::mouse_moved(
editor,
event,
&position_map,
text_bounds,
gutter_bounds,
&stacking_order,
cx,
)
}); });
if stop_propogating { if stop_propogating {
@ -2617,9 +2660,11 @@ impl Element for EditorElement {
// We call with_z_index to establish a new stacking context. // We call with_z_index to establish a new stacking context.
cx.with_z_index(0, |cx| { cx.with_z_index(0, |cx| {
cx.with_content_mask(Some(ContentMask { bounds }), |cx| { cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
// Paint mouse listeners first, so any elements we paint on top of the editor // Paint mouse listeners at z-index 0 so any elements we paint on top of the editor
// take precedence. // take precedence.
cx.with_z_index(0, |cx| {
self.paint_mouse_listeners(bounds, gutter_bounds, text_bounds, &layout, cx); self.paint_mouse_listeners(bounds, gutter_bounds, text_bounds, &layout, cx);
});
let input_handler = ElementInputHandler::new(bounds, self.editor.clone(), cx); let input_handler = ElementInputHandler::new(bounds, self.editor.clone(), cx);
cx.handle_input(&focus_handle, input_handler); cx.handle_input(&focus_handle, input_handler);

View file

@ -483,9 +483,6 @@ impl InfoPopover {
// Prevent a mouse move on the popover from being propagated to the editor, // Prevent a mouse move on the popover from being propagated to the editor,
// because that would dismiss the popover. // because that would dismiss the popover.
.on_mouse_move(|_, cx| cx.stop_propagation()) .on_mouse_move(|_, cx| cx.stop_propagation())
// Prevent a mouse down on the popover from being propagated to the editor,
// because that would move the cursor.
.on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
.child(crate::render_parsed_markdown( .child(crate::render_parsed_markdown(
"content", "content",
&self.parsed_content, &self.parsed_content,

View file

@ -2,9 +2,8 @@ use collections::HashMap;
use editor::{scroll::autoscroll::Autoscroll, Bias, Editor}; use editor::{scroll::autoscroll::Autoscroll, Bias, Editor};
use fuzzy::{CharBag, PathMatch, PathMatchCandidate}; use fuzzy::{CharBag, PathMatch, PathMatchCandidate};
use gpui::{ use gpui::{
actions, div, AnyElement, AppContext, DismissEvent, Div, Element, EventEmitter, FocusHandle, actions, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, Model,
FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Render, Styled, Task, ParentElement, Render, Styled, Task, View, ViewContext, VisualContext, WeakView,
View, ViewContext, VisualContext, WeakView,
}; };
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId}; use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId};
@ -16,8 +15,7 @@ use std::{
}, },
}; };
use text::Point; use text::Point;
use theme::ActiveTheme; use ui::{v_stack, HighlightedLabel, ListItem};
use ui::{v_stack, HighlightedLabel, StyledExt};
use util::{paths::PathLikeWithPosition, post_inc, ResultExt}; use util::{paths::PathLikeWithPosition, post_inc, ResultExt};
use workspace::Workspace; use workspace::Workspace;
@ -530,6 +528,8 @@ impl FileFinderDelegate {
} }
impl PickerDelegate for FileFinderDelegate { impl PickerDelegate for FileFinderDelegate {
type ListItem = ListItem;
fn placeholder_text(&self) -> Arc<str> { fn placeholder_text(&self) -> Arc<str> {
"Search project files...".into() "Search project files...".into()
} }
@ -709,31 +709,22 @@ impl PickerDelegate for FileFinderDelegate {
ix: usize, ix: usize,
selected: bool, selected: bool,
cx: &mut ViewContext<Picker<Self>>, cx: &mut ViewContext<Picker<Self>>,
) -> AnyElement { ) -> Option<Self::ListItem> {
let path_match = self let path_match = self
.matches .matches
.get(ix) .get(ix)
.expect("Invalid matches state: no element for index {ix}"); .expect("Invalid matches state: no element for index {ix}");
let theme = cx.theme();
let colors = theme.colors();
let (file_name, file_name_positions, full_path, full_path_positions) = let (file_name, file_name_positions, full_path, full_path_positions) =
self.labels_for_match(path_match, cx, ix); self.labels_for_match(path_match, cx, ix);
div() Some(
.px_1() ListItem::new(ix).inset(true).selected(selected).child(
.text_color(colors.text)
.text_ui()
.bg(colors.ghost_element_background)
.rounded_md()
.when(selected, |this| this.bg(colors.ghost_element_selected))
.hover(|this| this.bg(colors.ghost_element_hover))
.child(
v_stack() v_stack()
.child(HighlightedLabel::new(file_name, file_name_positions)) .child(HighlightedLabel::new(file_name, file_name_positions))
.child(HighlightedLabel::new(full_path, full_path_positions)), .child(HighlightedLabel::new(full_path, full_path_positions)),
),
) )
.into_any()
} }
} }

View file

@ -3,7 +3,8 @@ use crate::{
BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusEvent, FocusHandle, BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusEvent, FocusHandle,
IntoElement, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent, IntoElement, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent,
MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Render, ScrollWheelEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Render, ScrollWheelEvent,
SharedString, Size, Style, StyleRefinement, Styled, Task, View, Visibility, WindowContext, SharedString, Size, StackingOrder, Style, StyleRefinement, Styled, Task, View, Visibility,
WindowContext,
}; };
use collections::HashMap; use collections::HashMap;
use refineable::Refineable; use refineable::Refineable;
@ -84,7 +85,7 @@ pub trait InteractiveElement: Sized + Element {
move |event, bounds, phase, cx| { move |event, bounds, phase, cx| {
if phase == DispatchPhase::Bubble if phase == DispatchPhase::Bubble
&& event.button == button && event.button == button
&& bounds.contains_point(&event.position) && bounds.visibly_contains(&event.position, cx)
{ {
(listener)(event, cx) (listener)(event, cx)
} }
@ -99,7 +100,7 @@ pub trait InteractiveElement: Sized + Element {
) -> Self { ) -> Self {
self.interactivity().mouse_down_listeners.push(Box::new( self.interactivity().mouse_down_listeners.push(Box::new(
move |event, bounds, phase, cx| { move |event, bounds, phase, cx| {
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
(listener)(event, cx) (listener)(event, cx)
} }
}, },
@ -117,7 +118,7 @@ pub trait InteractiveElement: Sized + Element {
.push(Box::new(move |event, bounds, phase, cx| { .push(Box::new(move |event, bounds, phase, cx| {
if phase == DispatchPhase::Bubble if phase == DispatchPhase::Bubble
&& event.button == button && event.button == button
&& bounds.contains_point(&event.position) && bounds.visibly_contains(&event.position, cx)
{ {
(listener)(event, cx) (listener)(event, cx)
} }
@ -132,7 +133,7 @@ pub trait InteractiveElement: Sized + Element {
self.interactivity() self.interactivity()
.mouse_up_listeners .mouse_up_listeners
.push(Box::new(move |event, bounds, phase, cx| { .push(Box::new(move |event, bounds, phase, cx| {
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
(listener)(event, cx) (listener)(event, cx)
} }
})); }));
@ -145,7 +146,8 @@ pub trait InteractiveElement: Sized + Element {
) -> Self { ) -> Self {
self.interactivity().mouse_down_listeners.push(Box::new( self.interactivity().mouse_down_listeners.push(Box::new(
move |event, bounds, phase, cx| { move |event, bounds, phase, cx| {
if phase == DispatchPhase::Capture && !bounds.contains_point(&event.position) { if phase == DispatchPhase::Capture && !bounds.visibly_contains(&event.position, cx)
{
(listener)(event, cx) (listener)(event, cx)
} }
}, },
@ -163,7 +165,7 @@ pub trait InteractiveElement: Sized + Element {
.push(Box::new(move |event, bounds, phase, cx| { .push(Box::new(move |event, bounds, phase, cx| {
if phase == DispatchPhase::Capture if phase == DispatchPhase::Capture
&& event.button == button && event.button == button
&& !bounds.contains_point(&event.position) && !bounds.visibly_contains(&event.position, cx)
{ {
(listener)(event, cx); (listener)(event, cx);
} }
@ -177,7 +179,7 @@ pub trait InteractiveElement: Sized + Element {
) -> Self { ) -> Self {
self.interactivity().mouse_move_listeners.push(Box::new( self.interactivity().mouse_move_listeners.push(Box::new(
move |event, bounds, phase, cx| { move |event, bounds, phase, cx| {
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
(listener)(event, cx); (listener)(event, cx);
} }
}, },
@ -191,7 +193,7 @@ pub trait InteractiveElement: Sized + Element {
) -> Self { ) -> Self {
self.interactivity().scroll_wheel_listeners.push(Box::new( self.interactivity().scroll_wheel_listeners.push(Box::new(
move |event, bounds, phase, cx| { move |event, bounds, phase, cx| {
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
(listener)(event, cx); (listener)(event, cx);
} }
}, },
@ -526,15 +528,15 @@ pub type FocusListeners = SmallVec<[FocusListener; 2]>;
pub type FocusListener = Box<dyn Fn(&FocusHandle, &FocusEvent, &mut WindowContext) + 'static>; pub type FocusListener = Box<dyn Fn(&FocusHandle, &FocusEvent, &mut WindowContext) + 'static>;
pub type MouseDownListener = pub type MouseDownListener =
Box<dyn Fn(&MouseDownEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>; Box<dyn Fn(&MouseDownEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
pub type MouseUpListener = pub type MouseUpListener =
Box<dyn Fn(&MouseUpEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>; Box<dyn Fn(&MouseUpEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
pub type MouseMoveListener = pub type MouseMoveListener =
Box<dyn Fn(&MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>; Box<dyn Fn(&MouseMoveEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
pub type ScrollWheelListener = pub type ScrollWheelListener =
Box<dyn Fn(&ScrollWheelEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>; Box<dyn Fn(&ScrollWheelEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
pub type ClickListener = Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>; pub type ClickListener = Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>;
@ -719,6 +721,18 @@ pub struct Interactivity {
pub tooltip_builder: Option<TooltipBuilder>, pub tooltip_builder: Option<TooltipBuilder>,
} }
#[derive(Clone)]
pub struct InteractiveBounds {
pub bounds: Bounds<Pixels>,
pub stacking_order: StackingOrder,
}
impl InteractiveBounds {
pub fn visibly_contains(&self, point: &Point<Pixels>, cx: &WindowContext) -> bool {
self.bounds.contains_point(point) && cx.was_top_layer(&point, &self.stacking_order)
}
}
impl Interactivity { impl Interactivity {
pub fn layout( pub fn layout(
&mut self, &mut self,
@ -755,34 +769,52 @@ impl Interactivity {
) { ) {
let style = self.compute_style(Some(bounds), element_state, cx); let style = self.compute_style(Some(bounds), element_state, cx);
if style
.background
.as_ref()
.is_some_and(|fill| fill.color().is_some_and(|color| !color.is_transparent()))
{
cx.with_z_index(style.z_index.unwrap_or(0), |cx| cx.add_opaque_layer(bounds))
}
let interactive_bounds = Rc::new(InteractiveBounds {
bounds: bounds.intersect(&cx.content_mask().bounds),
stacking_order: cx.stacking_order().clone(),
});
if let Some(mouse_cursor) = style.mouse_cursor { if let Some(mouse_cursor) = style.mouse_cursor {
let hovered = bounds.contains_point(&cx.mouse_position()); let mouse_position = &cx.mouse_position();
let hovered = interactive_bounds.visibly_contains(mouse_position, cx);
if hovered { if hovered {
cx.set_cursor_style(mouse_cursor); cx.set_cursor_style(mouse_cursor);
} }
} }
for listener in self.mouse_down_listeners.drain(..) { for listener in self.mouse_down_listeners.drain(..) {
let interactive_bounds = interactive_bounds.clone();
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| { cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
listener(event, &bounds, phase, cx); listener(event, &*interactive_bounds, phase, cx);
}) })
} }
for listener in self.mouse_up_listeners.drain(..) { for listener in self.mouse_up_listeners.drain(..) {
let interactive_bounds = interactive_bounds.clone();
cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| { cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
listener(event, &bounds, phase, cx); listener(event, &*interactive_bounds, phase, cx);
}) })
} }
for listener in self.mouse_move_listeners.drain(..) { for listener in self.mouse_move_listeners.drain(..) {
let interactive_bounds = interactive_bounds.clone();
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
listener(event, &bounds, phase, cx); listener(event, &*interactive_bounds, phase, cx);
}) })
} }
for listener in self.scroll_wheel_listeners.drain(..) { for listener in self.scroll_wheel_listeners.drain(..) {
let interactive_bounds = interactive_bounds.clone();
cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| { cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
listener(event, &bounds, phase, cx); listener(event, &*interactive_bounds, phase, cx);
}) })
} }
@ -792,6 +824,7 @@ impl Interactivity {
.and_then(|group_hover| GroupBounds::get(&group_hover.group, cx)); .and_then(|group_hover| GroupBounds::get(&group_hover.group, cx));
if let Some(group_bounds) = hover_group_bounds { if let Some(group_bounds) = hover_group_bounds {
// todo!() needs cx.was_top_layer
let hovered = group_bounds.contains_point(&cx.mouse_position()); let hovered = group_bounds.contains_point(&cx.mouse_position());
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
if phase == DispatchPhase::Capture { if phase == DispatchPhase::Capture {
@ -805,10 +838,11 @@ impl Interactivity {
if self.hover_style.is_some() if self.hover_style.is_some()
|| (cx.active_drag.is_some() && !self.drag_over_styles.is_empty()) || (cx.active_drag.is_some() && !self.drag_over_styles.is_empty())
{ {
let hovered = bounds.contains_point(&cx.mouse_position()); let interactive_bounds = interactive_bounds.clone();
let hovered = interactive_bounds.visibly_contains(&cx.mouse_position(), cx);
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
if phase == DispatchPhase::Capture { if phase == DispatchPhase::Capture {
if bounds.contains_point(&event.position) != hovered { if interactive_bounds.visibly_contains(&event.position, cx) != hovered {
cx.notify(); cx.notify();
} }
} }
@ -817,8 +851,11 @@ impl Interactivity {
if cx.active_drag.is_some() { if cx.active_drag.is_some() {
let drop_listeners = mem::take(&mut self.drop_listeners); let drop_listeners = mem::take(&mut self.drop_listeners);
let interactive_bounds = interactive_bounds.clone();
cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| { cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { if phase == DispatchPhase::Bubble
&& interactive_bounds.visibly_contains(&event.position, &cx)
{
if let Some(drag_state_type) = if let Some(drag_state_type) =
cx.active_drag.as_ref().map(|drag| drag.view.entity_type()) cx.active_drag.as_ref().map(|drag| drag.view.entity_type())
{ {
@ -847,6 +884,7 @@ impl Interactivity {
if let Some(mouse_down) = mouse_down { if let Some(mouse_down) = mouse_down {
if let Some(drag_listener) = drag_listener { if let Some(drag_listener) = drag_listener {
let active_state = element_state.clicked_state.clone(); let active_state = element_state.clicked_state.clone();
let interactive_bounds = interactive_bounds.clone();
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
if cx.active_drag.is_some() { if cx.active_drag.is_some() {
@ -854,7 +892,7 @@ impl Interactivity {
cx.notify(); cx.notify();
} }
} else if phase == DispatchPhase::Bubble } else if phase == DispatchPhase::Bubble
&& bounds.contains_point(&event.position) && interactive_bounds.visibly_contains(&event.position, cx)
&& (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD && (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD
{ {
*active_state.borrow_mut() = ElementClickedState::default(); *active_state.borrow_mut() = ElementClickedState::default();
@ -867,8 +905,11 @@ impl Interactivity {
}); });
} }
let interactive_bounds = interactive_bounds.clone();
cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| { cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { if phase == DispatchPhase::Bubble
&& interactive_bounds.visibly_contains(&event.position, cx)
{
let mouse_click = ClickEvent { let mouse_click = ClickEvent {
down: mouse_down.clone(), down: mouse_down.clone(),
up: event.clone(), up: event.clone(),
@ -881,8 +922,11 @@ impl Interactivity {
cx.notify(); cx.notify();
}); });
} else { } else {
let interactive_bounds = interactive_bounds.clone();
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| { cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { if phase == DispatchPhase::Bubble
&& interactive_bounds.visibly_contains(&event.position, cx)
{
*pending_mouse_down.borrow_mut() = Some(event.clone()); *pending_mouse_down.borrow_mut() = Some(event.clone());
cx.notify(); cx.notify();
} }
@ -893,13 +937,14 @@ impl Interactivity {
if let Some(hover_listener) = self.hover_listener.take() { if let Some(hover_listener) = self.hover_listener.take() {
let was_hovered = element_state.hover_state.clone(); let was_hovered = element_state.hover_state.clone();
let has_mouse_down = element_state.pending_mouse_down.clone(); let has_mouse_down = element_state.pending_mouse_down.clone();
let interactive_bounds = interactive_bounds.clone();
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
if phase != DispatchPhase::Bubble { if phase != DispatchPhase::Bubble {
return; return;
} }
let is_hovered = let is_hovered = interactive_bounds.visibly_contains(&event.position, cx)
bounds.contains_point(&event.position) && has_mouse_down.borrow().is_none(); && has_mouse_down.borrow().is_none();
let mut was_hovered = was_hovered.borrow_mut(); let mut was_hovered = was_hovered.borrow_mut();
if is_hovered != was_hovered.clone() { if is_hovered != was_hovered.clone() {
@ -914,14 +959,15 @@ impl Interactivity {
if let Some(tooltip_builder) = self.tooltip_builder.take() { if let Some(tooltip_builder) = self.tooltip_builder.take() {
let active_tooltip = element_state.active_tooltip.clone(); let active_tooltip = element_state.active_tooltip.clone();
let pending_mouse_down = element_state.pending_mouse_down.clone(); let pending_mouse_down = element_state.pending_mouse_down.clone();
let interactive_bounds = interactive_bounds.clone();
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
if phase != DispatchPhase::Bubble { if phase != DispatchPhase::Bubble {
return; return;
} }
let is_hovered = let is_hovered = interactive_bounds.visibly_contains(&event.position, cx)
bounds.contains_point(&event.position) && pending_mouse_down.borrow().is_none(); && pending_mouse_down.borrow().is_none();
if !is_hovered { if !is_hovered {
active_tooltip.borrow_mut().take(); active_tooltip.borrow_mut().take();
return; return;
@ -979,11 +1025,12 @@ impl Interactivity {
.group_active_style .group_active_style
.as_ref() .as_ref()
.and_then(|group_active| GroupBounds::get(&group_active.group, cx)); .and_then(|group_active| GroupBounds::get(&group_active.group, cx));
let interactive_bounds = interactive_bounds.clone();
cx.on_mouse_event(move |down: &MouseDownEvent, phase, cx| { cx.on_mouse_event(move |down: &MouseDownEvent, phase, cx| {
if phase == DispatchPhase::Bubble { if phase == DispatchPhase::Bubble {
let group = active_group_bounds let group = active_group_bounds
.map_or(false, |bounds| bounds.contains_point(&down.position)); .map_or(false, |bounds| bounds.contains_point(&down.position));
let element = bounds.contains_point(&down.position); let element = interactive_bounds.visibly_contains(&down.position, cx);
if group || element { if group || element {
*active_state.borrow_mut() = ElementClickedState { group, element }; *active_state.borrow_mut() = ElementClickedState { group, element };
cx.notify(); cx.notify();
@ -1000,9 +1047,12 @@ impl Interactivity {
.clone(); .clone();
let line_height = cx.line_height(); let line_height = cx.line_height();
let scroll_max = (content_size - bounds.size).max(&Size::default()); let scroll_max = (content_size - bounds.size).max(&Size::default());
let interactive_bounds = interactive_bounds.clone();
cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| { cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { if phase == DispatchPhase::Bubble
&& interactive_bounds.visibly_contains(&event.position, cx)
{
let mut scroll_offset = scroll_offset.borrow_mut(); let mut scroll_offset = scroll_offset.borrow_mut();
let old_scroll_offset = *scroll_offset; let old_scroll_offset = *scroll_offset;
let delta = event.delta.pixel_delta(line_height); let delta = event.delta.pixel_delta(line_height);
@ -1098,19 +1148,21 @@ impl Interactivity {
} }
} }
} }
// if self.hover_style.is_some() { if self.hover_style.is_some() {
if bounds.contains_point(&mouse_position) { if bounds
// eprintln!("div hovered {bounds:?} {mouse_position:?}"); .intersect(&cx.content_mask().bounds)
.contains_point(&mouse_position)
&& cx.was_top_layer(&mouse_position, cx.stacking_order())
{
style.refine(&self.hover_style); style.refine(&self.hover_style);
} else {
// eprintln!("div NOT hovered {bounds:?} {mouse_position:?}");
} }
// } }
if let Some(drag) = cx.active_drag.take() { if let Some(drag) = cx.active_drag.take() {
for (state_type, group_drag_style) in &self.group_drag_over_styles { for (state_type, group_drag_style) in &self.group_drag_over_styles {
if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) { if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) {
if *state_type == drag.view.entity_type() if *state_type == drag.view.entity_type()
// todo!() needs to handle cx.content_mask() and cx.is_top()
&& group_bounds.contains_point(&mouse_position) && group_bounds.contains_point(&mouse_position)
{ {
style.refine(&group_drag_style.style); style.refine(&group_drag_style.style);
@ -1120,7 +1172,10 @@ impl Interactivity {
for (state_type, drag_over_style) in &self.drag_over_styles { for (state_type, drag_over_style) in &self.drag_over_styles {
if *state_type == drag.view.entity_type() if *state_type == drag.view.entity_type()
&& bounds.contains_point(&mouse_position) && bounds
.intersect(&cx.content_mask().bounds)
.contains_point(&mouse_position)
&& cx.was_top_layer(&mouse_position, cx.stacking_order())
{ {
style.refine(drag_over_style); style.refine(drag_over_style);
} }

View file

@ -39,8 +39,8 @@ use util::ResultExt;
/// A global stacking order, which is created by stacking successive z-index values. /// A global stacking order, which is created by stacking successive z-index values.
/// Each z-index will always be interpreted in the context of its parent z-index. /// Each z-index will always be interpreted in the context of its parent z-index.
#[derive(Deref, DerefMut, Ord, PartialOrd, Eq, PartialEq, Clone, Default)] #[derive(Deref, DerefMut, Ord, PartialOrd, Eq, PartialEq, Clone, Default, Debug)]
pub(crate) struct StackingOrder(pub(crate) SmallVec<[u32; 16]>); pub struct StackingOrder(pub(crate) SmallVec<[u32; 16]>);
/// Represents the two different phases when dispatching events. /// Represents the two different phases when dispatching events.
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq)] #[derive(Default, Copy, Clone, Debug, Eq, PartialEq)]
@ -243,7 +243,8 @@ pub(crate) struct Frame {
pub(crate) dispatch_tree: DispatchTree, pub(crate) dispatch_tree: DispatchTree,
pub(crate) focus_listeners: Vec<AnyFocusListener>, pub(crate) focus_listeners: Vec<AnyFocusListener>,
pub(crate) scene_builder: SceneBuilder, pub(crate) scene_builder: SceneBuilder,
z_index_stack: StackingOrder, pub(crate) depth_map: Vec<(StackingOrder, Bounds<Pixels>)>,
pub(crate) z_index_stack: StackingOrder,
content_mask_stack: Vec<ContentMask<Pixels>>, content_mask_stack: Vec<ContentMask<Pixels>>,
element_offset_stack: Vec<Point<Pixels>>, element_offset_stack: Vec<Point<Pixels>>,
} }
@ -257,6 +258,7 @@ impl Frame {
focus_listeners: Vec::new(), focus_listeners: Vec::new(),
scene_builder: SceneBuilder::default(), scene_builder: SceneBuilder::default(),
z_index_stack: StackingOrder::default(), z_index_stack: StackingOrder::default(),
depth_map: Default::default(),
content_mask_stack: Vec::new(), content_mask_stack: Vec::new(),
element_offset_stack: Vec::new(), element_offset_stack: Vec::new(),
} }
@ -806,6 +808,32 @@ impl<'a> WindowContext<'a> {
result result
} }
/// Called during painting to track which z-index is on top at each pixel position
pub fn add_opaque_layer(&mut self, bounds: Bounds<Pixels>) {
let stacking_order = self.window.current_frame.z_index_stack.clone();
let depth_map = &mut self.window.current_frame.depth_map;
match depth_map.binary_search_by(|(level, _)| stacking_order.cmp(&level)) {
Ok(i) | Err(i) => depth_map.insert(i, (stacking_order, bounds)),
}
}
/// Returns true if the top-most opaque layer painted over this point was part of the
/// same layer as the given stacking order.
pub fn was_top_layer(&self, point: &Point<Pixels>, level: &StackingOrder) -> bool {
for (stack, bounds) in self.window.previous_frame.depth_map.iter() {
if bounds.contains_point(point) {
return level.starts_with(stack) || stack.starts_with(level);
}
}
false
}
/// Called during painting to get the current stacking order.
pub fn stacking_order(&self) -> &StackingOrder {
&self.window.current_frame.z_index_stack
}
/// Paint one or more drop shadows into the scene for the current frame at the current z-index. /// Paint one or more drop shadows into the scene for the current frame at the current z-index.
pub fn paint_shadows( pub fn paint_shadows(
&mut self, &mut self,
@ -1153,6 +1181,7 @@ impl<'a> WindowContext<'a> {
frame.mouse_listeners.values_mut().for_each(Vec::clear); frame.mouse_listeners.values_mut().for_each(Vec::clear);
frame.focus_listeners.clear(); frame.focus_listeners.clear();
frame.dispatch_tree.clear(); frame.dispatch_tree.clear();
frame.depth_map.clear();
} }
/// Dispatch a mouse or keyboard event on the window. /// Dispatch a mouse or keyboard event on the window.

View file

@ -73,6 +73,7 @@ impl RealNodeRuntime {
let npm_file = node_dir.join("bin/npm"); let npm_file = node_dir.join("bin/npm");
let result = Command::new(&node_binary) let result = Command::new(&node_binary)
.env_clear()
.arg(npm_file) .arg(npm_file)
.arg("--version") .arg("--version")
.stdin(Stdio::null()) .stdin(Stdio::null())
@ -149,6 +150,7 @@ impl NodeRuntime for RealNodeRuntime {
} }
let mut command = Command::new(node_binary); let mut command = Command::new(node_binary);
command.env_clear();
command.env("PATH", env_path); command.env("PATH", env_path);
command.arg(npm_file).arg(subcommand); command.arg(npm_file).arg(subcommand);
command.args(["--cache".into(), installation_path.join("cache")]); command.args(["--cache".into(), installation_path.join("cache")]);
@ -200,11 +202,11 @@ impl NodeRuntime for RealNodeRuntime {
&[ &[
name, name,
"--json", "--json",
"-fetch-retry-mintimeout", "--fetch-retry-mintimeout",
"2000", "2000",
"-fetch-retry-maxtimeout", "--fetch-retry-maxtimeout",
"5000", "5000",
"-fetch-timeout", "--fetch-timeout",
"5000", "5000",
], ],
) )
@ -229,11 +231,11 @@ impl NodeRuntime for RealNodeRuntime {
let mut arguments: Vec<_> = packages.iter().map(|p| p.as_str()).collect(); let mut arguments: Vec<_> = packages.iter().map(|p| p.as_str()).collect();
arguments.extend_from_slice(&[ arguments.extend_from_slice(&[
"-fetch-retry-mintimeout", "--fetch-retry-mintimeout",
"2000", "2000",
"-fetch-retry-maxtimeout", "--fetch-retry-maxtimeout",
"5000", "5000",
"-fetch-timeout", "--fetch-timeout",
"5000", "5000",
]); ]);

View file

@ -16,6 +16,7 @@ pub struct Picker<D: PickerDelegate> {
} }
pub trait PickerDelegate: Sized + 'static { pub trait PickerDelegate: Sized + 'static {
type ListItem: IntoElement;
fn match_count(&self) -> usize; fn match_count(&self) -> usize;
fn selected_index(&self) -> usize; fn selected_index(&self) -> usize;
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>); fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>);
@ -31,7 +32,7 @@ pub trait PickerDelegate: Sized + 'static {
ix: usize, ix: usize,
selected: bool, selected: bool,
cx: &mut ViewContext<Picker<Self>>, cx: &mut ViewContext<Picker<Self>>,
) -> AnyElement; ) -> Option<Self::ListItem>;
} }
impl<D: PickerDelegate> FocusableView for Picker<D> { impl<D: PickerDelegate> FocusableView for Picker<D> {
@ -113,7 +114,6 @@ impl<D: PickerDelegate> Picker<D> {
} }
fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) { fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
dbg!("canceling!");
self.delegate.dismissed(cx); self.delegate.dismissed(cx);
} }
@ -229,7 +229,7 @@ impl<D: PickerDelegate> Render for Picker<D> {
) )
}), }),
) )
.child(picker.delegate.render_match( .children(picker.delegate.render_match(
ix, ix,
ix == selected_index, ix == selected_index,
cx, cx,

View file

@ -10,9 +10,8 @@ use anyhow::{anyhow, Result};
use gpui::{ use gpui::{
actions, div, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext, actions, div, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext,
ClipboardItem, Div, EventEmitter, FocusHandle, Focusable, FocusableView, InteractiveElement, ClipboardItem, Div, EventEmitter, FocusHandle, Focusable, FocusableView, InteractiveElement,
IntoElement, Model, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Model, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, Stateful, Styled,
Render, Stateful, StatefulInteractiveElement, Styled, Task, UniformListScrollHandle, View, Task, UniformListScrollHandle, View, ViewContext, VisualContext as _, WeakView, WindowContext,
ViewContext, VisualContext as _, WeakView, WindowContext,
}; };
use menu::{Confirm, SelectNext, SelectPrev}; use menu::{Confirm, SelectNext, SelectPrev};
use project::{ use project::{
@ -30,7 +29,7 @@ use std::{
sync::Arc, sync::Arc,
}; };
use theme::ActiveTheme as _; use theme::ActiveTheme as _;
use ui::{h_stack, v_stack, IconElement, Label}; use ui::{v_stack, IconElement, Label, ListItem};
use unicase::UniCase; use unicase::UniCase;
use util::{maybe, ResultExt, TryFutureExt}; use util::{maybe, ResultExt, TryFutureExt};
use workspace::{ use workspace::{
@ -1335,13 +1334,19 @@ impl ProjectPanel {
} }
} }
fn render_entry_visual_element( fn render_entry(
details: &EntryDetails, &self,
editor: Option<&View<Editor>>, entry_id: ProjectEntryId,
padding: Pixels, details: EntryDetails,
// dragged_entry_destination: &mut Option<Arc<Path>>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Div { ) -> ListItem {
let kind = details.kind;
let settings = ProjectPanelSettings::get_global(cx);
let show_editor = details.is_editing && !details.is_processing; let show_editor = details.is_editing && !details.is_processing;
let is_selected = self
.selection
.map_or(false, |selection| selection.entry_id == entry_id);
let theme = cx.theme(); let theme = cx.theme();
let filename_text_color = details let filename_text_color = details
@ -1354,14 +1359,17 @@ impl ProjectPanel {
}) })
.unwrap_or(theme.status().info); .unwrap_or(theme.status().info);
h_stack() ListItem::new(entry_id.to_proto() as usize)
.indent_level(details.depth)
.indent_step_size(px(settings.indent_size))
.selected(is_selected)
.child(if let Some(icon) = &details.icon { .child(if let Some(icon) = &details.icon {
div().child(IconElement::from_path(icon.to_string())) div().child(IconElement::from_path(icon.to_string()))
} else { } else {
div() div()
}) })
.child( .child(
if let (Some(editor), true) = (editor, show_editor) { if let (Some(editor), true) = (Some(&self.filename_editor), show_editor) {
div().w_full().child(editor.clone()) div().w_full().child(editor.clone())
} else { } else {
div() div()
@ -1370,33 +1378,6 @@ impl ProjectPanel {
} }
.ml_1(), .ml_1(),
) )
.pl(padding)
}
fn render_entry(
&self,
entry_id: ProjectEntryId,
details: EntryDetails,
// dragged_entry_destination: &mut Option<Arc<Path>>,
cx: &mut ViewContext<Self>,
) -> Stateful<Div> {
let kind = details.kind;
let settings = ProjectPanelSettings::get_global(cx);
const INDENT_SIZE: Pixels = px(16.0);
let padding = INDENT_SIZE + details.depth as f32 * px(settings.indent_size);
let show_editor = details.is_editing && !details.is_processing;
let is_selected = self
.selection
.map_or(false, |selection| selection.entry_id == entry_id);
Self::render_entry_visual_element(&details, Some(&self.filename_editor), padding, cx)
.id(entry_id.to_proto() as usize)
.w_full()
.cursor_pointer()
.when(is_selected, |this| {
this.bg(cx.theme().colors().element_selected)
})
.hover(|style| style.bg(cx.theme().colors().element_hover))
.on_click(cx.listener(move |this, event: &gpui::ClickEvent, cx| { .on_click(cx.listener(move |this, event: &gpui::ClickEvent, cx| {
if !show_editor { if !show_editor {
if kind.is_dir() { if kind.is_dir() {
@ -1410,12 +1391,9 @@ impl ProjectPanel {
} }
} }
})) }))
.on_mouse_down( .on_secondary_mouse_down(cx.listener(move |this, event: &MouseDownEvent, cx| {
MouseButton::Right,
cx.listener(move |this, event: &MouseDownEvent, cx| {
this.deploy_context_menu(event.position, entry_id, cx); this.deploy_context_menu(event.position, entry_id, cx);
}), }))
)
// .on_drop::<ProjectEntryId>(|this, event, cx| { // .on_drop::<ProjectEntryId>(|this, event, cx| {
// this.move_entry( // this.move_entry(
// *dragged_entry, // *dragged_entry,

View file

@ -33,7 +33,6 @@ impl Render for FocusStory {
let theme = cx.theme(); let theme = cx.theme();
let color_1 = theme.status().created; let color_1 = theme.status().created;
let color_2 = theme.status().modified; let color_2 = theme.status().modified;
let color_3 = theme.status().deleted;
let color_4 = theme.status().conflict; let color_4 = theme.status().conflict;
let color_5 = theme.status().ignored; let color_5 = theme.status().ignored;
let color_6 = theme.status().renamed; let color_6 = theme.status().renamed;
@ -42,10 +41,10 @@ impl Render for FocusStory {
.id("parent") .id("parent")
.focusable() .focusable()
.key_context("parent") .key_context("parent")
.on_action(cx.listener(|_, action: &ActionA, cx| { .on_action(cx.listener(|_, _action: &ActionA, _cx| {
println!("Action A dispatched on parent"); println!("Action A dispatched on parent");
})) }))
.on_action(cx.listener(|_, action: &ActionB, cx| { .on_action(cx.listener(|_, _action: &ActionB, _cx| {
println!("Action B dispatched on parent"); println!("Action B dispatched on parent");
})) }))
.on_focus(cx.listener(|_, _, _| println!("Parent focused"))) .on_focus(cx.listener(|_, _, _| println!("Parent focused")))
@ -61,7 +60,7 @@ impl Render for FocusStory {
div() div()
.track_focus(&self.child_1_focus) .track_focus(&self.child_1_focus)
.key_context("child-1") .key_context("child-1")
.on_action(cx.listener(|_, action: &ActionB, cx| { .on_action(cx.listener(|_, _action: &ActionB, _cx| {
println!("Action B dispatched on child 1 during"); println!("Action B dispatched on child 1 during");
})) }))
.w_full() .w_full()
@ -83,7 +82,7 @@ impl Render for FocusStory {
div() div()
.track_focus(&self.child_2_focus) .track_focus(&self.child_2_focus)
.key_context("child-2") .key_context("child-2")
.on_action(cx.listener(|_, action: &ActionC, cx| { .on_action(cx.listener(|_, _action: &ActionC, _cx| {
println!("Action C dispatched on child 2"); println!("Action C dispatched on child 2");
})) }))
.w_full() .w_full()

View file

@ -9,7 +9,7 @@ pub struct KitchenSinkStory;
impl KitchenSinkStory { impl KitchenSinkStory {
pub fn view(cx: &mut WindowContext) -> View<Self> { pub fn view(cx: &mut WindowContext) -> View<Self> {
cx.build_view(|cx| Self) cx.build_view(|_cx| Self)
} }
} }

View file

@ -1,11 +1,11 @@
use fuzzy::StringMatchCandidate; use fuzzy::StringMatchCandidate;
use gpui::{ use gpui::{
div, prelude::*, AnyElement, Div, KeyBinding, Render, SharedString, Styled, Task, View, div, prelude::*, Div, KeyBinding, Render, SharedString, Styled, Task, View, WindowContext,
WindowContext,
}; };
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
use std::sync::Arc; use std::sync::Arc;
use theme2::ActiveTheme; use theme2::ActiveTheme;
use ui::{Label, ListItem};
pub struct PickerStory { pub struct PickerStory {
picker: View<Picker<Delegate>>, picker: View<Picker<Delegate>>,
@ -37,6 +37,8 @@ impl Delegate {
} }
impl PickerDelegate for Delegate { impl PickerDelegate for Delegate {
type ListItem = ListItem;
fn match_count(&self) -> usize { fn match_count(&self) -> usize {
self.candidates.len() self.candidates.len()
} }
@ -49,27 +51,20 @@ impl PickerDelegate for Delegate {
&self, &self,
ix: usize, ix: usize,
selected: bool, selected: bool,
cx: &mut gpui::ViewContext<Picker<Self>>, _cx: &mut gpui::ViewContext<Picker<Self>>,
) -> AnyElement { ) -> Option<Self::ListItem> {
let colors = cx.theme().colors();
let Some(candidate_ix) = self.matches.get(ix) else { let Some(candidate_ix) = self.matches.get(ix) else {
return div().into_any(); return None;
}; };
// TASK: Make StringMatchCandidate::string a SharedString // TASK: Make StringMatchCandidate::string a SharedString
let candidate = SharedString::from(self.candidates[*candidate_ix].string.clone()); let candidate = SharedString::from(self.candidates[*candidate_ix].string.clone());
div() Some(
.text_color(colors.text) ListItem::new(ix)
.when(selected, |s| { .inset(true)
s.border_l_10().border_color(colors.terminal_ansi_yellow) .selected(selected)
}) .child(Label::new(candidate)),
.hover(|style| { )
style
.bg(colors.element_active)
.text_color(colors.text_accent)
})
.child(candidate)
.into_any()
} }
fn selected_index(&self) -> usize { fn selected_index(&self) -> usize {
@ -81,7 +76,7 @@ impl PickerDelegate for Delegate {
cx.notify(); cx.notify();
} }
fn confirm(&mut self, secondary: bool, cx: &mut gpui::ViewContext<Picker<Self>>) { fn confirm(&mut self, secondary: bool, _cx: &mut gpui::ViewContext<Picker<Self>>) {
let candidate_ix = self.matches[self.selected_ix]; let candidate_ix = self.matches[self.selected_ix];
let candidate = self.candidates[candidate_ix].string.clone(); let candidate = self.candidates[candidate_ix].string.clone();

View file

@ -6,7 +6,7 @@ pub struct ScrollStory;
impl ScrollStory { impl ScrollStory {
pub fn view(cx: &mut WindowContext) -> View<ScrollStory> { pub fn view(cx: &mut WindowContext) -> View<ScrollStory> {
cx.build_view(|cx| ScrollStory) cx.build_view(|_cx| ScrollStory)
} }
} }

View file

@ -8,7 +8,7 @@ pub struct TextStory;
impl TextStory { impl TextStory {
pub fn view(cx: &mut WindowContext) -> View<Self> { pub fn view(cx: &mut WindowContext) -> View<Self> {
cx.build_view(|cx| Self) cx.build_view(|_cx| Self)
} }
} }
@ -68,7 +68,7 @@ impl Render for TextStory {
cx.text_style().to_run(18), cx.text_style().to_run(18),
]), ]),
) )
.on_click(vec![2..4, 1..3, 7..9], |range_ix, cx| { .on_click(vec![2..4, 1..3, 7..9], |range_ix, _cx| {
println!("Clicked range {range_ix}"); println!("Clicked range {range_ix}");
}) })
) )

View file

@ -9,7 +9,7 @@ pub struct ZIndexStory;
impl Render for ZIndexStory { impl Render for ZIndexStory {
type Element = Div; type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
Story::container().child(Story::title("z-index")).child( Story::container().child(Story::title("z-index")).child(
div() div()
.flex() .flex()
@ -84,7 +84,7 @@ struct ZIndexExample {
impl RenderOnce for ZIndexExample { impl RenderOnce for ZIndexExample {
type Rendered = Div; type Rendered = Div;
fn render(self, cx: &mut WindowContext) -> Self::Rendered { fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
div() div()
.relative() .relative()
.size_full() .size_full()

View file

@ -18,6 +18,7 @@ pub enum ComponentStory {
ContextMenu, ContextMenu,
Focus, Focus,
Icon, Icon,
IconButton,
Input, Input,
Keybinding, Keybinding,
Label, Label,
@ -37,6 +38,7 @@ impl ComponentStory {
Self::ContextMenu => cx.build_view(|_| ui::ContextMenuStory).into(), Self::ContextMenu => cx.build_view(|_| ui::ContextMenuStory).into(),
Self::Focus => FocusStory::view(cx).into(), Self::Focus => FocusStory::view(cx).into(),
Self::Icon => cx.build_view(|_| ui::IconStory).into(), Self::Icon => cx.build_view(|_| ui::IconStory).into(),
Self::IconButton => cx.build_view(|_| ui::IconButtonStory).into(),
Self::Input => cx.build_view(|_| ui::InputStory).into(), Self::Input => cx.build_view(|_| ui::InputStory).into(),
Self::Keybinding => cx.build_view(|_| ui::KeybindingStory).into(), Self::Keybinding => cx.build_view(|_| ui::KeybindingStory).into(),
Self::Label => cx.build_view(|_| ui::LabelStory).into(), Self::Label => cx.build_view(|_| ui::LabelStory).into(),

View file

@ -1,5 +1,3 @@
#![allow(dead_code, unused_variables)]
mod assets; mod assets;
mod stories; mod stories;
mod story_selector; mod story_selector;
@ -70,7 +68,7 @@ fn main() {
language::init(cx); language::init(cx);
editor::init(cx); editor::init(cx);
let window = cx.open_window( let _window = cx.open_window(
WindowOptions { WindowOptions {
bounds: WindowBounds::Fixed(Bounds { bounds: WindowBounds::Fixed(Bounds {
origin: Default::default(), origin: Default::default(),
@ -104,7 +102,7 @@ impl StoryWrapper {
impl Render for StoryWrapper { impl Render for StoryWrapper {
type Element = Div; type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
div() div()
.flex() .flex()
.flex_col() .flex_col()

View file

@ -23,15 +23,15 @@ impl ThemeColors {
surface_background: neutral().light().step_2(), surface_background: neutral().light().step_2(),
background: neutral().light().step_1(), background: neutral().light().step_1(),
element_background: neutral().light().step_3(), element_background: neutral().light().step_3(),
element_hover: neutral().light().step_4(), element_hover: neutral().light_alpha().step_4(),
element_active: neutral().light().step_5(), element_active: neutral().light_alpha().step_5(),
element_selected: neutral().light().step_5(), element_selected: neutral().light_alpha().step_5(),
element_disabled: neutral().light_alpha().step_3(), element_disabled: neutral().light_alpha().step_3(),
drop_target_background: blue().light_alpha().step_2(), drop_target_background: blue().light_alpha().step_2(),
ghost_element_background: system.transparent, ghost_element_background: system.transparent,
ghost_element_hover: neutral().light().step_4(), ghost_element_hover: neutral().light_alpha().step_4(),
ghost_element_active: neutral().light().step_5(), ghost_element_active: neutral().light_alpha().step_5(),
ghost_element_selected: neutral().light().step_5(), ghost_element_selected: neutral().light_alpha().step_5(),
ghost_element_disabled: neutral().light_alpha().step_3(), ghost_element_disabled: neutral().light_alpha().step_3(),
text: yellow().light().step_9(), text: yellow().light().step_9(),
text_muted: neutral().light().step_11(), text_muted: neutral().light().step_11(),
@ -95,15 +95,15 @@ impl ThemeColors {
surface_background: neutral().dark().step_2(), surface_background: neutral().dark().step_2(),
background: neutral().dark().step_1(), background: neutral().dark().step_1(),
element_background: neutral().dark().step_3(), element_background: neutral().dark().step_3(),
element_hover: neutral().dark().step_4(), element_hover: neutral().dark_alpha().step_4(),
element_active: neutral().dark().step_5(), element_active: neutral().dark_alpha().step_5(),
element_selected: neutral().dark().step_5(), element_selected: neutral().dark_alpha().step_5(),
element_disabled: neutral().dark_alpha().step_3(), element_disabled: neutral().dark_alpha().step_3(),
drop_target_background: blue().dark_alpha().step_2(), drop_target_background: blue().dark_alpha().step_2(),
ghost_element_background: system.transparent, ghost_element_background: system.transparent,
ghost_element_hover: neutral().dark().step_4(), ghost_element_hover: neutral().dark_alpha().step_4(),
ghost_element_active: neutral().dark().step_5(), ghost_element_active: neutral().dark_alpha().step_5(),
ghost_element_selected: neutral().dark().step_5(), ghost_element_selected: neutral().dark_alpha().step_5(),
ghost_element_disabled: neutral().dark_alpha().step_3(), ghost_element_disabled: neutral().dark_alpha().step_3(),
text: neutral().dark().step_12(), text: neutral().dark().step_12(),
text_muted: neutral().dark().step_11(), text_muted: neutral().dark().step_11(),

View file

@ -20,7 +20,7 @@ pub fn one_family() -> ThemeFamily {
pub(crate) fn one_dark() -> Theme { pub(crate) fn one_dark() -> Theme {
let bg = hsla(215. / 360., 12. / 100., 15. / 100., 1.); let bg = hsla(215. / 360., 12. / 100., 15. / 100., 1.);
let editor = hsla(220. / 360., 12. / 100., 18. / 100., 1.); let editor = hsla(220. / 360., 12. / 100., 18. / 100., 1.);
let elevated_surface = hsla(220. / 360., 12. / 100., 18. / 100., 1.); let elevated_surface = hsla(225. / 360., 12. / 100., 17. / 100., 1.);
let blue = hsla(207.8 / 360., 81. / 100., 66. / 100., 1.0); let blue = hsla(207.8 / 360., 81. / 100., 66. / 100., 1.0);
let gray = hsla(218.8 / 360., 10. / 100., 40. / 100., 1.0); let gray = hsla(218.8 / 360., 10. / 100., 40. / 100., 1.0);
@ -48,7 +48,7 @@ pub(crate) fn one_dark() -> Theme {
elevated_surface_background: elevated_surface, elevated_surface_background: elevated_surface,
surface_background: bg, surface_background: bg,
background: bg, background: bg,
element_background: elevated_surface, element_background: hsla(223.0 / 360., 13. / 100., 21. / 100., 1.0),
element_hover: hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 1.0), element_hover: hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 1.0),
element_active: hsla(220.0 / 360., 11.8 / 100., 20.0 / 100., 1.0), element_active: hsla(220.0 / 360., 11.8 / 100., 20.0 / 100., 1.0),
element_selected: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0), element_selected: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0),

View file

@ -13,6 +13,7 @@ editor = { package = "editor2", path = "../editor2" }
fuzzy = { package = "fuzzy2", path = "../fuzzy2" } fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
fs = { package = "fs2", path = "../fs2" } fs = { package = "fs2", path = "../fs2" }
gpui = { package = "gpui2", path = "../gpui2" } gpui = { package = "gpui2", path = "../gpui2" }
ui = { package = "ui2", path = "../ui2" }
picker = { package = "picker2", path = "../picker2" } picker = { package = "picker2", path = "../picker2" }
theme = { package = "theme2", path = "../theme2" } theme = { package = "theme2", path = "../theme2" }
settings = { package = "settings2", path = "../settings2" } settings = { package = "settings2", path = "../settings2" }

View file

@ -2,19 +2,16 @@ use feature_flags::FeatureFlagAppExt;
use fs::Fs; use fs::Fs;
use fuzzy::{match_strings, StringMatch, StringMatchCandidate}; use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
use gpui::{ use gpui::{
actions, div, AnyElement, AppContext, DismissEvent, Element, EventEmitter, FocusableView, actions, AppContext, DismissEvent, EventEmitter, FocusableView, ParentElement, Render,
InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, View, SharedString, View, ViewContext, VisualContext, WeakView,
ViewContext, VisualContext, WeakView,
}; };
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
use settings::{update_settings_file, SettingsStore}; use settings::{update_settings_file, SettingsStore};
use std::sync::Arc; use std::sync::Arc;
use theme::{ActiveTheme, Theme, ThemeRegistry, ThemeSettings}; use theme::{ActiveTheme, Theme, ThemeRegistry, ThemeSettings};
use ui::ListItem;
use util::ResultExt; use util::ResultExt;
use workspace::{ use workspace::{ui::HighlightedLabel, Workspace};
ui::{HighlightedLabel, StyledExt},
Workspace,
};
actions!(Toggle, Reload); actions!(Toggle, Reload);
@ -160,6 +157,8 @@ impl ThemeSelectorDelegate {
} }
impl PickerDelegate for ThemeSelectorDelegate { impl PickerDelegate for ThemeSelectorDelegate {
type ListItem = ui::ListItem;
fn placeholder_text(&self) -> Arc<str> { fn placeholder_text(&self) -> Arc<str> {
"Select Theme...".into() "Select Theme...".into()
} }
@ -260,24 +259,18 @@ impl PickerDelegate for ThemeSelectorDelegate {
&self, &self,
ix: usize, ix: usize,
selected: bool, selected: bool,
cx: &mut ViewContext<Picker<Self>>, _cx: &mut ViewContext<Picker<Self>>,
) -> AnyElement { ) -> Option<Self::ListItem> {
let theme = cx.theme();
let colors = theme.colors();
let theme_match = &self.matches[ix]; let theme_match = &self.matches[ix];
div()
.px_1() Some(
.text_color(colors.text) ListItem::new(ix)
.text_ui() .inset(true)
.bg(colors.ghost_element_background) .selected(selected)
.rounded_md()
.when(selected, |this| this.bg(colors.ghost_element_selected))
.hover(|this| this.bg(colors.ghost_element_hover))
.child(HighlightedLabel::new( .child(HighlightedLabel::new(
theme_match.string.clone(), theme_match.string.clone(),
theme_match.positions.clone(), theme_match.positions.clone(),
)) )),
.into_any() )
} }
} }

View file

@ -15,11 +15,11 @@ gpui = { package = "gpui2", path = "../gpui2" }
itertools = { version = "0.11.0", optional = true } itertools = { version = "0.11.0", optional = true }
menu = { package = "menu2", path = "../menu2"} menu = { package = "menu2", path = "../menu2"}
serde.workspace = true serde.workspace = true
settings2 = { path = "../settings2" } settings = { package = "settings2", path = "../settings2" }
smallvec.workspace = true smallvec.workspace = true
story = { path = "../story", optional = true } story = { path = "../story", optional = true }
strum = { version = "0.25.0", features = ["derive"] } strum = { version = "0.25.0", features = ["derive"] }
theme2 = { path = "../theme2" } theme = { package = "theme2", path = "../theme2" }
rand = "0.8" rand = "0.8"
[features] [features]

View file

@ -49,6 +49,12 @@ impl Avatar {
} }
} }
pub fn source(src: ImageSource) -> Self {
Self {
src,
shape: Shape::Circle,
}
}
pub fn shape(mut self, shape: Shape) -> Self { pub fn shape(mut self, shape: Shape) -> Self {
self.shape = shape; self.shape = shape;
self self

View file

@ -1,7 +1,6 @@
use gpui::{div, prelude::*, Div, Element, ElementId, IntoElement, Styled, WindowContext}; use gpui::{div, prelude::*, Div, Element, ElementId, IntoElement, Styled, WindowContext};
use theme2::ActiveTheme; use crate::prelude::*;
use crate::{Color, Icon, IconElement, Selection}; use crate::{Color, Icon, IconElement, Selection};
pub type CheckHandler = Box<dyn Fn(&Selection, &mut WindowContext) + 'static>; pub type CheckHandler = Box<dyn Fn(&Selection, &mut WindowContext) + 'static>;

View file

@ -264,7 +264,7 @@ impl<M: ManagedView> Element for MenuHandle<M> {
let new_menu = (builder)(cx); let new_menu = (builder)(cx);
let menu2 = menu.clone(); let menu2 = menu.clone();
cx.subscribe(&new_menu, move |modal, e, cx| match e { cx.subscribe(&new_menu, move |_modal, e, cx| match e {
&DismissEvent::Dismiss => { &DismissEvent::Dismiss => {
*menu2.borrow_mut() = None; *menu2.borrow_mut() = None;
cx.notify(); cx.notify();

View file

@ -49,17 +49,4 @@ impl Divider {
self.inset = true; self.inset = true;
self self
} }
fn render(self, cx: &mut WindowContext) -> impl Element {
div()
.map(|this| match self.direction {
DividerDirection::Horizontal => {
this.h_px().w_full().when(self.inset, |this| this.mx_1p5())
}
DividerDirection::Vertical => {
this.w_px().h_full().when(self.inset, |this| this.my_1p5())
}
})
.bg(cx.theme().colors().border_variant)
}
} }

View file

@ -14,6 +14,8 @@ pub enum IconSize {
pub enum Icon { pub enum Icon {
Ai, Ai,
ArrowLeft, ArrowLeft,
ArrowUp,
ArrowDown,
ArrowRight, ArrowRight,
ArrowUpRight, ArrowUpRight,
AtSign, AtSign,
@ -61,6 +63,7 @@ pub enum Icon {
Mic, Mic,
MicMute, MicMute,
Plus, Plus,
Public,
Quote, Quote,
Replace, Replace,
ReplaceAll, ReplaceAll,
@ -71,6 +74,11 @@ pub enum Icon {
Terminal, Terminal,
WholeWord, WholeWord,
XCircle, XCircle,
Command,
Control,
Shift,
Option,
Return,
} }
impl Icon { impl Icon {
@ -79,6 +87,8 @@ impl Icon {
Icon::Ai => "icons/ai.svg", Icon::Ai => "icons/ai.svg",
Icon::ArrowLeft => "icons/arrow_left.svg", Icon::ArrowLeft => "icons/arrow_left.svg",
Icon::ArrowRight => "icons/arrow_right.svg", Icon::ArrowRight => "icons/arrow_right.svg",
Icon::ArrowUp => "icons/arrow_up.svg",
Icon::ArrowDown => "icons/arrow_down.svg",
Icon::ArrowUpRight => "icons/arrow_up_right.svg", Icon::ArrowUpRight => "icons/arrow_up_right.svg",
Icon::AtSign => "icons/at-sign.svg", Icon::AtSign => "icons/at-sign.svg",
Icon::AudioOff => "icons/speaker-off.svg", Icon::AudioOff => "icons/speaker-off.svg",
@ -125,6 +135,7 @@ impl Icon {
Icon::Mic => "icons/mic.svg", Icon::Mic => "icons/mic.svg",
Icon::MicMute => "icons/mic-mute.svg", Icon::MicMute => "icons/mic-mute.svg",
Icon::Plus => "icons/plus.svg", Icon::Plus => "icons/plus.svg",
Icon::Public => "icons/public.svg",
Icon::Quote => "icons/quote.svg", Icon::Quote => "icons/quote.svg",
Icon::Replace => "icons/replace.svg", Icon::Replace => "icons/replace.svg",
Icon::ReplaceAll => "icons/replace_all.svg", Icon::ReplaceAll => "icons/replace_all.svg",
@ -135,6 +146,11 @@ impl Icon {
Icon::Terminal => "icons/terminal.svg", Icon::Terminal => "icons/terminal.svg",
Icon::WholeWord => "icons/word_search.svg", Icon::WholeWord => "icons/word_search.svg",
Icon::XCircle => "icons/error.svg", Icon::XCircle => "icons/error.svg",
Icon::Command => "icons/command.svg",
Icon::Control => "icons/control.svg",
Icon::Shift => "icons/shift.svg",
Icon::Option => "icons/option.svg",
Icon::Return => "icons/return.svg",
} }
} }
} }
@ -151,8 +167,8 @@ impl RenderOnce for IconElement {
fn render(self, cx: &mut WindowContext) -> Self::Rendered { fn render(self, cx: &mut WindowContext) -> Self::Rendered {
let svg_size = match self.size { let svg_size = match self.size {
IconSize::Small => rems(0.75), IconSize::Small => rems(14. / 16.),
IconSize::Medium => rems(0.9375), IconSize::Medium => rems(16. / 16.),
}; };
svg() svg()
@ -189,17 +205,4 @@ impl IconElement {
self.size = size; self.size = size;
self self
} }
fn render(self, cx: &mut WindowContext) -> impl Element {
let svg_size = match self.size {
IconSize::Small => rems(0.75),
IconSize::Medium => rems(0.9375),
};
svg()
.size(svg_size)
.flex_none()
.path(self.path)
.text_color(self.color.color(cx))
}
} }

View file

@ -23,15 +23,13 @@ impl RenderOnce for IconButton {
_ => self.color, _ => self.color,
}; };
let (mut bg_color, bg_hover_color, bg_active_color) = match self.variant { let (mut bg_color, bg_active_color) = match self.variant {
ButtonVariant::Filled => ( ButtonVariant::Filled => (
cx.theme().colors().element_background, cx.theme().colors().element_background,
cx.theme().colors().element_hover,
cx.theme().colors().element_active, cx.theme().colors().element_active,
), ),
ButtonVariant::Ghost => ( ButtonVariant::Ghost => (
cx.theme().colors().ghost_element_background, cx.theme().colors().ghost_element_background,
cx.theme().colors().ghost_element_hover,
cx.theme().colors().ghost_element_active, cx.theme().colors().ghost_element_active,
), ),
}; };
@ -67,7 +65,8 @@ impl RenderOnce for IconButton {
} }
} }
button // HACK: Add an additional identified element wrapper to fix tooltips not showing up.
div().id(self.id.clone()).child(button)
} }
} }
@ -124,6 +123,6 @@ impl IconButton {
} }
pub fn action(self, action: Box<dyn Action>) -> Self { pub fn action(self, action: Box<dyn Action>) -> Self {
self.on_click(move |this, cx| cx.dispatch_action(action.boxed_clone())) self.on_click(move |_event, cx| cx.dispatch_action(action.boxed_clone()))
} }
} }

View file

@ -1,5 +1,5 @@
use crate::prelude::*; use crate::{h_stack, prelude::*, Icon, IconElement, IconSize};
use gpui::{Action, Div, IntoElement}; use gpui::{relative, rems, Action, Div, IntoElement, Keystroke};
#[derive(IntoElement, Clone)] #[derive(IntoElement, Clone)]
pub struct KeyBinding { pub struct KeyBinding {
@ -14,19 +14,35 @@ impl RenderOnce for KeyBinding {
type Rendered = Div; type Rendered = Div;
fn render(self, cx: &mut WindowContext) -> Self::Rendered { fn render(self, cx: &mut WindowContext) -> Self::Rendered {
div() h_stack()
.flex() .flex_none()
.gap_2() .gap_2()
.children(self.key_binding.keystrokes().iter().map(|keystroke| { .children(self.key_binding.keystrokes().iter().map(|keystroke| {
div() let key_icon = Self::icon_for_key(&keystroke);
.flex()
.gap_1() h_stack()
.flex_none()
.gap_0p5()
.bg(cx.theme().colors().element_background)
.p_0p5()
.rounded_sm()
.when(keystroke.modifiers.function, |el| el.child(Key::new("fn"))) .when(keystroke.modifiers.function, |el| el.child(Key::new("fn")))
.when(keystroke.modifiers.control, |el| el.child(Key::new("^"))) .when(keystroke.modifiers.control, |el| {
.when(keystroke.modifiers.alt, |el| el.child(Key::new(""))) el.child(KeyIcon::new(Icon::Control))
.when(keystroke.modifiers.command, |el| el.child(Key::new(""))) })
.when(keystroke.modifiers.shift, |el| el.child(Key::new(""))) .when(keystroke.modifiers.alt, |el| {
.child(Key::new(keystroke.key.clone())) el.child(KeyIcon::new(Icon::Option))
})
.when(keystroke.modifiers.command, |el| {
el.child(KeyIcon::new(Icon::Command))
})
.when(keystroke.modifiers.shift, |el| {
el.child(KeyIcon::new(Icon::Shift))
})
.when_some(key_icon, |el, icon| el.child(KeyIcon::new(icon)))
.when(key_icon.is_none(), |el| {
el.child(Key::new(keystroke.key.to_uppercase().clone()))
})
})) }))
} }
} }
@ -39,6 +55,22 @@ impl KeyBinding {
Some(Self::new(key_binding)) Some(Self::new(key_binding))
} }
fn icon_for_key(keystroke: &Keystroke) -> Option<Icon> {
let mut icon: Option<Icon> = None;
if keystroke.key == "left".to_string() {
icon = Some(Icon::ArrowLeft);
} else if keystroke.key == "right".to_string() {
icon = Some(Icon::ArrowRight);
} else if keystroke.key == "up".to_string() {
icon = Some(Icon::ArrowUp);
} else if keystroke.key == "down".to_string() {
icon = Some(Icon::ArrowDown);
}
icon
}
pub fn new(key_binding: gpui::KeyBinding) -> Self { pub fn new(key_binding: gpui::KeyBinding) -> Self {
Self { key_binding } Self { key_binding }
} }
@ -53,13 +85,18 @@ impl RenderOnce for Key {
type Rendered = Div; type Rendered = Div;
fn render(self, cx: &mut WindowContext) -> Self::Rendered { fn render(self, cx: &mut WindowContext) -> Self::Rendered {
let single_char = self.key.len() == 1;
div() div()
.px_2()
.py_0() .py_0()
.rounded_md() .when(single_char, |el| {
.text_ui_sm() el.w(rems(14. / 16.)).flex().flex_none().justify_center()
})
.when(!single_char, |el| el.px_0p5())
.h(rems(14. / 16.))
.text_ui()
.line_height(relative(1.))
.text_color(cx.theme().colors().text) .text_color(cx.theme().colors().text)
.bg(cx.theme().colors().element_background)
.child(self.key.clone()) .child(self.key.clone())
} }
} }
@ -69,3 +106,24 @@ impl Key {
Self { key: key.into() } Self { key: key.into() }
} }
} }
#[derive(IntoElement)]
pub struct KeyIcon {
icon: Icon,
}
impl RenderOnce for KeyIcon {
type Rendered = Div;
fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
div()
.w(rems(14. / 16.))
.child(IconElement::new(self.icon).size(IconSize::Small))
}
}
impl KeyIcon {
pub fn new(icon: Icon) -> Self {
Self { icon }
}
}

View file

@ -1,6 +1,6 @@
use crate::prelude::*; use crate::prelude::*;
use crate::styled_ext::StyledExt; use crate::styled_ext::StyledExt;
use gpui::{relative, Div, Hsla, IntoElement, StyledText, TextRun, WindowContext}; use gpui::{relative, Div, IntoElement, StyledText, TextRun, WindowContext};
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
pub enum LabelSize { pub enum LabelSize {
@ -182,9 +182,3 @@ impl HighlightedLabel {
self self
} }
} }
/// A run of text that receives the same style.
struct Run {
pub text: String,
pub color: Hsla,
}

View file

@ -1,25 +1,19 @@
use gpui::{
div, px, AnyElement, ClickEvent, Div, IntoElement, Stateful, StatefulInteractiveElement,
};
use smallvec::SmallVec;
use std::rc::Rc; use std::rc::Rc;
use gpui::{
div, px, AnyElement, ClickEvent, Div, ImageSource, IntoElement, MouseButton, MouseDownEvent,
Pixels, Stateful, StatefulInteractiveElement,
};
use smallvec::SmallVec;
use crate::{ use crate::{
disclosure_control, h_stack, v_stack, Avatar, Icon, IconElement, IconSize, Label, Toggle, disclosure_control, h_stack, v_stack, Avatar, Icon, IconButton, IconElement, IconSize, Label,
Toggle,
}; };
use crate::{prelude::*, GraphicSlot}; use crate::{prelude::*, GraphicSlot};
#[derive(Clone, Copy, Default, Debug, PartialEq)]
pub enum ListItemVariant {
/// The list item extends to the far left and right of the list.
FullWidth,
#[default]
Inset,
}
pub enum ListHeaderMeta { pub enum ListHeaderMeta {
// TODO: These should be IconButtons Tools(Vec<IconButton>),
Tools(Vec<Icon>),
// TODO: This should be a button // TODO: This should be a button
Button(Label), Button(Label),
Text(Label), Text(Label),
@ -30,8 +24,39 @@ pub struct ListHeader {
label: SharedString, label: SharedString,
left_icon: Option<Icon>, left_icon: Option<Icon>,
meta: Option<ListHeaderMeta>, meta: Option<ListHeaderMeta>,
variant: ListItemVariant,
toggle: Toggle, toggle: Toggle,
inset: bool,
}
impl ListHeader {
pub fn new(label: impl Into<SharedString>) -> Self {
Self {
label: label.into(),
left_icon: None,
meta: None,
inset: false,
toggle: Toggle::NotToggleable,
}
}
pub fn toggle(mut self, toggle: Toggle) -> Self {
self.toggle = toggle;
self
}
pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
self.left_icon = left_icon;
self
}
pub fn right_button(self, button: IconButton) -> Self {
self.meta(Some(ListHeaderMeta::Tools(vec![button])))
}
pub fn meta(mut self, meta: Option<ListHeaderMeta>) -> Self {
self.meta = meta;
self
}
} }
impl RenderOnce for ListHeader { impl RenderOnce for ListHeader {
@ -45,11 +70,7 @@ impl RenderOnce for ListHeader {
h_stack() h_stack()
.gap_2() .gap_2()
.items_center() .items_center()
.children(icons.into_iter().map(|i| { .children(icons.into_iter().map(|i| i.color(Color::Muted))),
IconElement::new(i)
.color(Color::Muted)
.size(IconSize::Small)
})),
), ),
Some(ListHeaderMeta::Button(label)) => div().child(label), Some(ListHeaderMeta::Button(label)) => div().child(label),
Some(ListHeaderMeta::Text(label)) => div().child(label), Some(ListHeaderMeta::Text(label)) => div().child(label),
@ -63,7 +84,7 @@ impl RenderOnce for ListHeader {
.child( .child(
div() div()
.h_5() .h_5()
.when(self.variant == ListItemVariant::Inset, |this| this.px_2()) .when(self.inset, |this| this.px_2())
.flex() .flex()
.flex_1() .flex_1()
.items_center() .items_center()
@ -92,98 +113,11 @@ impl RenderOnce for ListHeader {
} }
} }
impl ListHeader {
pub fn new(label: impl Into<SharedString>) -> Self {
Self {
label: label.into(),
left_icon: None,
meta: None,
variant: ListItemVariant::default(),
toggle: Toggle::NotToggleable,
}
}
pub fn toggle(mut self, toggle: Toggle) -> Self {
self.toggle = toggle;
self
}
pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
self.left_icon = left_icon;
self
}
pub fn meta(mut self, meta: Option<ListHeaderMeta>) -> Self {
self.meta = meta;
self
}
// before_ship!("delete")
// fn render<V: 'static>(self, cx: &mut WindowContext) -> impl Element<V> {
// let disclosure_control = disclosure_control(self.toggle);
// let meta = match self.meta {
// Some(ListHeaderMeta::Tools(icons)) => div().child(
// h_stack()
// .gap_2()
// .items_center()
// .children(icons.into_iter().map(|i| {
// IconElement::new(i)
// .color(TextColor::Muted)
// .size(IconSize::Small)
// })),
// ),
// Some(ListHeaderMeta::Button(label)) => div().child(label),
// Some(ListHeaderMeta::Text(label)) => div().child(label),
// None => div(),
// };
// h_stack()
// .w_full()
// .bg(cx.theme().colors().surface_background)
// // TODO: Add focus state
// // .when(self.state == InteractionState::Focused, |this| {
// // this.border()
// // .border_color(cx.theme().colors().border_focused)
// // })
// .relative()
// .child(
// div()
// .h_5()
// .when(self.variant == ListItemVariant::Inset, |this| this.px_2())
// .flex()
// .flex_1()
// .items_center()
// .justify_between()
// .w_full()
// .gap_1()
// .child(
// h_stack()
// .gap_1()
// .child(
// div()
// .flex()
// .gap_1()
// .items_center()
// .children(self.left_icon.map(|i| {
// IconElement::new(i)
// .color(TextColor::Muted)
// .size(IconSize::Small)
// }))
// .child(Label::new(self.label.clone()).color(TextColor::Muted)),
// )
// .child(disclosure_control),
// )
// .child(meta),
// )
// }
}
#[derive(IntoElement, Clone)] #[derive(IntoElement, Clone)]
pub struct ListSubHeader { pub struct ListSubHeader {
label: SharedString, label: SharedString,
left_icon: Option<Icon>, left_icon: Option<Icon>,
variant: ListItemVariant, inset: bool,
} }
impl ListSubHeader { impl ListSubHeader {
@ -191,7 +125,7 @@ impl ListSubHeader {
Self { Self {
label: label.into(), label: label.into(),
left_icon: None, left_icon: None,
variant: ListItemVariant::default(), inset: false,
} }
} }
@ -204,11 +138,11 @@ impl ListSubHeader {
impl RenderOnce for ListSubHeader { impl RenderOnce for ListSubHeader {
type Rendered = Div; type Rendered = Div;
fn render(self, cx: &mut WindowContext) -> Self::Rendered { fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
h_stack().flex_1().w_full().relative().py_1().child( h_stack().flex_1().w_full().relative().py_1().child(
div() div()
.h_6() .h_6()
.when(self.variant == ListItemVariant::Inset, |this| this.px_2()) .when(self.inset, |this| this.px_2())
.flex() .flex()
.flex_1() .flex_1()
.w_full() .w_full()
@ -231,26 +165,19 @@ impl RenderOnce for ListSubHeader {
} }
} }
#[derive(Default, PartialEq, Copy, Clone)]
pub enum ListEntrySize {
#[default]
Small,
Medium,
}
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct ListItem { pub struct ListItem {
id: ElementId, id: ElementId,
disabled: bool, selected: bool,
// TODO: Reintroduce this // TODO: Reintroduce this
// disclosure_control_style: DisclosureControlVisibility, // disclosure_control_style: DisclosureControlVisibility,
indent_level: u32, indent_level: usize,
indent_step_size: Pixels,
left_slot: Option<GraphicSlot>, left_slot: Option<GraphicSlot>,
overflow: OverflowStyle,
size: ListEntrySize,
toggle: Toggle, toggle: Toggle,
variant: ListItemVariant, inset: bool,
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>, on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
on_secondary_mouse_down: Option<Rc<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
children: SmallVec<[AnyElement; 2]>, children: SmallVec<[AnyElement; 2]>,
} }
@ -258,14 +185,14 @@ impl ListItem {
pub fn new(id: impl Into<ElementId>) -> Self { pub fn new(id: impl Into<ElementId>) -> Self {
Self { Self {
id: id.into(), id: id.into(),
disabled: false, selected: false,
indent_level: 0, indent_level: 0,
indent_step_size: px(12.),
left_slot: None, left_slot: None,
overflow: OverflowStyle::Hidden,
size: ListEntrySize::default(),
toggle: Toggle::NotToggleable, toggle: Toggle::NotToggleable,
variant: ListItemVariant::default(), inset: false,
on_click: Default::default(), on_click: None,
on_secondary_mouse_down: None,
children: SmallVec::new(), children: SmallVec::new(),
} }
} }
@ -275,21 +202,39 @@ impl ListItem {
self self
} }
pub fn variant(mut self, variant: ListItemVariant) -> Self { pub fn on_secondary_mouse_down(
self.variant = variant; mut self,
handler: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
) -> Self {
self.on_secondary_mouse_down = Some(Rc::new(handler));
self self
} }
pub fn indent_level(mut self, indent_level: u32) -> Self { pub fn inset(mut self, inset: bool) -> Self {
self.inset = inset;
self
}
pub fn indent_level(mut self, indent_level: usize) -> Self {
self.indent_level = indent_level; self.indent_level = indent_level;
self self
} }
pub fn indent_step_size(mut self, indent_step_size: Pixels) -> Self {
self.indent_step_size = indent_step_size;
self
}
pub fn toggle(mut self, toggle: Toggle) -> Self { pub fn toggle(mut self, toggle: Toggle) -> Self {
self.toggle = toggle; self.toggle = toggle;
self self
} }
pub fn selected(mut self, selected: bool) -> Self {
self.selected = selected;
self
}
pub fn left_content(mut self, left_content: GraphicSlot) -> Self { pub fn left_content(mut self, left_content: GraphicSlot) -> Self {
self.left_slot = Some(left_content); self.left_slot = Some(left_content);
self self
@ -300,15 +245,10 @@ impl ListItem {
self self
} }
pub fn left_avatar(mut self, left_avatar: impl Into<SharedString>) -> Self { pub fn left_avatar(mut self, left_avatar: impl Into<ImageSource>) -> Self {
self.left_slot = Some(GraphicSlot::Avatar(left_avatar.into())); self.left_slot = Some(GraphicSlot::Avatar(left_avatar.into()));
self self
} }
pub fn size(mut self, size: ListEntrySize) -> Self {
self.size = size;
self
}
} }
impl RenderOnce for ListItem { impl RenderOnce for ListItem {
@ -323,61 +263,64 @@ impl RenderOnce for ListItem {
.color(Color::Muted), .color(Color::Muted),
), ),
), ),
Some(GraphicSlot::Avatar(src)) => Some(h_stack().child(Avatar::uri(src))), Some(GraphicSlot::Avatar(src)) => Some(h_stack().child(Avatar::source(src))),
Some(GraphicSlot::PublicActor(src)) => Some(h_stack().child(Avatar::uri(src))), Some(GraphicSlot::PublicActor(src)) => Some(h_stack().child(Avatar::uri(src))),
None => None, None => None,
}; };
let sized_item = match self.size {
ListEntrySize::Small => div().h_6(),
ListEntrySize::Medium => div().h_7(),
};
div() div()
.id(self.id) .id(self.id)
.relative() .relative()
.hover(|mut style| {
style.background = Some(cx.theme().colors().editor_background.into());
style
})
.on_click({
let on_click = self.on_click.clone();
move |event, cx| {
if let Some(on_click) = &on_click {
(on_click)(event, cx)
}
}
})
// TODO: Add focus state // TODO: Add focus state
// .when(self.state == InteractionState::Focused, |this| { // .when(self.state == InteractionState::Focused, |this| {
// this.border() // this.border()
// .border_color(cx.theme().colors().border_focused) // .border_color(cx.theme().colors().border_focused)
// }) // })
.when(self.inset, |this| this.rounded_md())
.hover(|style| style.bg(cx.theme().colors().ghost_element_hover)) .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
.active(|style| style.bg(cx.theme().colors().ghost_element_active)) .active(|style| style.bg(cx.theme().colors().ghost_element_active))
.when(self.selected, |this| {
this.bg(cx.theme().colors().ghost_element_selected)
})
.when_some(self.on_click.clone(), |this, on_click| {
this.on_click(move |event, cx| {
// HACK: GPUI currently fires `on_click` with any mouse button,
// but we only care about the left button.
if event.down.button == MouseButton::Left {
(on_click)(event, cx)
}
})
})
.when_some(self.on_secondary_mouse_down, |this, on_mouse_down| {
this.on_mouse_down(MouseButton::Right, move |event, cx| {
(on_mouse_down)(event, cx)
})
})
.child( .child(
sized_item
.when(self.variant == ListItemVariant::Inset, |this| this.px_2())
// .ml(rems(0.75 * self.indent_level as f32))
.children((0..self.indent_level).map(|_| {
div() div()
.w(px(4.)) .when(self.inset, |this| this.px_2())
.h_full() .ml(self.indent_level as f32 * self.indent_step_size)
.flex()
.justify_center()
.group_hover("", |style| style.bg(cx.theme().colors().border_focused))
.child(
h_stack()
.child(div().w_px().h_full())
.child(div().w_px().h_full().bg(cx.theme().colors().border)),
)
}))
.flex() .flex()
.gap_1() .gap_1()
.items_center() .items_center()
.relative() .relative()
.child(disclosure_control(self.toggle)) .child(disclosure_control(self.toggle))
.children(left_content) .children(left_content)
.children(self.children), .children(self.children)
// HACK: We need to attach the `on_click` handler to the child element in order to have the click
// event actually fire.
// Once this is fixed in GPUI we can remove this and rely on the `on_click` handler set above on the
// outer `div`.
.id("on_click_hack")
.when_some(self.on_click, |this, on_click| {
this.on_click(move |event, cx| {
// HACK: GPUI currently fires `on_click` with any mouse button,
// but we only care about the left button.
if event.down.button == MouseButton::Left {
(on_click)(event, cx)
}
})
}),
) )
} }
} }
@ -418,7 +361,7 @@ pub struct List {
impl RenderOnce for List { impl RenderOnce for List {
type Rendered = Div; type Rendered = Div;
fn render(self, cx: &mut WindowContext) -> Self::Rendered { fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
let list_content = match (self.children.is_empty(), self.toggle) { let list_content = match (self.children.is_empty(), self.toggle) {
(false, _) => div().children(self.children), (false, _) => div().children(self.children),
(true, Toggle::Toggled(false)) => div(), (true, Toggle::Toggled(false)) => div(),

View file

@ -1,10 +1,11 @@
use gpui::{ use gpui::{
AnyElement, Div, Element, ElementId, IntoElement, ParentElement, RenderOnce, Styled, div, AnyElement, Div, Element, ElementId, IntoElement, ParentElement, RenderOnce, Styled,
WindowContext, WindowContext,
}; };
use smallvec::SmallVec; use smallvec::SmallVec;
use crate::{v_stack, StyledExt}; use crate::prelude::*;
use crate::v_stack;
/// A popover is used to display a menu or show some options. /// A popover is used to display a menu or show some options.
/// ///
@ -43,22 +44,16 @@ impl RenderOnce for Popover {
type Rendered = Div; type Rendered = Div;
fn render(self, cx: &mut WindowContext) -> Self::Rendered { fn render(self, cx: &mut WindowContext) -> Self::Rendered {
v_stack() div()
.relative() .flex()
.elevation_2(cx) .gap_1()
.p_1() .child(v_stack().elevation_2(cx).px_1().children(self.children))
.children(self.children)
.when_some(self.aside, |this, aside| { .when_some(self.aside, |this, aside| {
// TODO: This will statically position the aside to the top right of the popover.
// We should update this to use gpui2::overlay avoid collisions with the window edges.
this.child( this.child(
v_stack() v_stack()
.top_0()
.left_full()
.ml_1()
.absolute()
.elevation_2(cx) .elevation_2(cx)
.p_1() .bg(cx.theme().colors().surface_background)
.px_1()
.child(aside), .child(aside),
) )
}) })

View file

@ -1,4 +1,4 @@
use gpui::SharedString; use gpui::{ImageSource, SharedString};
use crate::Icon; use crate::Icon;
@ -9,6 +9,6 @@ use crate::Icon;
/// Can be filled with a [] /// Can be filled with a []
pub enum GraphicSlot { pub enum GraphicSlot {
Icon(Icon), Icon(Icon),
Avatar(SharedString), Avatar(ImageSource),
PublicActor(SharedString), PublicActor(SharedString),
} }

View file

@ -3,6 +3,7 @@ mod button;
mod checkbox; mod checkbox;
mod context_menu; mod context_menu;
mod icon; mod icon;
mod icon_button;
mod input; mod input;
mod keybinding; mod keybinding;
mod label; mod label;
@ -13,6 +14,7 @@ pub use button::*;
pub use checkbox::*; pub use checkbox::*;
pub use context_menu::*; pub use context_menu::*;
pub use icon::*; pub use icon::*;
pub use icon_button::*;
pub use input::*; pub use input::*;
pub use keybinding::*; pub use keybinding::*;
pub use label::*; pub use label::*;

View file

@ -9,7 +9,7 @@ pub struct AvatarStory;
impl Render for AvatarStory { impl Render for AvatarStory {
type Element = Div; type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
Story::container() Story::container()
.child(Story::title_for::<Avatar>()) .child(Story::title_for::<Avatar>())
.child(Story::label("Default")) .child(Story::label("Default"))

View file

@ -10,7 +10,7 @@ pub struct ButtonStory;
impl Render for ButtonStory { impl Render for ButtonStory {
type Element = Div; type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
let states = InteractionState::iter(); let states = InteractionState::iter();
Story::container() Story::container()
@ -139,7 +139,7 @@ impl Render for ButtonStory {
.child( .child(
Button::new("Label") Button::new("Label")
.variant(ButtonVariant::Ghost) .variant(ButtonVariant::Ghost)
.on_click(|_, cx| println!("Button clicked.")), .on_click(|_, _cx| println!("Button clicked.")),
) )
} }
} }

View file

@ -10,11 +10,11 @@ fn build_menu(cx: &mut WindowContext, header: impl Into<SharedString>) -> View<C
ContextMenu::build(cx, |menu, _| { ContextMenu::build(cx, |menu, _| {
menu.header(header) menu.header(header)
.separator() .separator()
.entry("Print current time", |v, cx| { .entry("Print current time", |_event, cx| {
println!("dispatching PrintCurrentTime action"); println!("dispatching PrintCurrentTime action");
cx.dispatch_action(PrintCurrentDate.boxed_clone()) cx.dispatch_action(PrintCurrentDate.boxed_clone())
}) })
.entry("Print best foot", |v, cx| { .entry("Print best foot", |_event, cx| {
cx.dispatch_action(PrintBestFood.boxed_clone()) cx.dispatch_action(PrintBestFood.boxed_clone())
}) })
}) })
@ -25,7 +25,7 @@ pub struct ContextMenuStory;
impl Render for ContextMenuStory { impl Render for ContextMenuStory {
type Element = Div; type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
Story::container() Story::container()
.on_action(|_: &PrintCurrentDate, _| { .on_action(|_: &PrintCurrentDate, _| {
println!("printing unix time!"); println!("printing unix time!");

View file

@ -10,7 +10,7 @@ pub struct IconStory;
impl Render for IconStory { impl Render for IconStory {
type Element = Div; type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
let icons = Icon::iter(); let icons = Icon::iter();
Story::container() Story::container()

View file

@ -0,0 +1,35 @@
use gpui::{Div, Render};
use story::Story;
use crate::{prelude::*, Tooltip};
use crate::{Icon, IconButton};
pub struct IconButtonStory;
impl Render for IconButtonStory {
type Element = Div;
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
Story::container()
.child(Story::title_for::<IconButton>())
.child(Story::label("Default"))
.child(div().w_8().child(IconButton::new("icon_a", Icon::Hash)))
.child(Story::label("With `on_click`"))
.child(
div()
.w_8()
.child(
IconButton::new("with_on_click", Icon::Ai).on_click(|_event, _cx| {
println!("Clicked!");
}),
),
)
.child(Story::label("With `tooltip`"))
.child(
div().w_8().child(
IconButton::new("with_tooltip", Icon::MessageBubbles)
.tooltip(|cx| Tooltip::text("Open messages", cx)),
),
)
}
}

View file

@ -9,7 +9,7 @@ pub struct InputStory;
impl Render for InputStory { impl Render for InputStory {
type Element = Div; type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
Story::container() Story::container()
.child(Story::title_for::<Input>()) .child(Story::title_for::<Input>())
.child(Story::label("Default")) .child(Story::label("Default"))

View file

@ -16,7 +16,7 @@ pub fn binding(key: &str) -> gpui::KeyBinding {
impl Render for KeybindingStory { impl Render for KeybindingStory {
type Element = Div; type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
let all_modifier_permutations = ["ctrl", "alt", "cmd", "shift"].into_iter().permutations(2); let all_modifier_permutations = ["ctrl", "alt", "cmd", "shift"].into_iter().permutations(2);
Story::container() Story::container()

View file

@ -9,7 +9,7 @@ pub struct LabelStory;
impl Render for LabelStory { impl Render for LabelStory {
type Element = Div; type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
Story::container() Story::container()
.child(Story::title_for::<Label>()) .child(Story::title_for::<Label>())
.child(Story::label("Default")) .child(Story::label("Default"))

View file

@ -9,7 +9,7 @@ pub struct ListItemStory;
impl Render for ListItemStory { impl Render for ListItemStory {
type Element = Div; type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
Story::container() Story::container()
.child(Story::title_for::<ListItem>()) .child(Story::title_for::<ListItem>())
.child(Story::label("Default")) .child(Story::label("Default"))
@ -22,5 +22,13 @@ impl Render for ListItemStory {
println!("Clicked!"); println!("Clicked!");
}), }),
) )
.child(Story::label("With `on_secondary_mouse_down`"))
.child(
ListItem::new("with_on_secondary_mouse_down").on_secondary_mouse_down(
|_event, _cx| {
println!("Right mouse down!");
},
),
)
} }
} }

View file

@ -1,6 +1,6 @@
use gpui::{overlay, Action, AnyView, IntoElement, Overlay, Render, VisualContext}; use gpui::{overlay, Action, AnyView, IntoElement, Overlay, Render, VisualContext};
use settings2::Settings; use settings::Settings;
use theme2::{ActiveTheme, ThemeSettings}; use theme::ThemeSettings;
use crate::prelude::*; use crate::prelude::*;
use crate::{h_stack, v_stack, Color, KeyBinding, Label, LabelSize, StyledExt}; use crate::{h_stack, v_stack, Color, KeyBinding, Label, LabelSize, StyledExt};
@ -13,7 +13,7 @@ pub struct Tooltip {
impl Tooltip { impl Tooltip {
pub fn text(title: impl Into<SharedString>, cx: &mut WindowContext) -> AnyView { pub fn text(title: impl Into<SharedString>, cx: &mut WindowContext) -> AnyView {
cx.build_view(|cx| Self { cx.build_view(|_cx| Self {
title: title.into(), title: title.into(),
meta: None, meta: None,
key_binding: None, key_binding: None,

View file

@ -5,7 +5,7 @@ pub use gpui::{
pub use crate::StyledExt; pub use crate::StyledExt;
pub use crate::{ButtonVariant, Color}; pub use crate::{ButtonVariant, Color};
pub use theme2::ActiveTheme; pub use theme::ActiveTheme;
use strum::EnumIter; use strum::EnumIter;
@ -16,12 +16,6 @@ pub enum IconSide {
Right, Right,
} }
#[derive(Debug, PartialEq, Eq, Clone, Copy, EnumIter)]
pub enum OverflowStyle {
Hidden,
Wrap,
}
#[derive(Default, PartialEq, Copy, Clone, EnumIter, strum::Display)] #[derive(Default, PartialEq, Copy, Clone, EnumIter, strum::Display)]
pub enum InteractionState { pub enum InteractionState {
/// An element that is enabled and not hovered, active, focused, or disabled. /// An element that is enabled and not hovered, active, focused, or disabled.

View file

@ -1,12 +1,12 @@
use gpui::{Styled, WindowContext}; use gpui::{px, Styled, WindowContext};
use theme2::ActiveTheme;
use crate::prelude::*;
use crate::{ElevationIndex, UITextSize}; use crate::{ElevationIndex, UITextSize};
fn elevated<E: Styled>(this: E, cx: &mut WindowContext, index: ElevationIndex) -> E { fn elevated<E: Styled>(this: E, cx: &mut WindowContext, index: ElevationIndex) -> E {
this.bg(cx.theme().colors().elevated_surface_background) this.bg(cx.theme().colors().elevated_surface_background)
.z_index(index.z_index()) .z_index(index.z_index())
.rounded_lg() .rounded(px(8.))
.border() .border()
.border_color(cx.theme().colors().border_variant) .border_color(cx.theme().colors().border_variant)
.shadow(index.shadow()) .shadow(index.shadow())

View file

@ -1,5 +1,5 @@
use gpui::{Hsla, WindowContext}; use gpui::{Hsla, WindowContext};
use theme2::ActiveTheme; use theme::ActiveTheme;
#[derive(Default, PartialEq, Copy, Clone)] #[derive(Default, PartialEq, Copy, Clone)]
pub enum Color { pub enum Color {

View file

@ -11,8 +11,6 @@
#![doc = include_str!("../docs/hello-world.md")] #![doc = include_str!("../docs/hello-world.md")]
#![doc = include_str!("../docs/building-ui.md")] #![doc = include_str!("../docs/building-ui.md")]
#![doc = include_str!("../docs/todo.md")] #![doc = include_str!("../docs/todo.md")]
// TODO: Fix warnings instead of supressing.
#![allow(dead_code, unused_variables)]
mod components; mod components;
pub mod prelude; pub mod prelude;

View file

@ -16,55 +16,54 @@ fn distance_in_seconds(date: NaiveDateTime, base_date: NaiveDateTime) -> i64 {
fn distance_string(distance: i64, include_seconds: bool, add_suffix: bool) -> String { fn distance_string(distance: i64, include_seconds: bool, add_suffix: bool) -> String {
let suffix = if distance < 0 { " from now" } else { " ago" }; let suffix = if distance < 0 { " from now" } else { " ago" };
let d = distance.abs(); let distance = distance.abs();
let minutes = d / 60; let minutes = distance / 60;
let hours = d / 3600; let hours = distance / 3_600;
let days = d / 86400; let days = distance / 86_400;
let months = d / 2592000; let months = distance / 2_592_000;
let years = d / 31536000;
let string = if d < 5 && include_seconds { let string = if distance < 5 && include_seconds {
"less than 5 seconds".to_string() "less than 5 seconds".to_string()
} else if d < 10 && include_seconds { } else if distance < 10 && include_seconds {
"less than 10 seconds".to_string() "less than 10 seconds".to_string()
} else if d < 20 && include_seconds { } else if distance < 20 && include_seconds {
"less than 20 seconds".to_string() "less than 20 seconds".to_string()
} else if d < 40 && include_seconds { } else if distance < 40 && include_seconds {
"half a minute".to_string() "half a minute".to_string()
} else if d < 60 && include_seconds { } else if distance < 60 && include_seconds {
"less than a minute".to_string() "less than a minute".to_string()
} else if d < 90 && include_seconds { } else if distance < 90 && include_seconds {
"1 minute".to_string() "1 minute".to_string()
} else if d < 30 { } else if distance < 30 {
"less than a minute".to_string() "less than a minute".to_string()
} else if d < 90 { } else if distance < 90 {
"1 minute".to_string() "1 minute".to_string()
} else if d < 2700 { } else if distance < 2_700 {
format!("{} minutes", minutes) format!("{} minutes", minutes)
} else if d < 5400 { } else if distance < 5_400 {
"about 1 hour".to_string() "about 1 hour".to_string()
} else if d < 86400 { } else if distance < 86_400 {
format!("about {} hours", hours) format!("about {} hours", hours)
} else if d < 172800 { } else if distance < 172_800 {
"1 day".to_string() "1 day".to_string()
} else if d < 2592000 { } else if distance < 2_592_000 {
format!("{} days", days) format!("{} days", days)
} else if d < 5184000 { } else if distance < 5_184_000 {
"about 1 month".to_string() "about 1 month".to_string()
} else if d < 7776000 { } else if distance < 7_776_000 {
"about 2 months".to_string() "about 2 months".to_string()
} else if d < 31540000 { } else if distance < 31_540_000 {
format!("{} months", months) format!("{} months", months)
} else if d < 39425000 { } else if distance < 39_425_000 {
"about 1 year".to_string() "about 1 year".to_string()
} else if d < 55195000 { } else if distance < 55_195_000 {
"over 1 year".to_string() "over 1 year".to_string()
} else if d < 63080000 { } else if distance < 63_080_000 {
"almost 2 years".to_string() "almost 2 years".to_string()
} else { } else {
let years = d / 31536000; let years = distance / 31_536_000;
let remaining_months = (d % 31536000) / 2592000; let remaining_months = (distance % 31_536_000) / 2_592_000;
if remaining_months < 3 { if remaining_months < 3 {
format!("about {} years", years) format!("about {} years", years)
@ -76,7 +75,7 @@ fn distance_string(distance: i64, include_seconds: bool, add_suffix: bool) -> St
}; };
if add_suffix { if add_suffix {
return format!("{}{}", string, suffix); format!("{}{}", string, suffix)
} else { } else {
string string
} }

View file

@ -16,6 +16,7 @@ editor = { package = "editor2", path = "../editor2" }
fs = { package = "fs2", path = "../fs2" } fs = { package = "fs2", path = "../fs2" }
fuzzy = { package = "fuzzy2", path = "../fuzzy2" } fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
gpui = { package = "gpui2", path = "../gpui2" } gpui = { package = "gpui2", path = "../gpui2" }
ui = { package = "ui2", path = "../ui2" }
db = { package = "db2", path = "../db2" } db = { package = "db2", path = "../db2" }
install_cli = { package = "install_cli2", path = "../install_cli2" } install_cli = { package = "install_cli2", path = "../install_cli2" }
project = { package = "project2", path = "../project2" } project = { package = "project2", path = "../project2" }

View file

@ -1,13 +1,14 @@
use super::base_keymap_setting::BaseKeymap; use super::base_keymap_setting::BaseKeymap;
use fuzzy::{match_strings, StringMatch, StringMatchCandidate}; use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
use gpui::{ use gpui::{
actions, AppContext, DismissEvent, EventEmitter, FocusableView, IntoElement, Render, Task, actions, AppContext, DismissEvent, EventEmitter, FocusableView, ParentElement, Render, Task,
View, ViewContext, VisualContext, WeakView, View, ViewContext, VisualContext, WeakView,
}; };
use picker::{simple_picker_match, Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
use project::Fs; use project::Fs;
use settings::{update_settings_file, Settings}; use settings::{update_settings_file, Settings};
use std::sync::Arc; use std::sync::Arc;
use ui::ListItem;
use util::ResultExt; use util::ResultExt;
use workspace::{ui::HighlightedLabel, Workspace}; use workspace::{ui::HighlightedLabel, Workspace};
@ -97,6 +98,8 @@ impl BaseKeymapSelectorDelegate {
} }
impl PickerDelegate for BaseKeymapSelectorDelegate { impl PickerDelegate for BaseKeymapSelectorDelegate {
type ListItem = ui::ListItem;
fn placeholder_text(&self) -> Arc<str> { fn placeholder_text(&self) -> Arc<str> {
"Select a base keymap...".into() "Select a base keymap...".into()
} }
@ -188,13 +191,18 @@ impl PickerDelegate for BaseKeymapSelectorDelegate {
&self, &self,
ix: usize, ix: usize,
selected: bool, selected: bool,
cx: &mut gpui::ViewContext<Picker<Self>>, _cx: &mut gpui::ViewContext<Picker<Self>>,
) -> gpui::AnyElement { ) -> Option<Self::ListItem> {
let keymap_match = &self.matches[ix]; let keymap_match = &self.matches[ix];
simple_picker_match(selected, cx, |_cx| { Some(
HighlightedLabel::new(keymap_match.string.clone(), keymap_match.positions.clone()) ListItem::new(ix)
.into_any_element() .selected(selected)
}) .inset(true)
.child(HighlightedLabel::new(
keymap_match.string.clone(),
keymap_match.positions.clone(),
)),
)
} }
} }

View file

@ -95,10 +95,6 @@ impl Render for ModalLayer {
.track_focus(&active_modal.focus_handle) .track_focus(&active_modal.focus_handle)
.child( .child(
h_stack() h_stack()
// needed to prevent mouse events leaking to the
// UI below. // todo! for gpui3.
.on_any_mouse_down(|_, cx| cx.stop_propagation())
.on_any_mouse_up(|_, cx| cx.stop_propagation())
.on_mouse_down_out(cx.listener(|this, _, cx| { .on_mouse_down_out(cx.listener(|this, _, cx| {
this.hide_modal(cx); this.hide_modal(cx);
})) }))

View file

@ -2640,12 +2640,11 @@ impl Workspace {
.flex_col() .flex_col()
.justify_end() .justify_end()
.gap_2() .gap_2()
.children(self.notifications.iter().map(|(_, _, notification)| { .children(
div() self.notifications
.on_any_mouse_down(|_, cx| cx.stop_propagation()) .iter()
.on_any_mouse_up(|_, cx| cx.stop_propagation()) .map(|(_, _, notification)| notification.to_any()),
.child(notification.to_any()) ),
})),
) )
} }
} }

View file

@ -140,6 +140,7 @@ tree-sitter-lua.workspace = true
tree-sitter-nix.workspace = true tree-sitter-nix.workspace = true
tree-sitter-nu.workspace = true tree-sitter-nu.workspace = true
tree-sitter-vue.workspace = true tree-sitter-vue.workspace = true
tree-sitter-uiua.workspace = true
url = "2.2" url = "2.2"
urlencoding = "2.1.2" urlencoding = "2.1.2"

View file

@ -17,6 +17,7 @@ mod json;
#[cfg(feature = "plugin_runtime")] #[cfg(feature = "plugin_runtime")]
mod language_plugin; mod language_plugin;
mod lua; mod lua;
mod nu;
mod php; mod php;
mod python; mod python;
mod ruby; mod ruby;
@ -24,6 +25,7 @@ mod rust;
mod svelte; mod svelte;
mod tailwind; mod tailwind;
mod typescript; mod typescript;
mod uiua;
mod vue; mod vue;
mod yaml; mod yaml;
@ -210,12 +212,21 @@ pub fn init(
language("elm", tree_sitter_elm::language(), vec![]); language("elm", tree_sitter_elm::language(), vec![]);
language("glsl", tree_sitter_glsl::language(), vec![]); language("glsl", tree_sitter_glsl::language(), vec![]);
language("nix", tree_sitter_nix::language(), vec![]); language("nix", tree_sitter_nix::language(), vec![]);
language("nu", tree_sitter_nu::language(), vec![]); language(
"nu",
tree_sitter_nu::language(),
vec![Arc::new(nu::NuLanguageServer {})],
);
language( language(
"vue", "vue",
tree_sitter_vue::language(), tree_sitter_vue::language(),
vec![Arc::new(vue::VueLspAdapter::new(node_runtime))], vec![Arc::new(vue::VueLspAdapter::new(node_runtime))],
); );
language(
"uiua",
tree_sitter_uiua::language(),
vec![Arc::new(uiua::UiuaLanguageServer {})],
);
} }
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]

View file

@ -0,0 +1,81 @@
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use language::{CodeLabel, Language, LanguageServerName, LspAdapter, LspAdapterDelegate};
use lsp::LanguageServerBinary;
use std::{any::Any, path::PathBuf, sync::Arc};
pub struct NuLanguageServer;
#[async_trait]
impl LspAdapter for NuLanguageServer {
async fn name(&self) -> LanguageServerName {
LanguageServerName("nu".into())
}
fn short_name(&self) -> &'static str {
"nu"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Any + Send>> {
Ok(Box::new(()))
}
async fn fetch_server_binary(
&self,
_version: Box<dyn 'static + Send + Any>,
_container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
Err(anyhow!(
"nu v0.87.0 or greater must be installed and available in your $PATH"
))
}
async fn cached_server_binary(
&self,
_: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
Some(LanguageServerBinary {
path: "nu".into(),
arguments: vec!["--lsp".into()],
})
}
fn can_be_reinstalled(&self) -> bool {
false
}
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
None
}
async fn label_for_completion(
&self,
completion: &lsp::CompletionItem,
language: &Arc<Language>,
) -> Option<CodeLabel> {
return Some(CodeLabel {
runs: language
.highlight_text(&completion.label.clone().into(), 0..completion.label.len()),
text: completion.label.clone(),
filter_range: 0..completion.label.len(),
});
}
async fn label_for_symbol(
&self,
name: &str,
_: lsp::SymbolKind,
language: &Arc<Language>,
) -> Option<CodeLabel> {
Some(CodeLabel {
runs: language.highlight_text(&name.into(), 0..name.len()),
text: name.to_string(),
filter_range: 0..name.len(),
})
}
}

View file

@ -0,0 +1,55 @@
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
use lsp::LanguageServerBinary;
use std::{any::Any, path::PathBuf};
pub struct UiuaLanguageServer;
#[async_trait]
impl LspAdapter for UiuaLanguageServer {
async fn name(&self) -> LanguageServerName {
LanguageServerName("uiua".into())
}
fn short_name(&self) -> &'static str {
"uiua"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Any + Send>> {
Ok(Box::new(()))
}
async fn fetch_server_binary(
&self,
_version: Box<dyn 'static + Send + Any>,
_container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
Err(anyhow!(
"uiua must be installed and available in your $PATH"
))
}
async fn cached_server_binary(
&self,
_: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
Some(LanguageServerBinary {
path: "uiua".into(),
arguments: vec!["lsp".into()],
})
}
fn can_be_reinstalled(&self) -> bool {
false
}
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
None
}
}

View file

@ -0,0 +1,10 @@
name = "Uiua"
path_suffixes = ["ua"]
line_comment = "# "
autoclose_before = ")]}\""
brackets = [
{ start = "{", end = "}", close = true, newline = false },
{ start = "[", end = "]", close = true, newline = false },
{ start = "(", end = ")", close = true, newline = false },
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
]

View file

@ -0,0 +1,50 @@
[
(openParen)
(closeParen)
(openCurly)
(closeCurly)
(openBracket)
(closeBracket)
] @punctuation.bracket
[
(branchSeparator)
(underscore)
] @constructor
; ] @punctuation.delimiter
[ (character) ] @constant.character
[ (comment) ] @comment
[ (constant) ] @constant.numeric
[ (identifier) ] @variable
[ (leftArrow) ] @keyword
[ (function) ] @function
[ (modifier1) ] @operator
[ (modifier2) ] @operator
[ (number) ] @constant.numeric
[ (placeHolder) ] @special
[ (otherConstant) ] @string.special
[ (signature) ] @type
[ (system) ] @function.builtin
[ (tripleMinus) ] @module
; planet
[
"id"
"identity"
"∘"
"dip"
"⊙"
"gap"
"⋅"
] @tag
[
(string)
(multiLineString)
] @string
; [
; (deprecated)
; (identifierDeprecated)
; ] @warning

View file

@ -0,0 +1,3 @@
[
(array)
] @indent

View file

@ -21,7 +21,7 @@ audio = { package = "audio2", path = "../audio2" }
auto_update = { package = "auto_update2", path = "../auto_update2" } auto_update = { package = "auto_update2", path = "../auto_update2" }
# breadcrumbs = { path = "../breadcrumbs" } # breadcrumbs = { path = "../breadcrumbs" }
call = { package = "call2", path = "../call2" } call = { package = "call2", path = "../call2" }
# channel = { path = "../channel" } channel = { package = "channel2", path = "../channel2" }
cli = { path = "../cli" } cli = { path = "../cli" }
collab_ui = { package = "collab_ui2", path = "../collab_ui2" } collab_ui = { package = "collab_ui2", path = "../collab_ui2" }
collections = { path = "../collections" } collections = { path = "../collections" }
@ -136,6 +136,7 @@ tree-sitter-lua.workspace = true
tree-sitter-nix.workspace = true tree-sitter-nix.workspace = true
tree-sitter-nu.workspace = true tree-sitter-nu.workspace = true
tree-sitter-vue.workspace = true tree-sitter-vue.workspace = true
tree-sitter-uiua.workspace = true
url = "2.2" url = "2.2"
urlencoding = "2.1.2" urlencoding = "2.1.2"

View file

@ -18,6 +18,7 @@ mod json;
#[cfg(feature = "plugin_runtime")] #[cfg(feature = "plugin_runtime")]
mod language_plugin; mod language_plugin;
mod lua; mod lua;
mod nu;
mod php; mod php;
mod python; mod python;
mod ruby; mod ruby;
@ -25,6 +26,7 @@ mod rust;
mod svelte; mod svelte;
mod tailwind; mod tailwind;
mod typescript; mod typescript;
mod uiua;
mod vue; mod vue;
mod yaml; mod yaml;
@ -211,12 +213,21 @@ pub fn init(
language("elm", tree_sitter_elm::language(), vec![]); language("elm", tree_sitter_elm::language(), vec![]);
language("glsl", tree_sitter_glsl::language(), vec![]); language("glsl", tree_sitter_glsl::language(), vec![]);
language("nix", tree_sitter_nix::language(), vec![]); language("nix", tree_sitter_nix::language(), vec![]);
language("nu", tree_sitter_nu::language(), vec![]); language(
"nu",
tree_sitter_nu::language(),
vec![Arc::new(nu::NuLanguageServer {})],
);
language( language(
"vue", "vue",
tree_sitter_vue::language(), tree_sitter_vue::language(),
vec![Arc::new(vue::VueLspAdapter::new(node_runtime))], vec![Arc::new(vue::VueLspAdapter::new(node_runtime))],
); );
language(
"uiua",
tree_sitter_uiua::language(),
vec![Arc::new(uiua::UiuaLanguageServer {})],
);
} }
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]

View file

@ -0,0 +1,55 @@
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
use lsp::LanguageServerBinary;
use std::{any::Any, path::PathBuf};
pub struct NuLanguageServer;
#[async_trait]
impl LspAdapter for NuLanguageServer {
async fn name(&self) -> LanguageServerName {
LanguageServerName("nu".into())
}
fn short_name(&self) -> &'static str {
"nu"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Any + Send>> {
Ok(Box::new(()))
}
async fn fetch_server_binary(
&self,
_version: Box<dyn 'static + Send + Any>,
_container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
Err(anyhow!(
"nu v0.87.0 or greater must be installed and available in your $PATH"
))
}
async fn cached_server_binary(
&self,
_: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
Some(LanguageServerBinary {
path: "nu".into(),
arguments: vec!["--lsp".into()],
})
}
fn can_be_reinstalled(&self) -> bool {
false
}
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
None
}
}

View file

@ -0,0 +1,55 @@
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
use lsp::LanguageServerBinary;
use std::{any::Any, path::PathBuf};
pub struct UiuaLanguageServer;
#[async_trait]
impl LspAdapter for UiuaLanguageServer {
async fn name(&self) -> LanguageServerName {
LanguageServerName("uiua".into())
}
fn short_name(&self) -> &'static str {
"uiua"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Any + Send>> {
Ok(Box::new(()))
}
async fn fetch_server_binary(
&self,
_version: Box<dyn 'static + Send + Any>,
_container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
Err(anyhow!(
"uiua must be installed and available in your $PATH"
))
}
async fn cached_server_binary(
&self,
_: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
Some(LanguageServerBinary {
path: "uiua".into(),
arguments: vec!["lsp".into()],
})
}
fn can_be_reinstalled(&self) -> bool {
false
}
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
None
}
}

View file

@ -0,0 +1,10 @@
name = "Uiua"
path_suffixes = ["ua"]
line_comment = "# "
autoclose_before = ")]}\""
brackets = [
{ start = "{", end = "}", close = true, newline = false},
{ start = "[", end = "]", close = true, newline = false },
{ start = "(", end = ")", close = true, newline = false },
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
]

View file

@ -0,0 +1,50 @@
[
(openParen)
(closeParen)
(openCurly)
(closeCurly)
(openBracket)
(closeBracket)
] @punctuation.bracket
[
(branchSeparator)
(underscore)
] @constructor
; ] @punctuation.delimiter
[ (character) ] @constant.character
[ (comment) ] @comment
[ (constant) ] @constant.numeric
[ (identifier) ] @variable
[ (leftArrow) ] @keyword
[ (function) ] @function
[ (modifier1) ] @operator
[ (modifier2) ] @operator
[ (number) ] @constant.numeric
[ (placeHolder) ] @special
[ (otherConstant) ] @string.special
[ (signature) ] @type
[ (system) ] @function.builtin
[ (tripleMinus) ] @module
; planet
[
"id"
"identity"
"∘"
"dip"
"⊙"
"gap"
"⋅"
] @tag
[
(string)
(multiLineString)
] @string
; [
; (deprecated)
; (identifierDeprecated)
; ] @warning

View file

@ -0,0 +1,3 @@
[
(array)
] @indent

View file

@ -188,7 +188,7 @@ fn main() {
let app_state = Arc::new(AppState { let app_state = Arc::new(AppState {
languages, languages,
client: client.clone(), client: client.clone(),
user_store, user_store: user_store.clone(),
fs, fs,
build_window_options, build_window_options,
call_factory: call::Call::new, call_factory: call::Call::new,
@ -208,7 +208,7 @@ fn main() {
// outline::init(cx); // outline::init(cx);
// project_symbols::init(cx); // project_symbols::init(cx);
project_panel::init(Assets, cx); project_panel::init(Assets, cx);
// channel::init(&client, user_store.clone(), cx); channel::init(&client, user_store.clone(), cx);
// diagnostics::init(cx); // diagnostics::init(cx);
search::init(cx); search::init(cx);
// semantic_index::init(fs.clone(), http.clone(), languages.clone(), cx); // semantic_index::init(fs.clone(), http.clone(), languages.clone(), cx);