Merge branch 'main' into welcome2

This commit is contained in:
Mikayla
2023-11-28 15:38:51 -08:00
88 changed files with 2768 additions and 2094 deletions
+8 -3
View File
@@ -84,8 +84,8 @@ impl Settings for AutoUpdateSetting {
pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppContext) {
AutoUpdateSetting::register(cx);
cx.observe_new_views(|wokrspace: &mut Workspace, _cx| {
wokrspace
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
workspace
.register_action(|_, action: &Check, cx| check(action, cx))
.register_action(|_, _action: &CheckThatAutoUpdaterWorks, cx| {
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();
});
// @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();
@@ -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) {
let auto_updater = auto_updater.read(cx);
let server_url = &auto_updater.server_url;
+34 -66
View File
@@ -1,10 +1,12 @@
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 {
_version: SemanticVersion,
version: SemanticVersion,
}
impl EventEmitter<DismissEvent> for UpdateNotification {}
@@ -12,77 +14,43 @@ impl EventEmitter<DismissEvent> for UpdateNotification {}
impl Render for UpdateNotification {
type Element = Div;
fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> Self::Element {
div().child("Updated zed!")
// let theme = theme::current(cx).clone();
// let theme = &theme.update_notification;
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
let app_name = cx.global::<ReleaseChannel>().display_name();
// let app_name = cx.global::<ReleaseChannel>().display_name();
// MouseEventHandler::new::<ViewReleaseNotes, _>(0, cx, |state, cx| {
// Flex::column()
// .with_child(
// Flex::row()
// .with_child(
// Text::new(
// format!("Updated to {app_name} {}", self.version),
// theme.message.text.clone(),
// )
// .contained()
// .with_style(theme.message.container)
// .aligned()
// .top()
// .left()
// .flex(1., true),
// )
// .with_child(
// MouseEventHandler::new::<Cancel, _>(0, cx, |state, _| {
// let style = theme.dismiss_button.style_for(state);
// Svg::new("icons/x.svg")
// .with_color(style.color)
// .constrained()
// .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")
v_stack()
.elevation_3(cx)
.p_4()
.child(
h_stack()
.justify_between()
.child(Label::new(format!(
"Updated to {app_name} {}",
self.version
)))
.child(
div()
.id("cancel")
.child(IconElement::new(Icon::Close))
.cursor_pointer()
.on_click(cx.listener(|this, _, cx| this.dismiss(cx))),
),
)
.child(
div()
.id("notes")
.child(Label::new("View the release notes"))
.cursor_pointer()
.on_click(|_, cx| crate::view_release_notes(&Default::default(), cx)),
)
}
}
impl UpdateNotification {
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);
}
}
+5 -2
View File
@@ -660,9 +660,12 @@ impl CallHandler for Call {
self.active_call.as_ref().map(|call| {
call.0.update(cx, |this, cx| {
this.room().map(|room| {
room.update(cx, |this, cx| {
this.toggle_mute(cx).log_err();
let room = room.clone();
cx.spawn(|_, mut cx| async move {
room.update(&mut cx, |this, cx| this.toggle_mute(cx))??
.await
})
.detach_and_log_err(cx);
})
})
});
+7 -6
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 audio::{Audio, Sound};
use client::{
@@ -18,6 +21,7 @@ use live_kit_client::{
};
use postage::{sink::Sink, stream::Stream, watch};
use project::Project;
use settings::Settings as _;
use std::{future::Future, mem, sync::Arc, time::Duration};
use util::{post_inc, ResultExt, TryFutureExt};
@@ -328,10 +332,8 @@ impl Room {
}
}
pub fn mute_on_join(_cx: &AppContext) -> bool {
// todo!() po: This should be uncommented, though then unmuting does not work
false
//CallSettings::get_global(cx).mute_on_join || client::IMPERSONATE_LOGIN.is_some()
pub fn mute_on_join(cx: &AppContext) -> bool {
CallSettings::get_global(cx).mute_on_join || client::IMPERSONATE_LOGIN.is_some()
}
fn from_join_response(
@@ -1265,7 +1267,6 @@ impl Room {
.ok_or_else(|| anyhow!("live-kit was not initialized"))?
.await
};
let publication = publish_track.await;
this.upgrade()
.ok_or_else(|| anyhow!("room was dropped"))?
File diff suppressed because it is too large Load Diff
@@ -1,37 +1,34 @@
use client::{ContactRequestStatus, User, UserStore};
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 util::TryFutureExt;
use workspace::Modal;
use theme::ActiveTheme as _;
use ui::{h_stack, v_stack, Label};
use util::{ResultExt as _, TryFutureExt};
pub fn init(cx: &mut AppContext) {
Picker::<ContactFinderDelegate>::init(cx);
cx.add_action(ContactFinder::dismiss)
//Picker::<ContactFinderDelegate>::init(cx);
//cx.add_action(ContactFinder::dismiss)
}
pub struct ContactFinder {
picker: ViewHandle<Picker<ContactFinderDelegate>>,
picker: View<Picker<ContactFinderDelegate>>,
has_focus: bool,
}
impl ContactFinder {
pub fn new(user_store: ModelHandle<UserStore>, cx: &mut ViewContext<Self>) -> Self {
let picker = cx.add_view(|cx| {
Picker::new(
ContactFinderDelegate {
user_store,
potential_contacts: Arc::from([]),
selected_index: 0,
},
cx,
)
.with_theme(|theme| theme.collab_panel.tabbed_modal.picker.clone())
});
cx.subscribe(&picker, |_, _, e, cx| cx.emit(*e)).detach();
pub fn new(user_store: Model<UserStore>, cx: &mut ViewContext<Self>) -> Self {
let delegate = ContactFinderDelegate {
parent: cx.view().downgrade(),
user_store,
potential_contacts: Arc::from([]),
selected_index: 0,
};
let picker = cx.build_view(|cx| Picker::new(delegate, cx));
Self {
picker,
@@ -41,105 +38,72 @@ impl ContactFinder {
pub fn set_query(&mut self, query: String, cx: &mut ViewContext<Self>) {
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 {
type Event = PickerEvent;
}
impl View for ContactFinder {
fn ui_name() -> &'static str {
"ContactFinder"
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
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()
impl Render for ContactFinder {
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
fn render_mode_button(text: &'static str) -> AnyElement {
Label::new(text).into_any_element()
}
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(
"Invite new contacts",
&theme,
cx,
)]))
.expanded()
.contained()
.with_style(theme.header),
v_stack()
.child(
v_stack()
.child(Label::new("Contacts"))
.child(h_stack().children([render_mode_button("Invite new contacts")]))
.bg(cx.theme().colors().element_background),
)
.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()
.child(self.picker.clone())
.w_96()
}
fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
self.has_focus = true;
if cx.is_self_focused() {
cx.focus(&self.picker)
}
}
// fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
// self.has_focus = true;
// if cx.is_self_focused() {
// cx.focus(&self.picker)
// }
// }
fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) {
self.has_focus = false;
}
// fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) {
// self.has_focus = false;
// }
type Element = Div;
}
impl Modal for ContactFinder {
fn has_focus(&self) -> bool {
self.has_focus
}
// impl Modal for ContactFinder {
// fn has_focus(&self) -> bool {
// self.has_focus
// }
fn dismiss_on_event(event: &Self::Event) -> bool {
match event {
PickerEvent::Dismiss => true,
}
}
}
// fn dismiss_on_event(event: &Self::Event) -> bool {
// match event {
// PickerEvent::Dismiss => true,
// }
// }
// }
pub struct ContactFinderDelegate {
parent: WeakView<ContactFinder>,
potential_contacts: Arc<[Arc<User>]>,
user_store: ModelHandle<UserStore>,
user_store: Model<UserStore>,
selected_index: usize,
}
impl PickerDelegate for ContactFinderDelegate {
fn placeholder_text(&self) -> Arc<str> {
"Search collaborator by username...".into()
}
impl EventEmitter<DismissEvent> for ContactFinder {}
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 {
self.potential_contacts.len()
}
@@ -152,6 +116,10 @@ impl PickerDelegate for ContactFinderDelegate {
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<()> {
let search_users = self
.user_store
@@ -161,7 +129,7 @@ impl PickerDelegate for ContactFinderDelegate {
async {
let potential_contacts = search_users.await?;
picker.update(&mut cx, |picker, cx| {
picker.delegate_mut().potential_contacts = potential_contacts.into();
picker.delegate.potential_contacts = potential_contacts.into();
cx.notify();
})?;
anyhow::Ok(())
@@ -191,19 +159,18 @@ impl PickerDelegate for ContactFinderDelegate {
}
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(
&self,
ix: usize,
mouse_state: &mut MouseState,
selected: bool,
cx: &gpui::AppContext,
) -> AnyElement<Picker<Self>> {
let full_theme = &theme::current(cx);
let theme = &full_theme.collab_panel.contact_finder;
let tabbed_modal = &full_theme.collab_panel.tabbed_modal;
cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let user = &self.potential_contacts[ix];
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::RequestAccepted => None,
};
let button_style = if self.user_store.read(cx).is_contact_request_pending(user) {
&theme.disabled_contact_button
} else {
&theme.contact_button
};
let style = tabbed_modal
.picker
.item
.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| {
Svg::new(icon_path)
.with_color(button_style.color)
.constrained()
.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()
dbg!(icon_path);
Some(
div()
.flex_1()
.justify_between()
.children(user.avatar.clone().map(|avatar| img().data(avatar)))
.child(Label::new(user.github_login.clone()))
.children(icon_path.map(|icon_path| svg().path(icon_path))),
)
// 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| {
// Svg::new(icon_path)
// .with_color(button_style.color)
// .constrained()
// .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()
}
}
@@ -39,7 +39,7 @@ use project::Project;
use theme::ActiveTheme;
use ui::{h_stack, Avatar, Button, ButtonVariant, Color, IconButton, KeyBinding, Tooltip};
use util::ResultExt;
use workspace::Workspace;
use workspace::{notifications::NotifyResultExt, Workspace};
use crate::face_pile::FacePile;
@@ -290,11 +290,13 @@ impl Render for CollabTitlebarItem {
} else {
this.child(Button::new("Sign in").on_click(move |_, cx| {
let client = client.clone();
cx.spawn(move |cx| async move {
client.authenticate_and_connect(true, &cx).await?;
Ok::<(), anyhow::Error>(())
cx.spawn(move |mut cx| async move {
client
.authenticate_and_connect(true, &cx)
.await
.notify_async_err(&mut cx);
})
.detach_and_log_err(cx);
.detach();
}))
}
})
+2 -2
View File
@@ -3,8 +3,8 @@ use gpui::{
};
#[derive(Default)]
pub(crate) struct FacePile {
faces: Vec<AnyElement>,
pub struct FacePile {
pub faces: Vec<AnyElement>,
}
impl RenderOnce for FacePile {
+27 -23
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::{
cmp::{self, Reverse},
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::{
channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
ResultExt,
@@ -81,7 +81,7 @@ impl Render for CommandPalette {
type Element = Div;
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 {
type ListItem = ListItem;
fn placeholder_text(&self) -> Arc<str> {
"Execute a command...".into()
}
@@ -292,24 +294,26 @@ impl PickerDelegate for CommandPaletteDelegate {
ix: usize,
selected: bool,
cx: &mut ViewContext<Picker<Self>>,
) -> AnyElement {
) -> Option<Self::ListItem> {
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 {
return div().into_any();
return None;
};
simple_picker_match(selected, cx, |cx| {
h_stack()
.justify_between()
.child(HighlightedLabel::new(
command.name.clone(),
r#match.positions.clone(),
))
.children(KeyBinding::for_action(&*command.action, cx))
.into_any()
})
Some(
ListItem::new(ix).inset(true).selected(selected).child(
h_stack()
.w_full()
.justify_between()
.child(HighlightedLabel::new(
command.name.clone(),
r#match.positions.clone(),
))
.children(KeyBinding::for_action(&*command.action, cx)),
),
)
}
}
+17 -5
View File
@@ -1273,6 +1273,13 @@ impl CompletionsMenu {
multiline_docs.map(|div| {
div.id("multiline_docs")
.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()
// Prevent a mouse down on documentation from being propagated to the editor,
// because that would move the cursor.
@@ -1327,13 +1334,18 @@ impl CompletionsMenu {
div()
.id(mat.candidate_id)
.min_w(px(300.))
.max_w(px(700.))
.min_w(px(220.))
.max_w(px(540.))
.whitespace_nowrap()
.overflow_hidden()
.bg(gpui::green())
.hover(|style| style.bg(gpui::blue()))
.when(item_ix == selected_item, |div| div.bg(gpui::red()))
.text_ui()
.px_1()
.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(
MouseButton::Left,
cx.listener(move |editor, event, cx| {
+59 -14
View File
@@ -22,10 +22,11 @@ use collections::{BTreeMap, HashMap};
use gpui::{
div, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace,
BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element, ElementId,
ElementInputHandler, Entity, EntityId, Hsla, InteractiveElement, IntoElement, LineLayout,
MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, RenderOnce,
ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled,
TextRun, TextStyle, View, ViewContext, WeakView, WindowContext, WrappedLine,
ElementInputHandler, Entity, EntityId, Hsla, InteractiveBounds, InteractiveElement,
IntoElement, LineLayout, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
ParentElement, Pixels, RenderOnce, ScrollWheelEvent, ShapedLine, SharedString, Size,
StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, View,
ViewContext, WeakView, WindowContext, WrappedLine,
};
use itertools::Itertools;
use language::language_settings::ShowWhitespaceSetting;
@@ -316,6 +317,7 @@ impl EditorElement {
position_map: &PositionMap,
text_bounds: Bounds<Pixels>,
gutter_bounds: Bounds<Pixels>,
stacking_order: &StackingOrder,
cx: &mut ViewContext<Editor>,
) -> bool {
let mut click_count = event.click_count;
@@ -326,6 +328,9 @@ impl EditorElement {
} else if !text_bounds.contains_point(&event.position) {
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 position = point_for_position.previous_valid;
@@ -384,6 +389,7 @@ impl EditorElement {
event: &MouseUpEvent,
position_map: &PositionMap,
text_bounds: Bounds<Pixels>,
stacking_order: &StackingOrder,
cx: &mut ViewContext<Editor>,
) -> bool {
let end_selection = editor.has_pending_selection();
@@ -396,6 +402,7 @@ impl EditorElement {
if !pending_nonempty_selections
&& event.modifiers.command
&& 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 could_be_inlay = point.as_valid().is_none();
@@ -418,6 +425,7 @@ impl EditorElement {
position_map: &PositionMap,
text_bounds: Bounds<Pixels>,
gutter_bounds: Bounds<Pixels>,
stacking_order: &StackingOrder,
cx: &mut ViewContext<Editor>,
) -> bool {
let modifiers = event.modifiers;
@@ -457,10 +465,12 @@ impl EditorElement {
let text_hovered = text_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);
// 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);
match point_for_position.as_valid() {
@@ -490,7 +500,7 @@ impl EditorElement {
} else {
update_go_to_definition_link(editor, None, modifiers.command, modifiers.shift, cx);
hover_at(editor, None, cx);
gutter_hovered
gutter_hovered && was_top
}
}
@@ -498,10 +508,10 @@ impl EditorElement {
editor: &mut Editor,
event: &ScrollWheelEvent,
position_map: &PositionMap,
bounds: Bounds<Pixels>,
bounds: &InteractiveBounds,
cx: &mut ViewContext<Editor>,
) -> bool {
if !bounds.contains_point(&event.position) {
if !bounds.visibly_contains(&event.position, cx) {
return false;
}
@@ -2282,10 +2292,15 @@ impl EditorElement {
cx: &mut WindowContext,
) {
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({
let position_map = layout.position_map.clone();
let editor = self.editor.clone();
let interactive_bounds = interactive_bounds.clone();
move |event: &ScrollWheelEvent, phase, cx| {
if phase != DispatchPhase::Bubble {
@@ -2293,7 +2308,7 @@ impl EditorElement {
}
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 {
cx.stop_propagation();
@@ -2304,6 +2319,7 @@ impl EditorElement {
cx.on_mouse_event({
let position_map = layout.position_map.clone();
let editor = self.editor.clone();
let stacking_order = cx.stacking_order().clone();
move |event: &MouseDownEvent, phase, cx| {
if phase != DispatchPhase::Bubble {
@@ -2311,7 +2327,15 @@ impl EditorElement {
}
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 {
@@ -2323,9 +2347,18 @@ impl EditorElement {
cx.on_mouse_event({
let position_map = layout.position_map.clone();
let editor = self.editor.clone();
let stacking_order = cx.stacking_order().clone();
move |event: &MouseUpEvent, phase, 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 {
@@ -2351,13 +2384,23 @@ impl EditorElement {
cx.on_mouse_event({
let position_map = layout.position_map.clone();
let editor = self.editor.clone();
let stacking_order = cx.stacking_order().clone();
move |event: &MouseMoveEvent, phase, cx| {
if phase != DispatchPhase::Bubble {
return;
}
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 {
@@ -2617,9 +2660,11 @@ impl Element for EditorElement {
// We call with_z_index to establish a new stacking context.
cx.with_z_index(0, |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.
self.paint_mouse_listeners(bounds, gutter_bounds, text_bounds, &layout, cx);
cx.with_z_index(0, |cx| {
self.paint_mouse_listeners(bounds, gutter_bounds, text_bounds, &layout, cx);
});
let input_handler = ElementInputHandler::new(bounds, self.editor.clone(), cx);
cx.handle_input(&focus_handle, input_handler);
-3
View File
@@ -483,9 +483,6 @@ impl InfoPopover {
// Prevent a mouse move on the popover from being propagated to the editor,
// because that would dismiss the popover.
.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(
"content",
&self.parsed_content,
+10 -19
View File
@@ -2,9 +2,8 @@ use collections::HashMap;
use editor::{scroll::autoscroll::Autoscroll, Bias, Editor};
use fuzzy::{CharBag, PathMatch, PathMatchCandidate};
use gpui::{
actions, div, AnyElement, AppContext, DismissEvent, Div, Element, EventEmitter, FocusHandle,
FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Render, Styled, Task,
View, ViewContext, VisualContext, WeakView,
actions, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, Model,
ParentElement, Render, Styled, Task, View, ViewContext, VisualContext, WeakView,
};
use picker::{Picker, PickerDelegate};
use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId};
@@ -16,8 +15,7 @@ use std::{
},
};
use text::Point;
use theme::ActiveTheme;
use ui::{v_stack, HighlightedLabel, StyledExt};
use ui::{v_stack, HighlightedLabel, ListItem};
use util::{paths::PathLikeWithPosition, post_inc, ResultExt};
use workspace::Workspace;
@@ -530,6 +528,8 @@ impl FileFinderDelegate {
}
impl PickerDelegate for FileFinderDelegate {
type ListItem = ListItem;
fn placeholder_text(&self) -> Arc<str> {
"Search project files...".into()
}
@@ -709,31 +709,22 @@ impl PickerDelegate for FileFinderDelegate {
ix: usize,
selected: bool,
cx: &mut ViewContext<Picker<Self>>,
) -> AnyElement {
) -> Option<Self::ListItem> {
let path_match = self
.matches
.get(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) =
self.labels_for_match(path_match, cx, ix);
div()
.px_1()
.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(
Some(
ListItem::new(ix).inset(true).selected(selected).child(
v_stack()
.child(HighlightedLabel::new(file_name, file_name_positions))
.child(HighlightedLabel::new(full_path, full_path_positions)),
)
.into_any()
),
)
}
}
+93 -38
View File
@@ -3,7 +3,8 @@ use crate::{
BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusEvent, FocusHandle,
IntoElement, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent,
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 refineable::Refineable;
@@ -84,7 +85,7 @@ pub trait InteractiveElement: Sized + Element {
move |event, bounds, phase, cx| {
if phase == DispatchPhase::Bubble
&& event.button == button
&& bounds.contains_point(&event.position)
&& bounds.visibly_contains(&event.position, cx)
{
(listener)(event, cx)
}
@@ -99,7 +100,7 @@ pub trait InteractiveElement: Sized + Element {
) -> Self {
self.interactivity().mouse_down_listeners.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)
}
},
@@ -117,7 +118,7 @@ pub trait InteractiveElement: Sized + Element {
.push(Box::new(move |event, bounds, phase, cx| {
if phase == DispatchPhase::Bubble
&& event.button == button
&& bounds.contains_point(&event.position)
&& bounds.visibly_contains(&event.position, cx)
{
(listener)(event, cx)
}
@@ -132,7 +133,7 @@ pub trait InteractiveElement: Sized + Element {
self.interactivity()
.mouse_up_listeners
.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)
}
}));
@@ -145,7 +146,8 @@ pub trait InteractiveElement: Sized + Element {
) -> Self {
self.interactivity().mouse_down_listeners.push(Box::new(
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)
}
},
@@ -163,7 +165,7 @@ pub trait InteractiveElement: Sized + Element {
.push(Box::new(move |event, bounds, phase, cx| {
if phase == DispatchPhase::Capture
&& event.button == button
&& !bounds.contains_point(&event.position)
&& !bounds.visibly_contains(&event.position, cx)
{
(listener)(event, cx);
}
@@ -177,7 +179,7 @@ pub trait InteractiveElement: Sized + Element {
) -> Self {
self.interactivity().mouse_move_listeners.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);
}
},
@@ -191,7 +193,7 @@ pub trait InteractiveElement: Sized + Element {
) -> Self {
self.interactivity().scroll_wheel_listeners.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);
}
},
@@ -526,15 +528,15 @@ pub type FocusListeners = SmallVec<[FocusListener; 2]>;
pub type FocusListener = Box<dyn Fn(&FocusHandle, &FocusEvent, &mut WindowContext) + 'static>;
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 =
Box<dyn Fn(&MouseUpEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>;
Box<dyn Fn(&MouseUpEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
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 =
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>;
@@ -719,6 +721,18 @@ pub struct Interactivity {
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 {
pub fn layout(
&mut self,
@@ -755,34 +769,52 @@ impl Interactivity {
) {
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 {
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 {
cx.set_cursor_style(mouse_cursor);
}
}
for listener in self.mouse_down_listeners.drain(..) {
let interactive_bounds = interactive_bounds.clone();
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(..) {
let interactive_bounds = interactive_bounds.clone();
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(..) {
let interactive_bounds = interactive_bounds.clone();
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(..) {
let interactive_bounds = interactive_bounds.clone();
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));
if let Some(group_bounds) = hover_group_bounds {
// todo!() needs cx.was_top_layer
let hovered = group_bounds.contains_point(&cx.mouse_position());
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
if phase == DispatchPhase::Capture {
@@ -805,10 +838,11 @@ impl Interactivity {
if self.hover_style.is_some()
|| (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| {
if phase == DispatchPhase::Capture {
if bounds.contains_point(&event.position) != hovered {
if interactive_bounds.visibly_contains(&event.position, cx) != hovered {
cx.notify();
}
}
@@ -817,8 +851,11 @@ impl Interactivity {
if cx.active_drag.is_some() {
let drop_listeners = mem::take(&mut self.drop_listeners);
let interactive_bounds = interactive_bounds.clone();
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) =
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(drag_listener) = drag_listener {
let active_state = element_state.clicked_state.clone();
let interactive_bounds = interactive_bounds.clone();
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
if cx.active_drag.is_some() {
@@ -854,7 +892,7 @@ impl Interactivity {
cx.notify();
}
} 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
{
*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| {
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
if phase == DispatchPhase::Bubble
&& interactive_bounds.visibly_contains(&event.position, cx)
{
let mouse_click = ClickEvent {
down: mouse_down.clone(),
up: event.clone(),
@@ -881,8 +922,11 @@ impl Interactivity {
cx.notify();
});
} else {
let interactive_bounds = interactive_bounds.clone();
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());
cx.notify();
}
@@ -893,13 +937,14 @@ impl Interactivity {
if let Some(hover_listener) = self.hover_listener.take() {
let was_hovered = element_state.hover_state.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| {
if phase != DispatchPhase::Bubble {
return;
}
let is_hovered =
bounds.contains_point(&event.position) && has_mouse_down.borrow().is_none();
let is_hovered = interactive_bounds.visibly_contains(&event.position, cx)
&& has_mouse_down.borrow().is_none();
let mut was_hovered = was_hovered.borrow_mut();
if is_hovered != was_hovered.clone() {
@@ -914,14 +959,15 @@ impl Interactivity {
if let Some(tooltip_builder) = self.tooltip_builder.take() {
let active_tooltip = element_state.active_tooltip.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| {
if phase != DispatchPhase::Bubble {
return;
}
let is_hovered =
bounds.contains_point(&event.position) && pending_mouse_down.borrow().is_none();
let is_hovered = interactive_bounds.visibly_contains(&event.position, cx)
&& pending_mouse_down.borrow().is_none();
if !is_hovered {
active_tooltip.borrow_mut().take();
return;
@@ -979,11 +1025,12 @@ impl Interactivity {
.group_active_style
.as_ref()
.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| {
if phase == DispatchPhase::Bubble {
let group = active_group_bounds
.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 {
*active_state.borrow_mut() = ElementClickedState { group, element };
cx.notify();
@@ -1000,9 +1047,12 @@ impl Interactivity {
.clone();
let line_height = cx.line_height();
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| {
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 old_scroll_offset = *scroll_offset;
let delta = event.delta.pixel_delta(line_height);
@@ -1098,19 +1148,21 @@ impl Interactivity {
}
}
}
// if self.hover_style.is_some() {
if bounds.contains_point(&mouse_position) {
// eprintln!("div hovered {bounds:?} {mouse_position:?}");
style.refine(&self.hover_style);
} else {
// eprintln!("div NOT hovered {bounds:?} {mouse_position:?}");
if self.hover_style.is_some() {
if bounds
.intersect(&cx.content_mask().bounds)
.contains_point(&mouse_position)
&& cx.was_top_layer(&mouse_position, cx.stacking_order())
{
style.refine(&self.hover_style);
}
}
// }
if let Some(drag) = cx.active_drag.take() {
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 *state_type == drag.view.entity_type()
// todo!() needs to handle cx.content_mask() and cx.is_top()
&& group_bounds.contains_point(&mouse_position)
{
style.refine(&group_drag_style.style);
@@ -1120,7 +1172,10 @@ impl Interactivity {
for (state_type, drag_over_style) in &self.drag_over_styles {
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);
}
+32 -3
View File
@@ -39,8 +39,8 @@ use util::ResultExt;
/// 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.
#[derive(Deref, DerefMut, Ord, PartialOrd, Eq, PartialEq, Clone, Default)]
pub(crate) struct StackingOrder(pub(crate) SmallVec<[u32; 16]>);
#[derive(Deref, DerefMut, Ord, PartialOrd, Eq, PartialEq, Clone, Default, Debug)]
pub struct StackingOrder(pub(crate) SmallVec<[u32; 16]>);
/// Represents the two different phases when dispatching events.
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq)]
@@ -243,7 +243,8 @@ pub(crate) struct Frame {
pub(crate) dispatch_tree: DispatchTree,
pub(crate) focus_listeners: Vec<AnyFocusListener>,
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>>,
element_offset_stack: Vec<Point<Pixels>>,
}
@@ -257,6 +258,7 @@ impl Frame {
focus_listeners: Vec::new(),
scene_builder: SceneBuilder::default(),
z_index_stack: StackingOrder::default(),
depth_map: Default::default(),
content_mask_stack: Vec::new(),
element_offset_stack: Vec::new(),
}
@@ -806,6 +808,32 @@ impl<'a> WindowContext<'a> {
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.
pub fn paint_shadows(
&mut self,
@@ -1153,6 +1181,7 @@ impl<'a> WindowContext<'a> {
frame.mouse_listeners.values_mut().for_each(Vec::clear);
frame.focus_listeners.clear();
frame.dispatch_tree.clear();
frame.depth_map.clear();
}
/// Dispatch a mouse or keyboard event on the window.
+8 -6
View File
@@ -73,6 +73,7 @@ impl RealNodeRuntime {
let npm_file = node_dir.join("bin/npm");
let result = Command::new(&node_binary)
.env_clear()
.arg(npm_file)
.arg("--version")
.stdin(Stdio::null())
@@ -149,6 +150,7 @@ impl NodeRuntime for RealNodeRuntime {
}
let mut command = Command::new(node_binary);
command.env_clear();
command.env("PATH", env_path);
command.arg(npm_file).arg(subcommand);
command.args(["--cache".into(), installation_path.join("cache")]);
@@ -200,11 +202,11 @@ impl NodeRuntime for RealNodeRuntime {
&[
name,
"--json",
"-fetch-retry-mintimeout",
"--fetch-retry-mintimeout",
"2000",
"-fetch-retry-maxtimeout",
"--fetch-retry-maxtimeout",
"5000",
"-fetch-timeout",
"--fetch-timeout",
"5000",
],
)
@@ -229,11 +231,11 @@ impl NodeRuntime for RealNodeRuntime {
let mut arguments: Vec<_> = packages.iter().map(|p| p.as_str()).collect();
arguments.extend_from_slice(&[
"-fetch-retry-mintimeout",
"--fetch-retry-mintimeout",
"2000",
"-fetch-retry-maxtimeout",
"--fetch-retry-maxtimeout",
"5000",
"-fetch-timeout",
"--fetch-timeout",
"5000",
]);
+3 -3
View File
@@ -16,6 +16,7 @@ pub struct Picker<D: PickerDelegate> {
}
pub trait PickerDelegate: Sized + 'static {
type ListItem: IntoElement;
fn match_count(&self) -> usize;
fn selected_index(&self) -> usize;
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>);
@@ -31,7 +32,7 @@ pub trait PickerDelegate: Sized + 'static {
ix: usize,
selected: bool,
cx: &mut ViewContext<Picker<Self>>,
) -> AnyElement;
) -> Option<Self::ListItem>;
}
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>) {
dbg!("canceling!");
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 == selected_index,
cx,
+22 -44
View File
@@ -10,9 +10,8 @@ use anyhow::{anyhow, Result};
use gpui::{
actions, div, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext,
ClipboardItem, Div, EventEmitter, FocusHandle, Focusable, FocusableView, InteractiveElement,
IntoElement, Model, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel,
Render, Stateful, StatefulInteractiveElement, Styled, Task, UniformListScrollHandle, View,
ViewContext, VisualContext as _, WeakView, WindowContext,
Model, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, Stateful, Styled,
Task, UniformListScrollHandle, View, ViewContext, VisualContext as _, WeakView, WindowContext,
};
use menu::{Confirm, SelectNext, SelectPrev};
use project::{
@@ -30,7 +29,7 @@ use std::{
sync::Arc,
};
use theme::ActiveTheme as _;
use ui::{h_stack, v_stack, IconElement, Label};
use ui::{v_stack, IconElement, Label, ListItem};
use unicase::UniCase;
use util::{maybe, ResultExt, TryFutureExt};
use workspace::{
@@ -1335,13 +1334,19 @@ impl ProjectPanel {
}
}
fn render_entry_visual_element(
details: &EntryDetails,
editor: Option<&View<Editor>>,
padding: Pixels,
fn render_entry(
&self,
entry_id: ProjectEntryId,
details: EntryDetails,
// dragged_entry_destination: &mut Option<Arc<Path>>,
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 is_selected = self
.selection
.map_or(false, |selection| selection.entry_id == entry_id);
let theme = cx.theme();
let filename_text_color = details
@@ -1354,14 +1359,17 @@ impl ProjectPanel {
})
.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 {
div().child(IconElement::from_path(icon.to_string()))
} else {
div()
})
.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())
} else {
div()
@@ -1370,33 +1378,6 @@ impl ProjectPanel {
}
.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| {
if !show_editor {
if kind.is_dir() {
@@ -1410,12 +1391,9 @@ impl ProjectPanel {
}
}
}))
.on_mouse_down(
MouseButton::Right,
cx.listener(move |this, event: &MouseDownEvent, cx| {
this.deploy_context_menu(event.position, entry_id, cx);
}),
)
.on_secondary_mouse_down(cx.listener(move |this, event: &MouseDownEvent, cx| {
this.deploy_context_menu(event.position, entry_id, cx);
}))
// .on_drop::<ProjectEntryId>(|this, event, cx| {
// this.move_entry(
// *dragged_entry,
+4 -5
View File
@@ -33,7 +33,6 @@ impl Render for FocusStory {
let theme = cx.theme();
let color_1 = theme.status().created;
let color_2 = theme.status().modified;
let color_3 = theme.status().deleted;
let color_4 = theme.status().conflict;
let color_5 = theme.status().ignored;
let color_6 = theme.status().renamed;
@@ -42,10 +41,10 @@ impl Render for FocusStory {
.id("parent")
.focusable()
.key_context("parent")
.on_action(cx.listener(|_, action: &ActionA, cx| {
.on_action(cx.listener(|_, _action: &ActionA, _cx| {
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");
}))
.on_focus(cx.listener(|_, _, _| println!("Parent focused")))
@@ -61,7 +60,7 @@ impl Render for FocusStory {
div()
.track_focus(&self.child_1_focus)
.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");
}))
.w_full()
@@ -83,7 +82,7 @@ impl Render for FocusStory {
div()
.track_focus(&self.child_2_focus)
.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");
}))
.w_full()
@@ -9,7 +9,7 @@ pub struct KitchenSinkStory;
impl KitchenSinkStory {
pub fn view(cx: &mut WindowContext) -> View<Self> {
cx.build_view(|cx| Self)
cx.build_view(|_cx| Self)
}
}
+14 -19
View File
@@ -1,11 +1,11 @@
use fuzzy::StringMatchCandidate;
use gpui::{
div, prelude::*, AnyElement, Div, KeyBinding, Render, SharedString, Styled, Task, View,
WindowContext,
div, prelude::*, Div, KeyBinding, Render, SharedString, Styled, Task, View, WindowContext,
};
use picker::{Picker, PickerDelegate};
use std::sync::Arc;
use theme2::ActiveTheme;
use ui::{Label, ListItem};
pub struct PickerStory {
picker: View<Picker<Delegate>>,
@@ -37,6 +37,8 @@ impl Delegate {
}
impl PickerDelegate for Delegate {
type ListItem = ListItem;
fn match_count(&self) -> usize {
self.candidates.len()
}
@@ -49,27 +51,20 @@ impl PickerDelegate for Delegate {
&self,
ix: usize,
selected: bool,
cx: &mut gpui::ViewContext<Picker<Self>>,
) -> AnyElement {
let colors = cx.theme().colors();
_cx: &mut gpui::ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let Some(candidate_ix) = self.matches.get(ix) else {
return div().into_any();
return None;
};
// TASK: Make StringMatchCandidate::string a SharedString
let candidate = SharedString::from(self.candidates[*candidate_ix].string.clone());
div()
.text_color(colors.text)
.when(selected, |s| {
s.border_l_10().border_color(colors.terminal_ansi_yellow)
})
.hover(|style| {
style
.bg(colors.element_active)
.text_color(colors.text_accent)
})
.child(candidate)
.into_any()
Some(
ListItem::new(ix)
.inset(true)
.selected(selected)
.child(Label::new(candidate)),
)
}
fn selected_index(&self) -> usize {
@@ -81,7 +76,7 @@ impl PickerDelegate for Delegate {
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 = self.candidates[candidate_ix].string.clone();
+1 -1
View File
@@ -6,7 +6,7 @@ pub struct ScrollStory;
impl ScrollStory {
pub fn view(cx: &mut WindowContext) -> View<ScrollStory> {
cx.build_view(|cx| ScrollStory)
cx.build_view(|_cx| ScrollStory)
}
}
+2 -2
View File
@@ -8,7 +8,7 @@ pub struct TextStory;
impl TextStory {
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),
]),
)
.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}");
})
)
+2 -2
View File
@@ -9,7 +9,7 @@ pub struct ZIndexStory;
impl Render for ZIndexStory {
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(
div()
.flex()
@@ -84,7 +84,7 @@ struct ZIndexExample {
impl RenderOnce for ZIndexExample {
type Rendered = Div;
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
div()
.relative()
.size_full()
+2
View File
@@ -18,6 +18,7 @@ pub enum ComponentStory {
ContextMenu,
Focus,
Icon,
IconButton,
Input,
Keybinding,
Label,
@@ -37,6 +38,7 @@ impl ComponentStory {
Self::ContextMenu => cx.build_view(|_| ui::ContextMenuStory).into(),
Self::Focus => FocusStory::view(cx).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::Keybinding => cx.build_view(|_| ui::KeybindingStory).into(),
Self::Label => cx.build_view(|_| ui::LabelStory).into(),
+2 -4
View File
@@ -1,5 +1,3 @@
#![allow(dead_code, unused_variables)]
mod assets;
mod stories;
mod story_selector;
@@ -70,7 +68,7 @@ fn main() {
language::init(cx);
editor::init(cx);
let window = cx.open_window(
let _window = cx.open_window(
WindowOptions {
bounds: WindowBounds::Fixed(Bounds {
origin: Default::default(),
@@ -104,7 +102,7 @@ impl StoryWrapper {
impl Render for StoryWrapper {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
div()
.flex()
.flex_col()
+12 -12
View File
@@ -23,15 +23,15 @@ impl ThemeColors {
surface_background: neutral().light().step_2(),
background: neutral().light().step_1(),
element_background: neutral().light().step_3(),
element_hover: neutral().light().step_4(),
element_active: neutral().light().step_5(),
element_selected: neutral().light().step_5(),
element_hover: neutral().light_alpha().step_4(),
element_active: neutral().light_alpha().step_5(),
element_selected: neutral().light_alpha().step_5(),
element_disabled: neutral().light_alpha().step_3(),
drop_target_background: blue().light_alpha().step_2(),
ghost_element_background: system.transparent,
ghost_element_hover: neutral().light().step_4(),
ghost_element_active: neutral().light().step_5(),
ghost_element_selected: neutral().light().step_5(),
ghost_element_hover: neutral().light_alpha().step_4(),
ghost_element_active: neutral().light_alpha().step_5(),
ghost_element_selected: neutral().light_alpha().step_5(),
ghost_element_disabled: neutral().light_alpha().step_3(),
text: yellow().light().step_9(),
text_muted: neutral().light().step_11(),
@@ -95,15 +95,15 @@ impl ThemeColors {
surface_background: neutral().dark().step_2(),
background: neutral().dark().step_1(),
element_background: neutral().dark().step_3(),
element_hover: neutral().dark().step_4(),
element_active: neutral().dark().step_5(),
element_selected: neutral().dark().step_5(),
element_hover: neutral().dark_alpha().step_4(),
element_active: neutral().dark_alpha().step_5(),
element_selected: neutral().dark_alpha().step_5(),
element_disabled: neutral().dark_alpha().step_3(),
drop_target_background: blue().dark_alpha().step_2(),
ghost_element_background: system.transparent,
ghost_element_hover: neutral().dark().step_4(),
ghost_element_active: neutral().dark().step_5(),
ghost_element_selected: neutral().dark().step_5(),
ghost_element_hover: neutral().dark_alpha().step_4(),
ghost_element_active: neutral().dark_alpha().step_5(),
ghost_element_selected: neutral().dark_alpha().step_5(),
ghost_element_disabled: neutral().dark_alpha().step_3(),
text: neutral().dark().step_12(),
text_muted: neutral().dark().step_11(),
+2 -2
View File
@@ -20,7 +20,7 @@ pub fn one_family() -> ThemeFamily {
pub(crate) fn one_dark() -> Theme {
let bg = hsla(215. / 360., 12. / 100., 15. / 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 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,
surface_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_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),
+1
View File
@@ -13,6 +13,7 @@ editor = { package = "editor2", path = "../editor2" }
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
fs = { package = "fs2", path = "../fs2" }
gpui = { package = "gpui2", path = "../gpui2" }
ui = { package = "ui2", path = "../ui2" }
picker = { package = "picker2", path = "../picker2" }
theme = { package = "theme2", path = "../theme2" }
settings = { package = "settings2", path = "../settings2" }
+18 -25
View File
@@ -2,19 +2,16 @@ use feature_flags::FeatureFlagAppExt;
use fs::Fs;
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
use gpui::{
actions, div, AnyElement, AppContext, DismissEvent, Element, EventEmitter, FocusableView,
InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, View,
ViewContext, VisualContext, WeakView,
actions, AppContext, DismissEvent, EventEmitter, FocusableView, ParentElement, Render,
SharedString, View, ViewContext, VisualContext, WeakView,
};
use picker::{Picker, PickerDelegate};
use settings::{update_settings_file, SettingsStore};
use std::sync::Arc;
use theme::{ActiveTheme, Theme, ThemeRegistry, ThemeSettings};
use ui::ListItem;
use util::ResultExt;
use workspace::{
ui::{HighlightedLabel, StyledExt},
Workspace,
};
use workspace::{ui::HighlightedLabel, Workspace};
actions!(Toggle, Reload);
@@ -160,6 +157,8 @@ impl ThemeSelectorDelegate {
}
impl PickerDelegate for ThemeSelectorDelegate {
type ListItem = ui::ListItem;
fn placeholder_text(&self) -> Arc<str> {
"Select Theme...".into()
}
@@ -260,24 +259,18 @@ impl PickerDelegate for ThemeSelectorDelegate {
&self,
ix: usize,
selected: bool,
cx: &mut ViewContext<Picker<Self>>,
) -> AnyElement {
let theme = cx.theme();
let colors = theme.colors();
_cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let theme_match = &self.matches[ix];
div()
.px_1()
.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(HighlightedLabel::new(
theme_match.string.clone(),
theme_match.positions.clone(),
))
.into_any()
Some(
ListItem::new(ix)
.inset(true)
.selected(selected)
.child(HighlightedLabel::new(
theme_match.string.clone(),
theme_match.positions.clone(),
)),
)
}
}
+2 -2
View File
@@ -15,11 +15,11 @@ gpui = { package = "gpui2", path = "../gpui2" }
itertools = { version = "0.11.0", optional = true }
menu = { package = "menu2", path = "../menu2"}
serde.workspace = true
settings2 = { path = "../settings2" }
settings = { package = "settings2", path = "../settings2" }
smallvec.workspace = true
story = { path = "../story", optional = true }
strum = { version = "0.25.0", features = ["derive"] }
theme2 = { path = "../theme2" }
theme = { package = "theme2", path = "../theme2" }
rand = "0.8"
[features]
+6
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 {
self.shape = shape;
self
+1 -2
View File
@@ -1,7 +1,6 @@
use gpui::{div, prelude::*, Div, Element, ElementId, IntoElement, Styled, WindowContext};
use theme2::ActiveTheme;
use crate::prelude::*;
use crate::{Color, Icon, IconElement, Selection};
pub type CheckHandler = Box<dyn Fn(&Selection, &mut WindowContext) + 'static>;
+1 -1
View File
@@ -264,7 +264,7 @@ impl<M: ManagedView> Element for MenuHandle<M> {
let new_menu = (builder)(cx);
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 => {
*menu2.borrow_mut() = None;
cx.notify();
-13
View File
@@ -49,17 +49,4 @@ impl Divider {
self.inset = true;
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)
}
}
+18 -15
View File
@@ -14,6 +14,8 @@ pub enum IconSize {
pub enum Icon {
Ai,
ArrowLeft,
ArrowUp,
ArrowDown,
ArrowRight,
ArrowUpRight,
AtSign,
@@ -61,6 +63,7 @@ pub enum Icon {
Mic,
MicMute,
Plus,
Public,
Quote,
Replace,
ReplaceAll,
@@ -71,6 +74,11 @@ pub enum Icon {
Terminal,
WholeWord,
XCircle,
Command,
Control,
Shift,
Option,
Return,
}
impl Icon {
@@ -79,6 +87,8 @@ impl Icon {
Icon::Ai => "icons/ai.svg",
Icon::ArrowLeft => "icons/arrow_left.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::AtSign => "icons/at-sign.svg",
Icon::AudioOff => "icons/speaker-off.svg",
@@ -125,6 +135,7 @@ impl Icon {
Icon::Mic => "icons/mic.svg",
Icon::MicMute => "icons/mic-mute.svg",
Icon::Plus => "icons/plus.svg",
Icon::Public => "icons/public.svg",
Icon::Quote => "icons/quote.svg",
Icon::Replace => "icons/replace.svg",
Icon::ReplaceAll => "icons/replace_all.svg",
@@ -135,6 +146,11 @@ impl Icon {
Icon::Terminal => "icons/terminal.svg",
Icon::WholeWord => "icons/word_search.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 {
let svg_size = match self.size {
IconSize::Small => rems(0.75),
IconSize::Medium => rems(0.9375),
IconSize::Small => rems(14. / 16.),
IconSize::Medium => rems(16. / 16.),
};
svg()
@@ -189,17 +205,4 @@ impl IconElement {
self.size = size;
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))
}
}
+4 -5
View File
@@ -23,15 +23,13 @@ impl RenderOnce for IconButton {
_ => 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 => (
cx.theme().colors().element_background,
cx.theme().colors().element_hover,
cx.theme().colors().element_active,
),
ButtonVariant::Ghost => (
cx.theme().colors().ghost_element_background,
cx.theme().colors().ghost_element_hover,
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 {
self.on_click(move |this, cx| cx.dispatch_action(action.boxed_clone()))
self.on_click(move |_event, cx| cx.dispatch_action(action.boxed_clone()))
}
}
+74 -16
View File
@@ -1,5 +1,5 @@
use crate::prelude::*;
use gpui::{Action, Div, IntoElement};
use crate::{h_stack, prelude::*, Icon, IconElement, IconSize};
use gpui::{relative, rems, Action, Div, IntoElement, Keystroke};
#[derive(IntoElement, Clone)]
pub struct KeyBinding {
@@ -14,19 +14,35 @@ impl RenderOnce for KeyBinding {
type Rendered = Div;
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
div()
.flex()
h_stack()
.flex_none()
.gap_2()
.children(self.key_binding.keystrokes().iter().map(|keystroke| {
div()
.flex()
.gap_1()
let key_icon = Self::icon_for_key(&keystroke);
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.control, |el| el.child(Key::new("^")))
.when(keystroke.modifiers.alt, |el| el.child(Key::new("⌥")))
.when(keystroke.modifiers.command, |el| el.child(Key::new("⌘")))
.when(keystroke.modifiers.shift, |el| el.child(Key::new("⇧")))
.child(Key::new(keystroke.key.clone()))
.when(keystroke.modifiers.control, |el| {
el.child(KeyIcon::new(Icon::Control))
})
.when(keystroke.modifiers.alt, |el| {
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))
}
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 {
Self { key_binding }
}
@@ -53,13 +85,18 @@ impl RenderOnce for Key {
type Rendered = Div;
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
let single_char = self.key.len() == 1;
div()
.px_2()
.py_0()
.rounded_md()
.text_ui_sm()
.when(single_char, |el| {
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)
.bg(cx.theme().colors().element_background)
.child(self.key.clone())
}
}
@@ -69,3 +106,24 @@ impl Key {
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 }
}
}
+1 -7
View File
@@ -1,6 +1,6 @@
use crate::prelude::*;
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)]
pub enum LabelSize {
@@ -182,9 +182,3 @@ impl HighlightedLabel {
self
}
}
/// A run of text that receives the same style.
struct Run {
pub text: String,
pub color: Hsla,
}
+117 -174
View File
@@ -1,25 +1,19 @@
use gpui::{
div, px, AnyElement, ClickEvent, Div, IntoElement, Stateful, StatefulInteractiveElement,
};
use smallvec::SmallVec;
use std::rc::Rc;
use gpui::{
div, px, AnyElement, ClickEvent, Div, ImageSource, IntoElement, MouseButton, MouseDownEvent,
Pixels, Stateful, StatefulInteractiveElement,
};
use smallvec::SmallVec;
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};
#[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 {
// TODO: These should be IconButtons
Tools(Vec<Icon>),
Tools(Vec<IconButton>),
// TODO: This should be a button
Button(Label),
Text(Label),
@@ -30,8 +24,39 @@ pub struct ListHeader {
label: SharedString,
left_icon: Option<Icon>,
meta: Option<ListHeaderMeta>,
variant: ListItemVariant,
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 {
@@ -45,11 +70,7 @@ impl RenderOnce for ListHeader {
h_stack()
.gap_2()
.items_center()
.children(icons.into_iter().map(|i| {
IconElement::new(i)
.color(Color::Muted)
.size(IconSize::Small)
})),
.children(icons.into_iter().map(|i| i.color(Color::Muted))),
),
Some(ListHeaderMeta::Button(label)) => div().child(label),
Some(ListHeaderMeta::Text(label)) => div().child(label),
@@ -63,7 +84,7 @@ impl RenderOnce for ListHeader {
.child(
div()
.h_5()
.when(self.variant == ListItemVariant::Inset, |this| this.px_2())
.when(self.inset, |this| this.px_2())
.flex()
.flex_1()
.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)]
pub struct ListSubHeader {
label: SharedString,
left_icon: Option<Icon>,
variant: ListItemVariant,
inset: bool,
}
impl ListSubHeader {
@@ -191,7 +125,7 @@ impl ListSubHeader {
Self {
label: label.into(),
left_icon: None,
variant: ListItemVariant::default(),
inset: false,
}
}
@@ -204,11 +138,11 @@ impl ListSubHeader {
impl RenderOnce for ListSubHeader {
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(
div()
.h_6()
.when(self.variant == ListItemVariant::Inset, |this| this.px_2())
.when(self.inset, |this| this.px_2())
.flex()
.flex_1()
.w_full()
@@ -231,26 +165,19 @@ impl RenderOnce for ListSubHeader {
}
}
#[derive(Default, PartialEq, Copy, Clone)]
pub enum ListEntrySize {
#[default]
Small,
Medium,
}
#[derive(IntoElement)]
pub struct ListItem {
id: ElementId,
disabled: bool,
selected: bool,
// TODO: Reintroduce this
// disclosure_control_style: DisclosureControlVisibility,
indent_level: u32,
indent_level: usize,
indent_step_size: Pixels,
left_slot: Option<GraphicSlot>,
overflow: OverflowStyle,
size: ListEntrySize,
toggle: Toggle,
variant: ListItemVariant,
inset: bool,
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]>,
}
@@ -258,14 +185,14 @@ impl ListItem {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
disabled: false,
selected: false,
indent_level: 0,
indent_step_size: px(12.),
left_slot: None,
overflow: OverflowStyle::Hidden,
size: ListEntrySize::default(),
toggle: Toggle::NotToggleable,
variant: ListItemVariant::default(),
on_click: Default::default(),
inset: false,
on_click: None,
on_secondary_mouse_down: None,
children: SmallVec::new(),
}
}
@@ -275,21 +202,39 @@ impl ListItem {
self
}
pub fn variant(mut self, variant: ListItemVariant) -> Self {
self.variant = variant;
pub fn on_secondary_mouse_down(
mut self,
handler: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
) -> Self {
self.on_secondary_mouse_down = Some(Rc::new(handler));
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
}
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 {
self.toggle = toggle;
self
}
pub fn selected(mut self, selected: bool) -> Self {
self.selected = selected;
self
}
pub fn left_content(mut self, left_content: GraphicSlot) -> Self {
self.left_slot = Some(left_content);
self
@@ -300,15 +245,10 @@ impl ListItem {
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
}
pub fn size(mut self, size: ListEntrySize) -> Self {
self.size = size;
self
}
}
impl RenderOnce for ListItem {
@@ -323,61 +263,64 @@ impl RenderOnce for ListItem {
.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))),
None => None,
};
let sized_item = match self.size {
ListEntrySize::Small => div().h_6(),
ListEntrySize::Medium => div().h_7(),
};
div()
.id(self.id)
.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
// .when(self.state == InteractionState::Focused, |this| {
// this.border()
// .border_color(cx.theme().colors().border_focused)
// })
.when(self.inset, |this| this.rounded_md())
.hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
.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(
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()
.w(px(4.))
.h_full()
.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)),
)
}))
div()
.when(self.inset, |this| this.px_2())
.ml(self.indent_level as f32 * self.indent_step_size)
.flex()
.gap_1()
.items_center()
.relative()
.child(disclosure_control(self.toggle))
.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 {
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) {
(false, _) => div().children(self.children),
(true, Toggle::Toggled(false)) => div(),
+9 -14
View File
@@ -1,10 +1,11 @@
use gpui::{
AnyElement, Div, Element, ElementId, IntoElement, ParentElement, RenderOnce, Styled,
div, AnyElement, Div, Element, ElementId, IntoElement, ParentElement, RenderOnce, Styled,
WindowContext,
};
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.
///
@@ -43,22 +44,16 @@ impl RenderOnce for Popover {
type Rendered = Div;
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
v_stack()
.relative()
.elevation_2(cx)
.p_1()
.children(self.children)
div()
.flex()
.gap_1()
.child(v_stack().elevation_2(cx).px_1().children(self.children))
.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(
v_stack()
.top_0()
.left_full()
.ml_1()
.absolute()
.elevation_2(cx)
.p_1()
.bg(cx.theme().colors().surface_background)
.px_1()
.child(aside),
)
})
+2 -2
View File
@@ -1,4 +1,4 @@
use gpui::SharedString;
use gpui::{ImageSource, SharedString};
use crate::Icon;
@@ -9,6 +9,6 @@ use crate::Icon;
/// Can be filled with a []
pub enum GraphicSlot {
Icon(Icon),
Avatar(SharedString),
Avatar(ImageSource),
PublicActor(SharedString),
}
+2
View File
@@ -3,6 +3,7 @@ mod button;
mod checkbox;
mod context_menu;
mod icon;
mod icon_button;
mod input;
mod keybinding;
mod label;
@@ -13,6 +14,7 @@ pub use button::*;
pub use checkbox::*;
pub use context_menu::*;
pub use icon::*;
pub use icon_button::*;
pub use input::*;
pub use keybinding::*;
pub use label::*;
+1 -1
View File
@@ -9,7 +9,7 @@ pub struct AvatarStory;
impl Render for AvatarStory {
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_for::<Avatar>())
.child(Story::label("Default"))
+2 -2
View File
@@ -10,7 +10,7 @@ pub struct ButtonStory;
impl Render for ButtonStory {
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();
Story::container()
@@ -139,7 +139,7 @@ impl Render for ButtonStory {
.child(
Button::new("Label")
.variant(ButtonVariant::Ghost)
.on_click(|_, cx| println!("Button clicked.")),
.on_click(|_, _cx| println!("Button clicked.")),
)
}
}
@@ -10,11 +10,11 @@ fn build_menu(cx: &mut WindowContext, header: impl Into<SharedString>) -> View<C
ContextMenu::build(cx, |menu, _| {
menu.header(header)
.separator()
.entry("Print current time", |v, cx| {
.entry("Print current time", |_event, cx| {
println!("dispatching PrintCurrentTime action");
cx.dispatch_action(PrintCurrentDate.boxed_clone())
})
.entry("Print best foot", |v, cx| {
.entry("Print best foot", |_event, cx| {
cx.dispatch_action(PrintBestFood.boxed_clone())
})
})
@@ -25,7 +25,7 @@ pub struct ContextMenuStory;
impl Render for ContextMenuStory {
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()
.on_action(|_: &PrintCurrentDate, _| {
println!("printing unix time!");
+1 -1
View File
@@ -10,7 +10,7 @@ pub struct IconStory;
impl Render for IconStory {
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();
Story::container()
@@ -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)),
),
)
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ pub struct InputStory;
impl Render for InputStory {
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_for::<Input>())
.child(Story::label("Default"))
@@ -16,7 +16,7 @@ pub fn binding(key: &str) -> gpui::KeyBinding {
impl Render for KeybindingStory {
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);
Story::container()
+1 -1
View File
@@ -9,7 +9,7 @@ pub struct LabelStory;
impl Render for LabelStory {
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_for::<Label>())
.child(Story::label("Default"))
@@ -9,7 +9,7 @@ pub struct ListItemStory;
impl Render for ListItemStory {
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_for::<ListItem>())
.child(Story::label("Default"))
@@ -22,5 +22,13 @@ impl Render for ListItemStory {
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!");
},
),
)
}
}
+3 -3
View File
@@ -1,6 +1,6 @@
use gpui::{overlay, Action, AnyView, IntoElement, Overlay, Render, VisualContext};
use settings2::Settings;
use theme2::{ActiveTheme, ThemeSettings};
use settings::Settings;
use theme::ThemeSettings;
use crate::prelude::*;
use crate::{h_stack, v_stack, Color, KeyBinding, Label, LabelSize, StyledExt};
@@ -13,7 +13,7 @@ pub struct Tooltip {
impl Tooltip {
pub fn text(title: impl Into<SharedString>, cx: &mut WindowContext) -> AnyView {
cx.build_view(|cx| Self {
cx.build_view(|_cx| Self {
title: title.into(),
meta: None,
key_binding: None,
+1 -7
View File
@@ -5,7 +5,7 @@ pub use gpui::{
pub use crate::StyledExt;
pub use crate::{ButtonVariant, Color};
pub use theme2::ActiveTheme;
pub use theme::ActiveTheme;
use strum::EnumIter;
@@ -16,12 +16,6 @@ pub enum IconSide {
Right,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, EnumIter)]
pub enum OverflowStyle {
Hidden,
Wrap,
}
#[derive(Default, PartialEq, Copy, Clone, EnumIter, strum::Display)]
pub enum InteractionState {
/// An element that is enabled and not hovered, active, focused, or disabled.
+3 -3
View File
@@ -1,12 +1,12 @@
use gpui::{Styled, WindowContext};
use theme2::ActiveTheme;
use gpui::{px, Styled, WindowContext};
use crate::prelude::*;
use crate::{ElevationIndex, UITextSize};
fn elevated<E: Styled>(this: E, cx: &mut WindowContext, index: ElevationIndex) -> E {
this.bg(cx.theme().colors().elevated_surface_background)
.z_index(index.z_index())
.rounded_lg()
.rounded(px(8.))
.border()
.border_color(cx.theme().colors().border_variant)
.shadow(index.shadow())
+1 -1
View File
@@ -1,5 +1,5 @@
use gpui::{Hsla, WindowContext};
use theme2::ActiveTheme;
use theme::ActiveTheme;
#[derive(Default, PartialEq, Copy, Clone)]
pub enum Color {
-2
View File
@@ -11,8 +11,6 @@
#![doc = include_str!("../docs/hello-world.md")]
#![doc = include_str!("../docs/building-ui.md")]
#![doc = include_str!("../docs/todo.md")]
// TODO: Fix warnings instead of supressing.
#![allow(dead_code, unused_variables)]
mod components;
pub mod prelude;
+27 -28
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 {
let suffix = if distance < 0 { " from now" } else { " ago" };
let d = distance.abs();
let distance = distance.abs();
let minutes = d / 60;
let hours = d / 3600;
let days = d / 86400;
let months = d / 2592000;
let years = d / 31536000;
let minutes = distance / 60;
let hours = distance / 3_600;
let days = distance / 86_400;
let months = distance / 2_592_000;
let string = if d < 5 && include_seconds {
let string = if distance < 5 && include_seconds {
"less than 5 seconds".to_string()
} else if d < 10 && include_seconds {
} else if distance < 10 && include_seconds {
"less than 10 seconds".to_string()
} else if d < 20 && include_seconds {
} else if distance < 20 && include_seconds {
"less than 20 seconds".to_string()
} else if d < 40 && include_seconds {
} else if distance < 40 && include_seconds {
"half a minute".to_string()
} else if d < 60 && include_seconds {
} else if distance < 60 && include_seconds {
"less than a minute".to_string()
} else if d < 90 && include_seconds {
} else if distance < 90 && include_seconds {
"1 minute".to_string()
} else if d < 30 {
} else if distance < 30 {
"less than a minute".to_string()
} else if d < 90 {
} else if distance < 90 {
"1 minute".to_string()
} else if d < 2700 {
} else if distance < 2_700 {
format!("{} minutes", minutes)
} else if d < 5400 {
} else if distance < 5_400 {
"about 1 hour".to_string()
} else if d < 86400 {
} else if distance < 86_400 {
format!("about {} hours", hours)
} else if d < 172800 {
} else if distance < 172_800 {
"1 day".to_string()
} else if d < 2592000 {
} else if distance < 2_592_000 {
format!("{} days", days)
} else if d < 5184000 {
} else if distance < 5_184_000 {
"about 1 month".to_string()
} else if d < 7776000 {
} else if distance < 7_776_000 {
"about 2 months".to_string()
} else if d < 31540000 {
} else if distance < 31_540_000 {
format!("{} months", months)
} else if d < 39425000 {
} else if distance < 39_425_000 {
"about 1 year".to_string()
} else if d < 55195000 {
} else if distance < 55_195_000 {
"over 1 year".to_string()
} else if d < 63080000 {
} else if distance < 63_080_000 {
"almost 2 years".to_string()
} else {
let years = d / 31536000;
let remaining_months = (d % 31536000) / 2592000;
let years = distance / 31_536_000;
let remaining_months = (distance % 31_536_000) / 2_592_000;
if remaining_months < 3 {
format!("about {} years", years)
@@ -76,7 +75,7 @@ fn distance_string(distance: i64, include_seconds: bool, add_suffix: bool) -> St
};
if add_suffix {
return format!("{}{}", string, suffix);
format!("{}{}", string, suffix)
} else {
string
}
+1
View File
@@ -16,6 +16,7 @@ editor = { package = "editor2", path = "../editor2" }
fs = { package = "fs2", path = "../fs2" }
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
gpui = { package = "gpui2", path = "../gpui2" }
ui = { package = "ui2", path = "../ui2" }
db = { package = "db2", path = "../db2" }
install_cli = { package = "install_cli2", path = "../install_cli2" }
project = { package = "project2", path = "../project2" }
+16 -8
View File
@@ -1,13 +1,14 @@
use super::base_keymap_setting::BaseKeymap;
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
use gpui::{
actions, AppContext, DismissEvent, EventEmitter, FocusableView, IntoElement, Render, Task,
actions, AppContext, DismissEvent, EventEmitter, FocusableView, ParentElement, Render, Task,
View, ViewContext, VisualContext, WeakView,
};
use picker::{simple_picker_match, Picker, PickerDelegate};
use picker::{Picker, PickerDelegate};
use project::Fs;
use settings::{update_settings_file, Settings};
use std::sync::Arc;
use ui::ListItem;
use util::ResultExt;
use workspace::{ui::HighlightedLabel, Workspace};
@@ -97,6 +98,8 @@ impl BaseKeymapSelectorDelegate {
}
impl PickerDelegate for BaseKeymapSelectorDelegate {
type ListItem = ui::ListItem;
fn placeholder_text(&self) -> Arc<str> {
"Select a base keymap...".into()
}
@@ -188,13 +191,18 @@ impl PickerDelegate for BaseKeymapSelectorDelegate {
&self,
ix: usize,
selected: bool,
cx: &mut gpui::ViewContext<Picker<Self>>,
) -> gpui::AnyElement {
_cx: &mut gpui::ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let keymap_match = &self.matches[ix];
simple_picker_match(selected, cx, |_cx| {
HighlightedLabel::new(keymap_match.string.clone(), keymap_match.positions.clone())
.into_any_element()
})
Some(
ListItem::new(ix)
.selected(selected)
.inset(true)
.child(HighlightedLabel::new(
keymap_match.string.clone(),
keymap_match.positions.clone(),
)),
)
}
}
-4
View File
@@ -95,10 +95,6 @@ impl Render for ModalLayer {
.track_focus(&active_modal.focus_handle)
.child(
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| {
this.hide_modal(cx);
}))
+5 -6
View File
@@ -2640,12 +2640,11 @@ impl Workspace {
.flex_col()
.justify_end()
.gap_2()
.children(self.notifications.iter().map(|(_, _, notification)| {
div()
.on_any_mouse_down(|_, cx| cx.stop_propagation())
.on_any_mouse_up(|_, cx| cx.stop_propagation())
.child(notification.to_any())
})),
.children(
self.notifications
.iter()
.map(|(_, _, notification)| notification.to_any()),
),
)
}
}
+1
View File
@@ -140,6 +140,7 @@ tree-sitter-lua.workspace = true
tree-sitter-nix.workspace = true
tree-sitter-nu.workspace = true
tree-sitter-vue.workspace = true
tree-sitter-uiua.workspace = true
url = "2.2"
urlencoding = "2.1.2"
+12 -1
View File
@@ -17,6 +17,7 @@ mod json;
#[cfg(feature = "plugin_runtime")]
mod language_plugin;
mod lua;
mod nu;
mod php;
mod python;
mod ruby;
@@ -24,6 +25,7 @@ mod rust;
mod svelte;
mod tailwind;
mod typescript;
mod uiua;
mod vue;
mod yaml;
@@ -210,12 +212,21 @@ pub fn init(
language("elm", tree_sitter_elm::language(), vec![]);
language("glsl", tree_sitter_glsl::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(
"vue",
tree_sitter_vue::language(),
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"))]
+81
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(),
})
}
}
+55
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
}
}
+10
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"] },
]
@@ -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
@@ -0,0 +1,3 @@
[
(array)
] @indent
+2 -1
View File
@@ -21,7 +21,7 @@ audio = { package = "audio2", path = "../audio2" }
auto_update = { package = "auto_update2", path = "../auto_update2" }
# breadcrumbs = { path = "../breadcrumbs" }
call = { package = "call2", path = "../call2" }
# channel = { path = "../channel" }
channel = { package = "channel2", path = "../channel2" }
cli = { path = "../cli" }
collab_ui = { package = "collab_ui2", path = "../collab_ui2" }
collections = { path = "../collections" }
@@ -136,6 +136,7 @@ tree-sitter-lua.workspace = true
tree-sitter-nix.workspace = true
tree-sitter-nu.workspace = true
tree-sitter-vue.workspace = true
tree-sitter-uiua.workspace = true
url = "2.2"
urlencoding = "2.1.2"
+12 -1
View File
@@ -18,6 +18,7 @@ mod json;
#[cfg(feature = "plugin_runtime")]
mod language_plugin;
mod lua;
mod nu;
mod php;
mod python;
mod ruby;
@@ -25,6 +26,7 @@ mod rust;
mod svelte;
mod tailwind;
mod typescript;
mod uiua;
mod vue;
mod yaml;
@@ -211,12 +213,21 @@ pub fn init(
language("elm", tree_sitter_elm::language(), vec![]);
language("glsl", tree_sitter_glsl::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(
"vue",
tree_sitter_vue::language(),
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"))]
+55
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
}
}
+55
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
}
}
@@ -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"] },
]
@@ -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
@@ -0,0 +1,3 @@
[
(array)
] @indent
+2 -2
View File
@@ -188,7 +188,7 @@ fn main() {
let app_state = Arc::new(AppState {
languages,
client: client.clone(),
user_store,
user_store: user_store.clone(),
fs,
build_window_options,
call_factory: call::Call::new,
@@ -208,7 +208,7 @@ fn main() {
// outline::init(cx);
// project_symbols::init(cx);
project_panel::init(Assets, cx);
// channel::init(&client, user_store.clone(), cx);
channel::init(&client, user_store.clone(), cx);
// diagnostics::init(cx);
search::init(cx);
// semantic_index::init(fs.clone(), http.clone(), languages.clone(), cx);