Merge branch 'main' into ime-support-2

This commit is contained in:
Antonio Scandurra 2022-07-22 16:03:38 +02:00
commit 7b009c8bbe
209 changed files with 1859 additions and 9740 deletions

View file

@ -3,7 +3,7 @@ use editor::Editor;
use futures::StreamExt;
use gpui::{
actions, elements::*, platform::CursorStyle, Action, AppContext, Entity, ModelHandle,
MutableAppContext, RenderContext, View, ViewContext, ViewHandle,
MouseButton, MutableAppContext, RenderContext, View, ViewContext, ViewHandle,
};
use language::{LanguageRegistry, LanguageServerBinaryStatus};
use project::{LanguageServerProgress, Project};
@ -15,9 +15,9 @@ use workspace::{ItemHandle, StatusItemView, Workspace};
actions!(lsp_status, [ShowErrorMessage]);
const DOWNLOAD_ICON: &'static str = "icons/download-solid-14.svg";
const WARNING_ICON: &'static str = "icons/warning-solid-14.svg";
const DONE_ICON: &'static str = "icons/accept.svg";
const DOWNLOAD_ICON: &'static str = "icons/download_12.svg";
const WARNING_ICON: &'static str = "icons/triangle_exclamation_12.svg";
const DONE_ICON: &'static str = "icons/circle_check_12.svg";
pub enum Event {
ShowError { lsp_name: Arc<str>, error: String },
@ -317,7 +317,9 @@ impl View for ActivityIndicator {
if let Some(action) = action {
element = element
.with_cursor_style(CursorStyle::PointingHand)
.on_click(move |_, _, cx| cx.dispatch_any_action(action.boxed_clone()));
.on_click(MouseButton::Left, move |_, cx| {
cx.dispatch_any_action(action.boxed_clone())
});
}
element.boxed()

View file

@ -2,7 +2,7 @@ use crate::ViewReleaseNotes;
use gpui::{
elements::{Flex, MouseEventHandler, Padding, ParentElement, Svg, Text},
platform::{AppVersion, CursorStyle},
Element, Entity, View, ViewContext,
Element, Entity, MouseButton, View, ViewContext,
};
use menu::Cancel;
use settings::Settings;
@ -49,7 +49,7 @@ impl View for UpdateNotification {
.with_child(
MouseEventHandler::new::<Cancel, _, _>(0, cx, |state, _| {
let style = theme.dismiss_button.style_for(state, false);
Svg::new("icons/decline.svg")
Svg::new("icons/x_mark_thin_8.svg")
.with_color(style.color)
.constrained()
.with_width(style.icon_width)
@ -62,7 +62,7 @@ impl View for UpdateNotification {
.boxed()
})
.with_padding(Padding::uniform(5.))
.on_click(move |_, _, cx| cx.dispatch_action(Cancel))
.on_click(MouseButton::Left, move |_, cx| cx.dispatch_action(Cancel))
.aligned()
.constrained()
.with_height(cx.font_cache().line_height(theme.message.text.font_size))
@ -84,7 +84,9 @@ impl View for UpdateNotification {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(|_, _, cx| cx.dispatch_action(ViewReleaseNotes))
.on_click(MouseButton::Left, |_, cx| {
cx.dispatch_action(ViewReleaseNotes)
})
.boxed()
}
}

View file

@ -8,8 +8,8 @@ use gpui::{
elements::*,
platform::CursorStyle,
views::{ItemType, Select, SelectStyle},
AppContext, Entity, ModelHandle, MutableAppContext, RenderContext, Subscription, Task, View,
ViewContext, ViewHandle,
AppContext, Entity, ModelHandle, MouseButton, MutableAppContext, RenderContext, Subscription,
Task, View, ViewContext, ViewHandle,
};
use menu::Confirm;
use postage::prelude::Stream;
@ -320,7 +320,7 @@ impl ChatPanel {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(move |_, _, cx| {
.on_click(MouseButton::Left, move |_, cx| {
let rpc = rpc.clone();
let this = this.clone();
cx.spawn(|mut cx| async move {

View file

@ -115,10 +115,10 @@ impl PickerDelegate for ContactFinder {
let icon_path = match request_status {
ContactRequestStatus::None | ContactRequestStatus::RequestReceived => {
"icons/accept.svg"
"icons/check_8.svg"
}
ContactRequestStatus::RequestSent | ContactRequestStatus::RequestAccepted => {
"icons/decline.svg"
"icons/x_mark_8.svg"
}
};
let button_style = if self.user_store.read(cx).is_contact_request_pending(&user) {

View file

@ -13,8 +13,9 @@ use gpui::{
geometry::{rect::RectF, vector::vec2f},
impl_actions, impl_internal_actions,
platform::CursorStyle,
AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, MutableAppContext,
RenderContext, Subscription, View, ViewContext, ViewHandle, WeakModelHandle, WeakViewHandle,
AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, MouseButton,
MutableAppContext, RenderContext, Subscription, View, ViewContext, ViewHandle, WeakModelHandle,
WeakViewHandle,
};
use join_project_notification::JoinProjectNotification;
use menu::{Confirm, SelectNext, SelectPrev};
@ -281,9 +282,9 @@ impl ContactsPanel {
Flex::row()
.with_child(
Svg::new(if is_collapsed {
"icons/disclosure-closed.svg"
"icons/chevron_right_8.svg"
} else {
"icons/disclosure-open.svg"
"icons/chevron_down_8.svg"
})
.with_color(header_style.text.color)
.constrained()
@ -310,7 +311,9 @@ impl ContactsPanel {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(move |_, _, cx| cx.dispatch_action(ToggleExpanded(section)))
.on_click(MouseButton::Left, move |_, cx| {
cx.dispatch_action(ToggleExpanded(section))
})
.boxed()
}
@ -433,7 +436,7 @@ impl ContactsPanel {
if is_going_offline {
icon_style.color = theme.disabled_button.color;
}
render_icon_button(&icon_style, "icons/lock-8.svg")
render_icon_button(&icon_style, "icons/lock_8.svg")
.aligned()
.boxed()
},
@ -445,7 +448,7 @@ impl ContactsPanel {
Some(
button
.with_cursor_style(CursorStyle::PointingHand)
.on_click(move |_, _, cx| {
.on_click(MouseButton::Left, move |_, cx| {
cx.dispatch_action(ToggleProjectOnline {
project: Some(open_project.clone()),
})
@ -499,7 +502,7 @@ impl ContactsPanel {
} else {
CursorStyle::Arrow
})
.on_click(move |_, _, cx| {
.on_click(MouseButton::Left, move |_, cx| {
if !is_host {
cx.dispatch_global_action(JoinProject {
contact: contact.clone(),
@ -551,7 +554,7 @@ impl ContactsPanel {
if is_going_online {
style.color = theme.disabled_button.color;
}
render_icon_button(&style, "icons/lock-8.svg")
render_icon_button(&style, "icons/lock_8.svg")
.aligned()
.constrained()
.with_width(host_avatar_height)
@ -563,7 +566,7 @@ impl ContactsPanel {
} else {
button
.with_cursor_style(CursorStyle::PointingHand)
.on_click(move |_, _, cx| {
.on_click(MouseButton::Left, move |_, cx| {
let project = project_handle.upgrade(cx.deref_mut());
cx.dispatch_action(ToggleProjectOnline { project })
})
@ -640,13 +643,13 @@ impl ContactsPanel {
} else {
&theme.contact_button.style_for(mouse_state, false)
};
render_icon_button(button_style, "icons/decline.svg")
render_icon_button(button_style, "icons/x_mark_8.svg")
.aligned()
// .flex_float()
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(move |_, _, cx| {
.on_click(MouseButton::Left, move |_, cx| {
cx.dispatch_action(RespondToContactRequest {
user_id,
accept: false,
@ -662,13 +665,13 @@ impl ContactsPanel {
} else {
&theme.contact_button.style_for(mouse_state, false)
};
render_icon_button(button_style, "icons/accept.svg")
render_icon_button(button_style, "icons/check_8.svg")
.aligned()
.flex_float()
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(move |_, _, cx| {
.on_click(MouseButton::Left, move |_, cx| {
cx.dispatch_action(RespondToContactRequest {
user_id,
accept: true,
@ -684,14 +687,16 @@ impl ContactsPanel {
} else {
&theme.contact_button.style_for(mouse_state, false)
};
render_icon_button(button_style, "icons/decline.svg")
render_icon_button(button_style, "icons/x_mark_8.svg")
.aligned()
.flex_float()
.boxed()
})
.with_padding(Padding::uniform(2.))
.with_cursor_style(CursorStyle::PointingHand)
.on_click(move |_, _, cx| cx.dispatch_action(RemoveContact(user_id)))
.on_click(MouseButton::Left, move |_, cx| {
cx.dispatch_action(RemoveContact(user_id))
})
.flex_float()
.boxed(),
);
@ -1068,17 +1073,19 @@ impl View for ContactsPanel {
)
.with_child(
MouseEventHandler::new::<AddContact, _, _>(0, cx, |_, _| {
Svg::new("icons/add-contact.svg")
Svg::new("icons/user_plus_16.svg")
.with_color(theme.add_contact_button.color)
.constrained()
.with_height(12.)
.with_height(16.)
.contained()
.with_style(theme.add_contact_button.container)
.aligned()
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(|_, _, cx| cx.dispatch_action(contact_finder::Toggle))
.on_click(MouseButton::Left, |_, cx| {
cx.dispatch_action(contact_finder::Toggle)
})
.boxed(),
)
.constrained()
@ -1126,7 +1133,7 @@ impl View for ContactsPanel {
},
)
.with_cursor_style(CursorStyle::PointingHand)
.on_click(move |_, _, cx| {
.on_click(MouseButton::Left, move |_, cx| {
cx.write_to_clipboard(ClipboardItem::new(
info.url.to_string(),
));

View file

@ -3,7 +3,7 @@ use client::User;
use gpui::{
elements::{Flex, Image, Label, MouseEventHandler, Padding, ParentElement, Text},
platform::CursorStyle,
Action, Element, ElementBox, RenderContext, View,
Action, Element, ElementBox, MouseButton, RenderContext, View,
};
use settings::Settings;
use std::sync::Arc;
@ -55,13 +55,15 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
MouseEventHandler::new::<Dismiss, _, _>(user.id as usize, cx, |state, _| {
render_icon_button(
theme.dismiss_button.style_for(state, false),
"icons/decline.svg",
"icons/x_mark_thin_8.svg",
)
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
.with_padding(Padding::uniform(5.))
.on_click(move |_, _, cx| cx.dispatch_any_action(dismiss_action.boxed_clone()))
.on_click(MouseButton::Left, move |_, cx| {
cx.dispatch_any_action(dismiss_action.boxed_clone())
})
.aligned()
.constrained()
.with_height(
@ -96,7 +98,9 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(move |_, _, cx| cx.dispatch_any_action(action.boxed_clone()))
.on_click(MouseButton::Left, move |_, cx| {
cx.dispatch_any_action(action.boxed_clone())
})
.boxed()
},
))

View file

@ -1,7 +1,7 @@
use gpui::{
elements::*, geometry::vector::Vector2F, impl_internal_actions, keymap, platform::CursorStyle,
Action, AppContext, Axis, Entity, MutableAppContext, RenderContext, SizeConstraint,
Subscription, View, ViewContext,
Action, AppContext, Axis, Entity, MouseButton, MutableAppContext, RenderContext,
SizeConstraint, Subscription, View, ViewContext,
};
use menu::*;
use settings::Settings;
@ -337,7 +337,7 @@ impl ContextMenu {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(move |_, _, cx| {
.on_click(MouseButton::Left, move |_, cx| {
cx.dispatch_action(Clicked);
cx.dispatch_any_action(action.boxed_clone());
})
@ -355,7 +355,7 @@ impl ContextMenu {
.with_style(style.container)
.boxed()
})
.on_mouse_down_out(|_, cx| cx.dispatch_action(Cancel))
.on_right_mouse_down_out(|_, cx| cx.dispatch_action(Cancel))
.on_down_out(MouseButton::Left, |_, cx| cx.dispatch_action(Cancel))
.on_down_out(MouseButton::Right, |_, cx| cx.dispatch_action(Cancel))
}
}

View file

@ -626,10 +626,10 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
let font_size = (style.text_scale_factor * settings.buffer_font_size).round();
let icon_width = cx.em_width * style.icon_width_factor;
let icon = if diagnostic.severity == DiagnosticSeverity::ERROR {
Svg::new("icons/diagnostic-error-10.svg")
Svg::new("icons/circle_x_mark_12.svg")
.with_color(theme.error_diagnostic.message.text.color)
} else {
Svg::new("icons/diagnostic-warning-10.svg")
Svg::new("icons/triangle_exclamation_12.svg")
.with_color(theme.warning_diagnostic.message.text.color)
};
@ -682,7 +682,7 @@ pub(crate) fn render_summary(
let summary_spacing = theme.tab_summary_spacing;
Flex::row()
.with_children([
Svg::new("icons/diagnostic-summary-error.svg")
Svg::new("icons/circle_x_mark_12.svg")
.with_color(text_style.color)
.constrained()
.with_width(icon_width)
@ -699,7 +699,7 @@ pub(crate) fn render_summary(
)
.aligned()
.boxed(),
Svg::new("icons/diagnostic-summary-warning.svg")
Svg::new("icons/triangle_exclamation_12.svg")
.with_color(text_style.color)
.constrained()
.with_width(icon_width)

View file

@ -1,8 +1,8 @@
use collections::HashSet;
use editor::{Editor, GoToNextDiagnostic};
use gpui::{
elements::*, platform::CursorStyle, serde_json, Entity, ModelHandle, MutableAppContext,
RenderContext, Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
elements::*, platform::CursorStyle, serde_json, Entity, ModelHandle, MouseButton,
MutableAppContext, RenderContext, Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
};
use language::Diagnostic;
use project::Project;
@ -101,7 +101,7 @@ impl View for DiagnosticIndicator {
let mut summary_row = Flex::row();
if self.summary.error_count > 0 {
summary_row.add_children([
Svg::new("icons/error-solid-14.svg")
Svg::new("icons/circle_x_mark_16.svg")
.with_color(style.icon_color_error)
.constrained()
.with_width(style.icon_width)
@ -117,7 +117,7 @@ impl View for DiagnosticIndicator {
if self.summary.warning_count > 0 {
summary_row.add_children([
Svg::new("icons/warning-solid-14.svg")
Svg::new("icons/triangle_exclamation_16.svg")
.with_color(style.icon_color_warning)
.constrained()
.with_width(style.icon_width)
@ -138,7 +138,7 @@ impl View for DiagnosticIndicator {
if self.summary.error_count == 0 && self.summary.warning_count == 0 {
summary_row.add_child(
Svg::new("icons/no-error-solid-14.svg")
Svg::new("icons/circle_check_16.svg")
.with_color(style.icon_color_ok)
.constrained()
.with_width(style.icon_width)
@ -161,7 +161,7 @@ impl View for DiagnosticIndicator {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(|_, _, cx| cx.dispatch_action(crate::Deploy))
.on_click(MouseButton::Left, |_, cx| cx.dispatch_action(crate::Deploy))
.with_tooltip::<Summary, _>(
0,
"Project Diagnostics".to_string(),
@ -201,7 +201,9 @@ impl View for DiagnosticIndicator {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(|_, _, cx| cx.dispatch_action(GoToNextDiagnostic))
.on_click(MouseButton::Left, |_, cx| {
cx.dispatch_action(GoToNextDiagnostic)
})
.boxed(),
);
}

View file

@ -30,8 +30,8 @@ use gpui::{
impl_actions, impl_internal_actions,
platform::CursorStyle,
text_layout, AppContext, AsyncAppContext, ClipboardItem, Element, ElementBox, Entity,
ModelHandle, MutableAppContext, RenderContext, Subscription, Task, View, ViewContext,
ViewHandle, WeakViewHandle,
ModelHandle, MouseButton, MutableAppContext, RenderContext, Subscription, Task, View,
ViewContext, ViewHandle, WeakViewHandle,
};
use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HoverState};
@ -705,7 +705,7 @@ impl CompletionsMenu {
},
)
.with_cursor_style(CursorStyle::PointingHand)
.on_mouse_down(move |_, cx| {
.on_down(MouseButton::Left, move |_, cx| {
cx.dispatch_action(ConfirmCompletion {
item_ix: Some(item_ix),
});
@ -838,7 +838,7 @@ impl CodeActionsMenu {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
.on_mouse_down(move |_, cx| {
.on_down(MouseButton::Left, move |_, cx| {
cx.dispatch_action(ConfirmCodeAction {
item_ix: Some(item_ix),
});
@ -2664,13 +2664,13 @@ impl Editor {
enum Tag {}
Some(
MouseEventHandler::new::<Tag, _, _>(0, cx, |_, _| {
Svg::new("icons/zap.svg")
Svg::new("icons/bolt_8.svg")
.with_color(style.code_actions_indicator)
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
.with_padding(Padding::uniform(3.))
.on_mouse_down(|_, cx| {
.on_down(MouseButton::Left, |_, cx| {
cx.dispatch_action(ToggleCodeActions {
deployed_from_indicator: true,
});

View file

@ -25,7 +25,7 @@ use gpui::{
platform::CursorStyle,
text_layout::{self, Line, RunStyle, TextLayoutCache},
AppContext, Axis, Border, CursorRegion, Element, ElementBox, Event, EventContext,
LayoutContext, ModifiersChangedEvent, MouseButton, MouseEvent, MouseMovedEvent,
LayoutContext, ModifiersChangedEvent, MouseButton, MouseButtonEvent, MouseMovedEvent,
MutableAppContext, PaintContext, Quad, Scene, ScrollWheelEvent, SizeConstraint, ViewContext,
WeakViewHandle,
};
@ -940,7 +940,7 @@ impl EditorElement {
cx.render(&editor, |_, cx| {
MouseEventHandler::new::<JumpIcon, _, _>(*key, cx, |state, _| {
let style = style.jump_icon.style_for(state, false);
Svg::new("icons/jump.svg")
Svg::new("icons/arrow_up_right_8.svg")
.with_color(style.color)
.constrained()
.with_width(style.icon_width)
@ -953,7 +953,9 @@ impl EditorElement {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(move |_, _, cx| cx.dispatch_action(jump_action.clone()))
.on_click(MouseButton::Left, move |_, cx| {
cx.dispatch_action(jump_action.clone())
})
.with_tooltip::<JumpIcon, _>(
*key,
"Jump to Buffer".to_string(),
@ -1468,7 +1470,7 @@ impl Element for EditorElement {
}
match event {
Event::MouseDown(MouseEvent {
Event::MouseDown(MouseButtonEvent {
button: MouseButton::Left,
position,
cmd,
@ -1486,12 +1488,12 @@ impl Element for EditorElement {
paint,
cx,
),
Event::MouseDown(MouseEvent {
Event::MouseDown(MouseButtonEvent {
button: MouseButton::Right,
position,
..
}) => self.mouse_right_down(*position, layout, paint, cx),
Event::MouseUp(MouseEvent {
Event::MouseUp(MouseButtonEvent {
button: MouseButton::Left,
position,
..
@ -1662,12 +1664,12 @@ impl PaintState {
} else {
0
};
let column_overshoot = (0f32.max(x - line.width()) / layout.em_advance) as u32;
(
DisplayPoint::new(row, column),
DisplayPoint::new(row_overshoot, column_overshoot),
)
let point = snapshot.clip_point(DisplayPoint::new(row, column), Bias::Left);
let mut column_overshoot = (0f32.max(x - line.width()) / layout.em_advance) as u32;
column_overshoot = column_overshoot + column - point.column();
(point, DisplayPoint::new(row_overshoot, column_overshoot))
}
}

View file

@ -7,7 +7,9 @@ use settings::Settings;
use util::TryFutureExt;
use workspace::Workspace;
use crate::{Anchor, DisplayPoint, Editor, EditorSnapshot, GoToDefinition, Select, SelectPhase};
use crate::{
Anchor, DisplayPoint, Editor, EditorSnapshot, Event, GoToDefinition, Select, SelectPhase,
};
#[derive(Clone, PartialEq)]
pub struct UpdateGoToDefinitionLink {
@ -276,6 +278,13 @@ pub fn go_to_fetched_definition(
});
if !definitions.is_empty() {
editor_handle.update(cx, |editor, cx| {
if !editor.focused {
cx.focus_self();
cx.emit(Event::Activate);
}
});
Editor::navigate_to_definitions(workspace, editor_handle, definitions, cx);
} else {
editor_handle.update(cx, |editor, cx| {

View file

@ -2,7 +2,7 @@ use context_menu::ContextMenuItem;
use gpui::{geometry::vector::Vector2F, impl_internal_actions, MutableAppContext, ViewContext};
use crate::{
DisplayPoint, Editor, EditorMode, FindAllReferences, GoToDefinition, Rename, SelectMode,
DisplayPoint, Editor, EditorMode, Event, FindAllReferences, GoToDefinition, Rename, SelectMode,
ToggleCodeActions,
};
@ -23,6 +23,11 @@ pub fn deploy_context_menu(
&DeployMouseContextMenu { position, point }: &DeployMouseContextMenu,
cx: &mut ViewContext<Editor>,
) {
if !editor.focused {
cx.focus_self();
cx.emit(Event::Activate);
}
// Don't show context menu for inline editors
if editor.mode() != EditorMode::Full {
return;

View file

@ -5581,7 +5581,7 @@ impl RefCounts {
#[cfg(test)]
mod tests {
use super::*;
use crate::{actions, elements::*, impl_actions, MouseButton, MouseEvent};
use crate::{actions, elements::*, impl_actions, MouseButton, MouseButtonEvent};
use serde::Deserialize;
use smol::future::poll_once;
use std::{
@ -5934,7 +5934,7 @@ mod tests {
let presenter = cx.presenters_and_platform_windows[&window_id].0.clone();
// Ensure window's root element is in a valid lifecycle state.
presenter.borrow_mut().dispatch_event(
Event::MouseDown(MouseEvent {
Event::MouseDown(MouseButtonEvent {
position: Default::default(),
button: MouseButton::Left,
ctrl: false,

View file

@ -1,11 +1,11 @@
use crate::{
geometry::vector::Vector2F, presenter::MeasurementContext, CursorRegion, DebugContext, Element,
ElementBox, Event, EventContext, LayoutContext, MouseButton, MouseEvent, MouseRegion,
ElementBox, Event, EventContext, LayoutContext, MouseButton, MouseButtonEvent, MouseRegion,
NavigationDirection, PaintContext, SizeConstraint,
};
use pathfinder_geometry::rect::RectF;
use serde_json::json;
use std::{any::TypeId, ops::Range, rc::Rc};
use std::{any::TypeId, ops::Range};
pub struct EventHandler {
child: ElementBox,
@ -82,19 +82,11 @@ impl Element for EventHandler {
bounds: visible_bounds,
style: Default::default(),
});
cx.scene.push_mouse_region(MouseRegion {
view_id: cx.current_view_id(),
discriminant: Some(discriminant),
bounds: visible_bounds,
hover: Some(Rc::new(|_, _, _| {})),
mouse_down: Some(Rc::new(|_, _| {})),
click: Some(Rc::new(|_, _, _| {})),
right_mouse_down: Some(Rc::new(|_, _| {})),
right_click: Some(Rc::new(|_, _, _| {})),
drag: Some(Rc::new(|_, _, _| {})),
mouse_down_out: Some(Rc::new(|_, _| {})),
right_mouse_down_out: Some(Rc::new(|_, _| {})),
});
cx.scene.push_mouse_region(MouseRegion::handle_all(
cx.current_view_id(),
Some(discriminant),
visible_bounds,
));
cx.scene.pop_stacking_context();
}
self.child.paint(bounds.origin(), visible_bounds, cx);
@ -117,7 +109,7 @@ impl Element for EventHandler {
true
} else {
match event {
Event::MouseDown(MouseEvent {
Event::MouseDown(MouseButtonEvent {
button: MouseButton::Left,
position,
..
@ -129,7 +121,7 @@ impl Element for EventHandler {
}
false
}
Event::MouseDown(MouseEvent {
Event::MouseDown(MouseButtonEvent {
button: MouseButton::Right,
position,
..
@ -141,7 +133,7 @@ impl Element for EventHandler {
}
false
}
Event::MouseDown(MouseEvent {
Event::MouseDown(MouseButtonEvent {
button: MouseButton::Navigate(direction),
position,
..

View file

@ -1,5 +1,3 @@
use std::{any::TypeId, ops::Range, rc::Rc};
use super::Padding;
use crate::{
geometry::{
@ -7,25 +5,19 @@ use crate::{
vector::{vec2f, Vector2F},
},
platform::CursorStyle,
scene::CursorRegion,
DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, MouseRegion,
MouseState, PaintContext, RenderContext, SizeConstraint, View, presenter::MeasurementContext,
scene::{CursorRegion, HandlerSet},
DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, MeasurementContext,
MouseButton, MouseButtonEvent, MouseMovedEvent, MouseRegion, MouseState, PaintContext,
RenderContext, SizeConstraint, View,
};
use serde_json::json;
use std::{any::TypeId, ops::Range};
pub struct MouseEventHandler {
child: ElementBox,
tag: TypeId,
id: usize,
discriminant: (TypeId, usize),
cursor_style: Option<CursorStyle>,
mouse_down: Option<Rc<dyn Fn(Vector2F, &mut EventContext)>>,
click: Option<Rc<dyn Fn(Vector2F, usize, &mut EventContext)>>,
right_mouse_down: Option<Rc<dyn Fn(Vector2F, &mut EventContext)>>,
right_click: Option<Rc<dyn Fn(Vector2F, usize, &mut EventContext)>>,
mouse_down_out: Option<Rc<dyn Fn(Vector2F, &mut EventContext)>>,
right_mouse_down_out: Option<Rc<dyn Fn(Vector2F, &mut EventContext)>>,
drag: Option<Rc<dyn Fn(Vector2F, Vector2F, &mut EventContext)>>,
hover: Option<Rc<dyn Fn(Vector2F, bool, &mut EventContext)>>,
handlers: HandlerSet,
padding: Padding,
}
@ -37,18 +29,10 @@ impl MouseEventHandler {
F: FnOnce(MouseState, &mut RenderContext<V>) -> ElementBox,
{
Self {
id,
tag: TypeId::of::<Tag>(),
child: render_child(cx.mouse_state::<Tag>(id), cx),
cursor_style: None,
mouse_down: None,
click: None,
right_mouse_down: None,
right_click: None,
mouse_down_out: None,
right_mouse_down_out: None,
drag: None,
hover: None,
discriminant: (TypeId::of::<Tag>(), id),
handlers: Default::default(),
padding: Default::default(),
}
}
@ -58,67 +42,47 @@ impl MouseEventHandler {
self
}
pub fn on_mouse_down(
pub fn on_down(
mut self,
handler: impl Fn(Vector2F, &mut EventContext) + 'static,
button: MouseButton,
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
) -> Self {
self.mouse_down = Some(Rc::new(handler));
self.handlers = self.handlers.on_down(button, handler);
self
}
pub fn on_click(
mut self,
handler: impl Fn(Vector2F, usize, &mut EventContext) + 'static,
button: MouseButton,
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
) -> Self {
self.click = Some(Rc::new(handler));
self.handlers = self.handlers.on_click(button, handler);
self
}
pub fn on_right_mouse_down(
pub fn on_down_out(
mut self,
handler: impl Fn(Vector2F, &mut EventContext) + 'static,
button: MouseButton,
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
) -> Self {
self.right_mouse_down = Some(Rc::new(handler));
self
}
pub fn on_right_click(
mut self,
handler: impl Fn(Vector2F, usize, &mut EventContext) + 'static,
) -> Self {
self.right_click = Some(Rc::new(handler));
self
}
pub fn on_mouse_down_out(
mut self,
handler: impl Fn(Vector2F, &mut EventContext) + 'static,
) -> Self {
self.mouse_down_out = Some(Rc::new(handler));
self
}
pub fn on_right_mouse_down_out(
mut self,
handler: impl Fn(Vector2F, &mut EventContext) + 'static,
) -> Self {
self.right_mouse_down_out = Some(Rc::new(handler));
self.handlers = self.handlers.on_down_out(button, handler);
self
}
pub fn on_drag(
mut self,
handler: impl Fn(Vector2F, Vector2F, &mut EventContext) + 'static,
button: MouseButton,
handler: impl Fn(Vector2F, MouseMovedEvent, &mut EventContext) + 'static,
) -> Self {
self.drag = Some(Rc::new(handler));
self.handlers = self.handlers.on_drag(button, handler);
self
}
pub fn on_hover(
mut self,
handler: impl Fn(Vector2F, bool, &mut EventContext) + 'static,
handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static,
) -> Self {
self.hover = Some(Rc::new(handler));
self.handlers = self.handlers.on_hover(handler);
self
}
@ -163,19 +127,12 @@ impl Element for MouseEventHandler {
});
}
cx.scene.push_mouse_region(MouseRegion {
view_id: cx.current_view_id(),
discriminant: Some((self.tag, self.id)),
bounds: hit_bounds,
hover: self.hover.clone(),
click: self.click.clone(),
mouse_down: self.mouse_down.clone(),
right_click: self.right_click.clone(),
right_mouse_down: self.right_mouse_down.clone(),
mouse_down_out: self.mouse_down_out.clone(),
right_mouse_down_out: self.right_mouse_down_out.clone(),
drag: self.drag.clone(),
});
cx.scene.push_mouse_region(MouseRegion::from_handlers(
cx.current_view_id(),
Some(self.discriminant.clone()),
hit_bounds,
self.handlers.clone(),
));
self.child.paint(bounds.origin(), visible_bounds, cx);
}

View file

@ -7,8 +7,8 @@ use crate::{
geometry::{rect::RectF, vector::Vector2F},
json::json,
presenter::MeasurementContext,
Action, Axis, ElementStateHandle, LayoutContext, PaintContext, RenderContext, SizeConstraint,
Task, View,
Action, Axis, ElementStateHandle, LayoutContext, MouseMovedEvent, PaintContext, RenderContext,
SizeConstraint, Task, View,
};
use serde::Deserialize;
use std::{
@ -93,7 +93,7 @@ impl Tooltip {
};
let child =
MouseEventHandler::new::<MouseEventHandlerState<Tag>, _, _>(id, cx, |_, _| child)
.on_hover(move |position, hover, cx| {
.on_hover(move |hover, MouseMovedEvent { position, .. }, cx| {
let window_id = cx.window_id();
if let Some(view_id) = cx.view_id() {
if hover {

View file

@ -19,20 +19,26 @@ pub struct ModifiersChangedEvent {
pub cmd: bool,
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Default)]
pub struct ScrollWheelEvent {
pub position: Vector2F,
pub delta: Vector2F,
pub precise: bool,
}
#[derive(Copy, Clone, Debug)]
#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
pub enum NavigationDirection {
Back,
Forward,
}
#[derive(Copy, Clone, Debug)]
impl Default for NavigationDirection {
fn default() -> Self {
Self::Back
}
}
#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
pub enum MouseButton {
Left,
Right,
@ -40,8 +46,26 @@ pub enum MouseButton {
Navigate(NavigationDirection),
}
#[derive(Clone, Debug)]
pub struct MouseEvent {
impl MouseButton {
pub fn all() -> Vec<Self> {
vec![
MouseButton::Left,
MouseButton::Right,
MouseButton::Middle,
MouseButton::Navigate(NavigationDirection::Back),
MouseButton::Navigate(NavigationDirection::Forward),
]
}
}
impl Default for MouseButton {
fn default() -> Self {
Self::Left
}
}
#[derive(Clone, Debug, Default)]
pub struct MouseButtonEvent {
pub button: MouseButton,
pub position: Vector2F,
pub ctrl: bool,
@ -51,7 +75,7 @@ pub struct MouseEvent {
pub click_count: usize,
}
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, Default)]
pub struct MouseMovedEvent {
pub position: Vector2F,
pub pressed_button: Option<MouseButton>,
@ -66,8 +90,8 @@ pub enum Event {
KeyDown(KeyDownEvent),
KeyUp(KeyUpEvent),
ModifiersChanged(ModifiersChangedEvent),
MouseDown(MouseEvent),
MouseUp(MouseEvent),
MouseDown(MouseButtonEvent),
MouseUp(MouseButtonEvent),
MouseMoved(MouseMovedEvent),
ScrollWheel(ScrollWheelEvent),
}

View file

@ -2,8 +2,8 @@ use crate::{
geometry::vector::vec2f,
keymap::Keystroke,
platform::{Event, NavigationDirection},
KeyDownEvent, KeyUpEvent, ModifiersChangedEvent, MouseButton, MouseEvent, MouseMovedEvent,
ScrollWheelEvent,
KeyDownEvent, KeyUpEvent, ModifiersChangedEvent, MouseButton, MouseButtonEvent,
MouseMovedEvent, ScrollWheelEvent,
};
use cocoa::{
appkit::{NSEvent, NSEventModifierFlags, NSEventType},
@ -104,7 +104,7 @@ impl Event {
let modifiers = native_event.modifierFlags();
window_height.map(|window_height| {
Self::MouseDown(MouseEvent {
Self::MouseDown(MouseButtonEvent {
button,
position: vec2f(
native_event.locationInWindow().x as f32,
@ -133,7 +133,7 @@ impl Event {
window_height.map(|window_height| {
let modifiers = native_event.modifierFlags();
Self::MouseUp(MouseEvent {
Self::MouseUp(MouseButtonEvent {
button,
position: vec2f(
native_event.locationInWindow().x as f32,

View file

@ -7,8 +7,8 @@ use crate::{
},
keymap::Keystroke,
platform::{self, Event, WindowBounds, WindowContext},
InputHandler, KeyDownEvent, ModifiersChangedEvent, MouseButton, MouseEvent, MouseMovedEvent,
Scene,
InputHandler, KeyDownEvent, ModifiersChangedEvent, MouseButton, MouseButtonEvent,
MouseMovedEvent, Scene,
};
use block::ConcreteBlock;
use cocoa::{
@ -768,7 +768,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
))
.detach();
}
Event::MouseUp(MouseEvent {
Event::MouseUp(MouseButtonEvent {
button: MouseButton::Left,
..
}) => {

View file

@ -6,10 +6,10 @@ use crate::{
json::{self, ToJson},
keymap::Keystroke,
platform::{CursorStyle, Event},
scene::CursorRegion,
scene::{CursorRegion, MouseRegionEvent},
text_layout::TextLayoutCache,
Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AssetCache, ElementBox, Entity,
FontSystem, ModelHandle, MouseButton, MouseEvent, MouseMovedEvent, MouseRegion, MouseRegionId,
FontSystem, ModelHandle, MouseButtonEvent, MouseMovedEvent, MouseRegion, MouseRegionId,
ReadModel, ReadView, RenderContext, RenderParams, Scene, UpgradeModelHandle, UpgradeViewHandle,
View, ViewHandle, WeakModelHandle, WeakViewHandle,
};
@ -241,105 +241,57 @@ impl Presenter {
let mut mouse_down_out_handlers = Vec::new();
let mut mouse_down_region = None;
let mut clicked_region = None;
let mut right_mouse_down_region = None;
let mut right_clicked_region = None;
let mut dragged_region = None;
match event {
Event::MouseDown(MouseEvent {
position,
button: MouseButton::Left,
..
}) => {
match &event {
Event::MouseDown(
e @ MouseButtonEvent {
position, button, ..
},
) => {
let mut hit = false;
for (region, _) in self.mouse_regions.iter().rev() {
if region.bounds.contains_point(position) {
if region.bounds.contains_point(*position) {
if !hit {
hit = true;
invalidated_views.push(region.view_id);
mouse_down_region = Some((region.clone(), position));
mouse_down_region =
Some((region.clone(), MouseRegionEvent::Down(e.clone())));
self.clicked_region = Some(region.clone());
self.prev_drag_position = Some(position);
self.prev_drag_position = Some(*position);
}
} else if let Some(handler) = region.mouse_down_out.clone() {
mouse_down_out_handlers.push((handler, region.view_id, position));
} else if let Some(handler) = region
.handlers
.get(&(MouseRegionEvent::down_out_disc(), Some(*button)))
{
mouse_down_out_handlers.push((
handler,
region.view_id,
MouseRegionEvent::DownOut(e.clone()),
));
}
}
}
Event::MouseUp(MouseEvent {
position,
click_count,
button: MouseButton::Left,
..
}) => {
Event::MouseUp(e @ MouseButtonEvent { position, .. }) => {
self.prev_drag_position.take();
if let Some(region) = self.clicked_region.take() {
invalidated_views.push(region.view_id);
if region.bounds.contains_point(position) {
clicked_region = Some((region, position, click_count));
if region.bounds.contains_point(*position) {
clicked_region = Some((region, MouseRegionEvent::Click(e.clone())));
}
}
}
Event::MouseDown(MouseEvent {
position,
button: MouseButton::Right,
..
}) => {
let mut hit = false;
for (region, _) in self.mouse_regions.iter().rev() {
if region.bounds.contains_point(position) {
if !hit {
hit = true;
invalidated_views.push(region.view_id);
right_mouse_down_region = Some((region.clone(), position));
self.right_clicked_region = Some(region.clone());
}
} else if let Some(handler) = region.right_mouse_down_out.clone() {
mouse_down_out_handlers.push((handler, region.view_id, position));
}
}
}
Event::MouseUp(MouseEvent {
position,
click_count,
button: MouseButton::Right,
..
}) => {
if let Some(region) = self.right_clicked_region.take() {
invalidated_views.push(region.view_id);
if region.bounds.contains_point(position) {
right_clicked_region = Some((region, position, click_count));
}
}
}
Event::MouseMoved(MouseMovedEvent {
pressed_button,
position,
shift,
ctrl,
alt,
cmd,
..
}) => {
if let Some(MouseButton::Left) = pressed_button {
if let Some((clicked_region, prev_drag_position)) = self
.clicked_region
.as_ref()
.zip(self.prev_drag_position.as_mut())
{
dragged_region =
Some((clicked_region.clone(), *prev_drag_position, position));
*prev_drag_position = position;
}
self.last_mouse_moved_event = Some(Event::MouseMoved(MouseMovedEvent {
position,
pressed_button: Some(MouseButton::Left),
shift,
ctrl,
alt,
cmd,
}));
Event::MouseMoved(e @ MouseMovedEvent { position, .. }) => {
if let Some((clicked_region, prev_drag_position)) = self
.clicked_region
.as_ref()
.zip(self.prev_drag_position.as_mut())
{
dragged_region = Some((
clicked_region.clone(),
MouseRegionEvent::Drag(*prev_drag_position, e.clone()),
));
*prev_drag_position = *position;
}
self.last_mouse_moved_event = Some(event.clone());
@ -350,51 +302,39 @@ impl Presenter {
let (mut handled, mut event_cx) =
self.handle_hover_events(&event, &mut invalidated_views, cx);
for (handler, view_id, position) in mouse_down_out_handlers {
event_cx.with_current_view(view_id, |event_cx| handler(position, event_cx))
for (handler, view_id, region_event) in mouse_down_out_handlers {
event_cx.with_current_view(view_id, |event_cx| handler(region_event, event_cx))
}
if let Some((mouse_down_region, position)) = mouse_down_region {
if let Some((mouse_down_region, region_event)) = mouse_down_region {
handled = true;
if let Some(mouse_down_callback) = mouse_down_region.mouse_down {
if let Some(mouse_down_callback) =
mouse_down_region.handlers.get(&region_event.handler_key())
{
event_cx.with_current_view(mouse_down_region.view_id, |event_cx| {
mouse_down_callback(position, event_cx);
mouse_down_callback(region_event, event_cx);
})
}
}
if let Some((clicked_region, position, click_count)) = clicked_region {
if let Some((clicked_region, region_event)) = clicked_region {
handled = true;
if let Some(click_callback) = clicked_region.click {
if let Some(click_callback) =
clicked_region.handlers.get(&region_event.handler_key())
{
event_cx.with_current_view(clicked_region.view_id, |event_cx| {
click_callback(position, click_count, event_cx);
click_callback(region_event, event_cx);
})
}
}
if let Some((right_mouse_down_region, position)) = right_mouse_down_region {
if let Some((dragged_region, region_event)) = dragged_region {
handled = true;
if let Some(right_mouse_down_callback) = right_mouse_down_region.right_mouse_down {
event_cx.with_current_view(right_mouse_down_region.view_id, |event_cx| {
right_mouse_down_callback(position, event_cx);
})
}
}
if let Some((right_clicked_region, position, click_count)) = right_clicked_region {
handled = true;
if let Some(right_click_callback) = right_clicked_region.right_click {
event_cx.with_current_view(right_clicked_region.view_id, |event_cx| {
right_click_callback(position, click_count, event_cx);
})
}
}
if let Some((dragged_region, prev_position, position)) = dragged_region {
handled = true;
if let Some(drag_callback) = dragged_region.drag {
if let Some(drag_callback) =
dragged_region.handlers.get(&region_event.handler_key())
{
event_cx.with_current_view(dragged_region.view_id, |event_cx| {
drag_callback(prev_position, position, event_cx);
drag_callback(region_event, event_cx);
})
}
}
@ -431,14 +371,17 @@ impl Presenter {
invalidated_views: &mut Vec<usize>,
cx: &'a mut MutableAppContext,
) -> (bool, EventContext<'a>) {
let mut unhovered_regions = Vec::new();
let mut hovered_regions = Vec::new();
let mut hover_regions = Vec::new();
// let mut unhovered_regions = Vec::new();
// let mut hovered_regions = Vec::new();
if let Event::MouseMoved(MouseMovedEvent {
position,
pressed_button,
..
}) = event
if let Event::MouseMoved(
e @ MouseMovedEvent {
position,
pressed_button,
..
},
) = event
{
if let None = pressed_button {
let mut style_to_assign = CursorStyle::Arrow;
@ -459,7 +402,10 @@ impl Presenter {
if let Some(region_id) = region.id() {
if !self.hovered_region_ids.contains(&region_id) {
invalidated_views.push(region.view_id);
hovered_regions.push((region.clone(), position));
hover_regions.push((
region.clone(),
MouseRegionEvent::Hover(true, e.clone()),
));
self.hovered_region_ids.insert(region_id);
}
}
@ -467,7 +413,10 @@ impl Presenter {
if let Some(region_id) = region.id() {
if self.hovered_region_ids.contains(&region_id) {
invalidated_views.push(region.view_id);
unhovered_regions.push((region.clone(), position));
hover_regions.push((
region.clone(),
MouseRegionEvent::Hover(false, e.clone()),
));
self.hovered_region_ids.remove(&region_id);
}
}
@ -479,20 +428,11 @@ impl Presenter {
let mut event_cx = self.build_event_context(cx);
let mut handled = false;
for (unhovered_region, position) in unhovered_regions {
for (hover_region, region_event) in hover_regions {
handled = true;
if let Some(hover_callback) = unhovered_region.hover {
event_cx.with_current_view(unhovered_region.view_id, |event_cx| {
hover_callback(*position, false, event_cx);
})
}
}
for (hovered_region, position) in hovered_regions {
handled = true;
if let Some(hover_callback) = hovered_region.hover {
event_cx.with_current_view(hovered_region.view_id, |event_cx| {
hover_callback(*position, true, event_cx);
if let Some(hover_callback) = hover_region.handlers.get(&region_event.handler_key()) {
event_cx.with_current_view(hover_region.view_id, |event_cx| {
hover_callback(region_event, event_cx);
})
}
}

View file

@ -1,6 +1,8 @@
mod mouse_region;
use serde::Deserialize;
use serde_json::json;
use std::{any::TypeId, borrow::Cow, rc::Rc, sync::Arc};
use std::{borrow::Cow, sync::Arc};
use crate::{
color::Color,
@ -8,8 +10,9 @@ use crate::{
geometry::{rect::RectF, vector::Vector2F},
json::ToJson,
platform::CursorStyle,
EventContext, ImageData, MouseEvent, MouseMovedEvent, ScrollWheelEvent,
ImageData,
};
pub use mouse_region::*;
pub struct Scene {
scale_factor: f32,
@ -44,38 +47,6 @@ pub struct CursorRegion {
pub style: CursorStyle,
}
pub enum MouseRegionEvent {
Moved(MouseMovedEvent),
Hover(MouseEvent),
Down(MouseEvent),
Up(MouseEvent),
Click(MouseEvent),
DownOut(MouseEvent),
ScrollWheel(ScrollWheelEvent),
}
#[derive(Clone, Default)]
pub struct MouseRegion {
pub view_id: usize,
pub discriminant: Option<(TypeId, usize)>,
pub bounds: RectF,
pub hover: Option<Rc<dyn Fn(Vector2F, bool, &mut EventContext)>>,
pub mouse_down: Option<Rc<dyn Fn(Vector2F, &mut EventContext)>>,
pub click: Option<Rc<dyn Fn(Vector2F, usize, &mut EventContext)>>,
pub right_mouse_down: Option<Rc<dyn Fn(Vector2F, &mut EventContext)>>,
pub right_click: Option<Rc<dyn Fn(Vector2F, usize, &mut EventContext)>>,
pub drag: Option<Rc<dyn Fn(Vector2F, Vector2F, &mut EventContext)>>,
pub mouse_down_out: Option<Rc<dyn Fn(Vector2F, &mut EventContext)>>,
pub right_mouse_down_out: Option<Rc<dyn Fn(Vector2F, &mut EventContext)>>,
}
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct MouseRegionId {
pub view_id: usize,
pub discriminant: (TypeId, usize),
}
#[derive(Default, Debug)]
pub struct Quad {
pub bounds: RectF,

View file

@ -0,0 +1,336 @@
use std::{any::TypeId, mem::Discriminant, rc::Rc};
use collections::HashMap;
use pathfinder_geometry::{rect::RectF, vector::Vector2F};
use crate::{EventContext, MouseButton, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent};
#[derive(Clone, Default)]
pub struct MouseRegion {
pub view_id: usize,
pub discriminant: Option<(TypeId, usize)>,
pub bounds: RectF,
pub handlers: HandlerSet,
}
impl MouseRegion {
pub fn new(view_id: usize, discriminant: Option<(TypeId, usize)>, bounds: RectF) -> Self {
Self::from_handlers(view_id, discriminant, bounds, Default::default())
}
pub fn from_handlers(
view_id: usize,
discriminant: Option<(TypeId, usize)>,
bounds: RectF,
handlers: HandlerSet,
) -> Self {
Self {
view_id,
discriminant,
bounds,
handlers,
}
}
pub fn handle_all(
view_id: usize,
discriminant: Option<(TypeId, usize)>,
bounds: RectF,
) -> Self {
Self {
view_id,
discriminant,
bounds,
handlers: HandlerSet::handle_all(),
}
}
pub fn on_down(
mut self,
button: MouseButton,
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
) -> Self {
self.handlers = self.handlers.on_down(button, handler);
self
}
pub fn on_up(
mut self,
button: MouseButton,
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
) -> Self {
self.handlers = self.handlers.on_up(button, handler);
self
}
pub fn on_click(
mut self,
button: MouseButton,
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
) -> Self {
self.handlers = self.handlers.on_click(button, handler);
self
}
pub fn on_down_out(
mut self,
button: MouseButton,
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
) -> Self {
self.handlers = self.handlers.on_down_out(button, handler);
self
}
pub fn on_drag(
mut self,
button: MouseButton,
handler: impl Fn(Vector2F, MouseMovedEvent, &mut EventContext) + 'static,
) -> Self {
self.handlers = self.handlers.on_drag(button, handler);
self
}
pub fn on_hover(
mut self,
handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static,
) -> Self {
self.handlers = self.handlers.on_hover(handler);
self
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct MouseRegionId {
pub view_id: usize,
pub discriminant: (TypeId, usize),
}
#[derive(Clone, Default)]
pub struct HandlerSet {
pub set: HashMap<
(Discriminant<MouseRegionEvent>, Option<MouseButton>),
Rc<dyn Fn(MouseRegionEvent, &mut EventContext)>,
>,
}
impl HandlerSet {
pub fn handle_all() -> Self {
let mut set: HashMap<
(Discriminant<MouseRegionEvent>, Option<MouseButton>),
Rc<dyn Fn(MouseRegionEvent, &mut EventContext)>,
> = Default::default();
set.insert((MouseRegionEvent::move_disc(), None), Rc::new(|_, _| {}));
set.insert((MouseRegionEvent::hover_disc(), None), Rc::new(|_, _| {}));
for button in MouseButton::all() {
set.insert(
(MouseRegionEvent::drag_disc(), Some(button)),
Rc::new(|_, _| {}),
);
set.insert(
(MouseRegionEvent::down_disc(), Some(button)),
Rc::new(|_, _| {}),
);
set.insert(
(MouseRegionEvent::up_disc(), Some(button)),
Rc::new(|_, _| {}),
);
set.insert(
(MouseRegionEvent::click_disc(), Some(button)),
Rc::new(|_, _| {}),
);
set.insert(
(MouseRegionEvent::down_out_disc(), Some(button)),
Rc::new(|_, _| {}),
);
}
set.insert(
(MouseRegionEvent::scroll_wheel_disc(), None),
Rc::new(|_, _| {}),
);
HandlerSet { set }
}
pub fn get(
&self,
key: &(Discriminant<MouseRegionEvent>, Option<MouseButton>),
) -> Option<Rc<dyn Fn(MouseRegionEvent, &mut EventContext)>> {
self.set.get(key).cloned()
}
pub fn on_down(
mut self,
button: MouseButton,
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
) -> Self {
self.set.insert((MouseRegionEvent::down_disc(), Some(button)),
Rc::new(move |region_event, cx| {
if let MouseRegionEvent::Down(mouse_button_event) = region_event {
handler(mouse_button_event, cx);
} else {
panic!(
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Down, found {:?}",
region_event);
}
}));
self
}
pub fn on_up(
mut self,
button: MouseButton,
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
) -> Self {
self.set.insert((MouseRegionEvent::up_disc(), Some(button)),
Rc::new(move |region_event, cx| {
if let MouseRegionEvent::Up(mouse_button_event) = region_event {
handler(mouse_button_event, cx);
} else {
panic!(
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Up, found {:?}",
region_event);
}
}));
self
}
pub fn on_click(
mut self,
button: MouseButton,
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
) -> Self {
self.set.insert((MouseRegionEvent::click_disc(), Some(button)),
Rc::new(move |region_event, cx| {
if let MouseRegionEvent::Click(mouse_button_event) = region_event {
handler(mouse_button_event, cx);
} else {
panic!(
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Click, found {:?}",
region_event);
}
}));
self
}
pub fn on_down_out(
mut self,
button: MouseButton,
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
) -> Self {
self.set.insert((MouseRegionEvent::down_out_disc(), Some(button)),
Rc::new(move |region_event, cx| {
if let MouseRegionEvent::DownOut(mouse_button_event) = region_event {
handler(mouse_button_event, cx);
} else {
panic!(
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::DownOut, found {:?}",
region_event);
}
}));
self
}
pub fn on_drag(
mut self,
button: MouseButton,
handler: impl Fn(Vector2F, MouseMovedEvent, &mut EventContext) + 'static,
) -> Self {
self.set.insert((MouseRegionEvent::drag_disc(), Some(button)),
Rc::new(move |region_event, cx| {
if let MouseRegionEvent::Drag(prev_drag_position, mouse_moved_event) = region_event {
handler(prev_drag_position, mouse_moved_event, cx);
} else {
panic!(
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Drag, found {:?}",
region_event);
}
}));
self
}
pub fn on_hover(
mut self,
handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static,
) -> Self {
self.set.insert((MouseRegionEvent::hover_disc(), None),
Rc::new(move |region_event, cx| {
if let MouseRegionEvent::Hover(hover, mouse_moved_event) = region_event {
handler(hover, mouse_moved_event, cx);
} else {
panic!(
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Hover, found {:?}",
region_event);
}
}));
self
}
}
#[derive(Debug)]
pub enum MouseRegionEvent {
Move(MouseMovedEvent),
Drag(Vector2F, MouseMovedEvent),
Hover(bool, MouseMovedEvent),
Down(MouseButtonEvent),
Up(MouseButtonEvent),
Click(MouseButtonEvent),
DownOut(MouseButtonEvent),
ScrollWheel(ScrollWheelEvent),
}
impl MouseRegionEvent {
pub fn move_disc() -> Discriminant<MouseRegionEvent> {
std::mem::discriminant(&MouseRegionEvent::Move(Default::default()))
}
pub fn drag_disc() -> Discriminant<MouseRegionEvent> {
std::mem::discriminant(&MouseRegionEvent::Drag(
Default::default(),
Default::default(),
))
}
pub fn hover_disc() -> Discriminant<MouseRegionEvent> {
std::mem::discriminant(&MouseRegionEvent::Hover(
Default::default(),
Default::default(),
))
}
pub fn down_disc() -> Discriminant<MouseRegionEvent> {
std::mem::discriminant(&MouseRegionEvent::Down(Default::default()))
}
pub fn up_disc() -> Discriminant<MouseRegionEvent> {
std::mem::discriminant(&MouseRegionEvent::Up(Default::default()))
}
pub fn click_disc() -> Discriminant<MouseRegionEvent> {
std::mem::discriminant(&MouseRegionEvent::Click(Default::default()))
}
pub fn down_out_disc() -> Discriminant<MouseRegionEvent> {
std::mem::discriminant(&MouseRegionEvent::DownOut(Default::default()))
}
pub fn scroll_wheel_disc() -> Discriminant<MouseRegionEvent> {
std::mem::discriminant(&MouseRegionEvent::ScrollWheel(Default::default()))
}
pub fn handler_key(&self) -> (Discriminant<MouseRegionEvent>, Option<MouseButton>) {
match self {
MouseRegionEvent::Move(_) => (Self::move_disc(), None),
MouseRegionEvent::Drag(_, MouseMovedEvent { pressed_button, .. }) => {
(Self::drag_disc(), *pressed_button)
}
MouseRegionEvent::Hover(_, _) => (Self::hover_disc(), None),
MouseRegionEvent::Down(MouseButtonEvent { button, .. }) => {
(Self::down_disc(), Some(*button))
}
MouseRegionEvent::Up(MouseButtonEvent { button, .. }) => {
(Self::up_disc(), Some(*button))
}
MouseRegionEvent::Click(MouseButtonEvent { button, .. }) => {
(Self::click_disc(), Some(*button))
}
MouseRegionEvent::DownOut(MouseButtonEvent { button, .. }) => {
(Self::down_out_disc(), Some(*button))
}
MouseRegionEvent::ScrollWheel(_) => (Self::scroll_wheel_disc(), None),
}
}
}

View file

@ -238,6 +238,8 @@ impl Line {
None
}
// If round_to_closest, find the closest index to the given x position
// If !round_to_closest, find the largest index before the given x position
pub fn index_for_x(&self, x: f32) -> Option<usize> {
if x >= self.layout.width {
None

View file

@ -1,8 +1,8 @@
use serde::Deserialize;
use crate::{
actions, elements::*, impl_actions, AppContext, Entity, MutableAppContext, RenderContext, View,
ViewContext, WeakViewHandle,
actions, elements::*, impl_actions, AppContext, Entity, MouseButton, MutableAppContext,
RenderContext, View, ViewContext, WeakViewHandle,
};
pub struct Select {
@ -119,7 +119,9 @@ impl View for Select {
.with_style(style.header)
.boxed()
})
.on_click(move |_, _, cx| cx.dispatch_action(ToggleSelect))
.on_click(MouseButton::Left, move |_, cx| {
cx.dispatch_action(ToggleSelect)
})
.boxed(),
);
if self.is_open {
@ -151,7 +153,7 @@ impl View for Select {
)
},
)
.on_click(move |_, _, cx| {
.on_click(MouseButton::Left, move |_, cx| {
cx.dispatch_action(SelectItem(ix))
})
.boxed()

View file

@ -7,8 +7,8 @@ use gpui::{
geometry::vector::{vec2f, Vector2F},
keymap,
platform::CursorStyle,
AppContext, Axis, Element, ElementBox, Entity, MouseState, MutableAppContext, RenderContext,
Task, View, ViewContext, ViewHandle, WeakViewHandle,
AppContext, Axis, Element, ElementBox, Entity, MouseButton, MouseState, MutableAppContext,
RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle,
};
use menu::{Cancel, Confirm, SelectFirst, SelectIndex, SelectLast, SelectNext, SelectPrev};
use settings::Settings;
@ -90,7 +90,9 @@ impl<D: PickerDelegate> View for Picker<D> {
.read(cx)
.render_match(ix, state, ix == selected_ix, cx)
})
.on_mouse_down(move |_, cx| cx.dispatch_action(SelectIndex(ix)))
.on_down(MouseButton::Left, move |_, cx| {
cx.dispatch_action(SelectIndex(ix))
})
.with_cursor_style(CursorStyle::PointingHand)
.boxed()
}));

View file

@ -11,8 +11,9 @@ use gpui::{
geometry::vector::Vector2F,
impl_internal_actions, keymap,
platform::CursorStyle,
AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, MutableAppContext,
PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle,
AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, MouseButton,
MouseButtonEvent, MutableAppContext, PromptLevel, RenderContext, Task, View, ViewContext,
ViewHandle,
};
use menu::{Confirm, SelectNext, SelectPrev};
use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
@ -1027,11 +1028,11 @@ impl ProjectPanel {
.with_child(
ConstrainedBox::new(if kind == EntryKind::Dir {
if details.is_expanded {
Svg::new("icons/disclosure-open.svg")
Svg::new("icons/chevron_right_8.svg")
.with_color(style.icon_color)
.boxed()
} else {
Svg::new("icons/disclosure-closed.svg")
Svg::new("icons/chevron_down_8.svg")
.with_color(style.icon_color)
.boxed()
}
@ -1068,19 +1069,25 @@ impl ProjectPanel {
.with_padding_left(padding)
.boxed()
})
.on_click(move |_, click_count, cx| {
if kind == EntryKind::Dir {
cx.dispatch_action(ToggleExpanded(entry_id))
} else {
cx.dispatch_action(Open {
entry_id,
change_focus: click_count > 1,
})
}
})
.on_right_mouse_down(move |position, cx| {
cx.dispatch_action(DeployContextMenu { entry_id, position })
})
.on_click(
MouseButton::Left,
move |MouseButtonEvent { click_count, .. }, cx| {
if kind == EntryKind::Dir {
cx.dispatch_action(ToggleExpanded(entry_id))
} else {
cx.dispatch_action(Open {
entry_id,
change_focus: click_count > 1,
})
}
},
)
.on_down(
MouseButton::Right,
move |MouseButtonEvent { position, .. }, cx| {
cx.dispatch_action(DeployContextMenu { entry_id, position })
},
)
.with_cursor_style(CursorStyle::PointingHand)
.boxed()
}
@ -1127,13 +1134,16 @@ impl View for ProjectPanel {
.expanded()
.boxed()
})
.on_right_mouse_down(move |position, cx| {
// When deploying the context menu anywhere below the last project entry,
// act as if the user clicked the root of the last worktree.
if let Some(entry_id) = last_worktree_root_id {
cx.dispatch_action(DeployContextMenu { entry_id, position })
}
})
.on_down(
MouseButton::Right,
move |MouseButtonEvent { position, .. }, cx| {
// When deploying the context menu anywhere below the last project entry,
// act as if the user clicked the root of the last worktree.
if let Some(entry_id) = last_worktree_root_id {
cx.dispatch_action(DeployContextMenu { entry_id, position })
}
},
)
.boxed(),
)
.with_child(ChildView::new(&self.context_menu).boxed())

View file

@ -7,8 +7,8 @@ use collections::HashMap;
use editor::{Anchor, Autoscroll, Editor};
use gpui::{
actions, elements::*, impl_actions, platform::CursorStyle, Action, AppContext, Entity,
MutableAppContext, RenderContext, Subscription, Task, View, ViewContext, ViewHandle,
WeakViewHandle,
MouseButton, MutableAppContext, RenderContext, Subscription, Task, View, ViewContext,
ViewHandle, WeakViewHandle,
};
use language::OffsetRangeExt;
use project::search::SearchQuery;
@ -285,7 +285,9 @@ impl BufferSearchBar {
.with_style(style.container)
.boxed()
})
.on_click(move |_, _, cx| cx.dispatch_any_action(option.to_toggle_action()))
.on_click(MouseButton::Left, move |_, cx| {
cx.dispatch_any_action(option.to_toggle_action())
})
.with_cursor_style(CursorStyle::PointingHand)
.with_tooltip::<Self, _>(
option as usize,
@ -330,9 +332,9 @@ impl BufferSearchBar {
.with_style(style.container)
.boxed()
})
.on_click({
.on_click(MouseButton::Left, {
let action = action.boxed_clone();
move |_, _, cx| cx.dispatch_any_action(action.boxed_clone())
move |_, cx| cx.dispatch_any_action(action.boxed_clone())
})
.with_cursor_style(CursorStyle::PointingHand)
.with_tooltip::<NavButton, _>(

View file

@ -7,8 +7,8 @@ use collections::HashMap;
use editor::{Anchor, Autoscroll, Editor, MultiBuffer, SelectAll, MAX_TAB_TITLE_LEN};
use gpui::{
actions, elements::*, platform::CursorStyle, Action, AppContext, ElementBox, Entity,
ModelContext, ModelHandle, MutableAppContext, RenderContext, Subscription, Task, View,
ViewContext, ViewHandle, WeakModelHandle, WeakViewHandle,
ModelContext, ModelHandle, MouseButton, MutableAppContext, RenderContext, Subscription, Task,
View, ViewContext, ViewHandle, WeakModelHandle, WeakViewHandle,
};
use menu::Confirm;
use project::{search::SearchQuery, Project};
@ -147,6 +147,7 @@ impl ProjectSearch {
pub enum ViewEvent {
UpdateTab,
Activate,
EditorEvent(editor::Event),
}
@ -162,7 +163,9 @@ impl View for ProjectSearchView {
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
let model = &self.model.read(cx);
if model.match_ranges.is_empty() {
let theme = &cx.global::<Settings>().theme;
enum Status {}
let theme = cx.global::<Settings>().theme.clone();
let text = if self.query_editor.read(cx).text(cx).is_empty() {
""
} else if model.pending_search.is_some() {
@ -170,12 +173,18 @@ impl View for ProjectSearchView {
} else {
"No results"
};
Label::new(text.to_string(), theme.search.results_status.clone())
.aligned()
.contained()
.with_background_color(theme.editor.background)
.flex(1., true)
.boxed()
MouseEventHandler::new::<Status, _, _>(0, cx, |_, _| {
Label::new(text.to_string(), theme.search.results_status.clone())
.aligned()
.contained()
.with_background_color(theme.editor.background)
.flex(1., true)
.boxed()
})
.on_down(MouseButton::Left, |_, cx| {
cx.focus_parent_view();
})
.boxed()
} else {
ChildView::new(&self.results_editor).flex(1., true).boxed()
}
@ -228,7 +237,7 @@ impl Item for ProjectSearchView {
let search_theme = &settings.theme.search;
Flex::row()
.with_child(
Svg::new("icons/magnifier.svg")
Svg::new("icons/magnifying_glass_12.svg")
.with_color(tab_theme.label.text.color)
.constrained()
.with_width(search_theme.tab_icon_width)
@ -735,9 +744,9 @@ impl ProjectSearchBar {
.with_style(style.container)
.boxed()
})
.on_click({
.on_click(MouseButton::Left, {
let action = action.boxed_clone();
move |_, _, cx| cx.dispatch_any_action(action.boxed_clone())
move |_, cx| cx.dispatch_any_action(action.boxed_clone())
})
.with_cursor_style(CursorStyle::PointingHand)
.with_tooltip::<NavButton, _>(
@ -770,7 +779,9 @@ impl ProjectSearchBar {
.with_style(style.container)
.boxed()
})
.on_click(move |_, _, cx| cx.dispatch_any_action(option.to_toggle_action()))
.on_click(MouseButton::Left, move |_, cx| {
cx.dispatch_any_action(option.to_toggle_action())
})
.with_cursor_style(CursorStyle::PointingHand)
.with_tooltip::<Self, _>(
option as usize,

View file

@ -290,7 +290,7 @@ impl Item for Terminal {
if self.has_bell {
flex.add_child(
Svg::new("icons/zap.svg") //TODO: Swap out for a better icon, or at least resize this
Svg::new("icons/bolt_12.svg") //TODO: Swap out for a better icon, or at least resize this
.with_color(tab_theme.label.text.color)
.constrained()
.with_width(search_theme.tab_icon_width)

View file

@ -20,8 +20,8 @@ use gpui::{
},
json::json,
text_layout::{Line, RunStyle},
Event, FontCache, KeyDownEvent, MouseRegion, PaintContext, Quad, ScrollWheelEvent,
SizeConstraint, TextLayoutCache, WeakModelHandle,
Event, FontCache, KeyDownEvent, MouseButton, MouseButtonEvent, MouseMovedEvent, MouseRegion,
PaintContext, Quad, ScrollWheelEvent, SizeConstraint, TextLayoutCache, WeakModelHandle,
};
use itertools::Itertools;
use ordered_float::OrderedFloat;
@ -29,7 +29,7 @@ use settings::Settings;
use theme::TerminalStyle;
use util::ResultExt;
use std::{cmp::min, ops::Range, rc::Rc, sync::Arc};
use std::{cmp::min, ops::Range, sync::Arc};
use std::{fmt::Debug, ops::Sub};
use crate::{color_translation::convert_color, connection::TerminalConnection, ZedListener};
@ -615,63 +615,76 @@ fn attach_mouse_handlers(
let drag_mutex = terminal_mutex.clone();
let mouse_down_mutex = terminal_mutex.clone();
cx.scene.push_mouse_region(MouseRegion {
view_id,
mouse_down: Some(Rc::new(move |pos, _| {
let mut term = mouse_down_mutex.lock();
let (point, side) = mouse_to_cell_data(
pos,
origin,
cur_size,
term.renderable_content().display_offset,
);
term.selection = Some(Selection::new(SelectionType::Simple, point, side))
})),
click: Some(Rc::new(move |pos, click_count, cx| {
let mut term = click_mutex.lock();
cx.scene.push_mouse_region(
MouseRegion::new(view_id, None, visible_bounds)
.on_down(
MouseButton::Left,
move |MouseButtonEvent { position, .. }, _| {
let mut term = mouse_down_mutex.lock();
let (point, side) = mouse_to_cell_data(
pos,
origin,
cur_size,
term.renderable_content().display_offset,
);
let (point, side) = mouse_to_cell_data(
position,
origin,
cur_size,
term.renderable_content().display_offset,
);
term.selection = Some(Selection::new(SelectionType::Simple, point, side))
},
)
.on_click(
MouseButton::Left,
move |MouseButtonEvent {
position,
click_count,
..
},
cx| {
let mut term = click_mutex.lock();
let selection_type = match click_count {
0 => return, //This is a release
1 => Some(SelectionType::Simple),
2 => Some(SelectionType::Semantic),
3 => Some(SelectionType::Lines),
_ => None,
};
let (point, side) = mouse_to_cell_data(
position,
origin,
cur_size,
term.renderable_content().display_offset,
);
let selection =
selection_type.map(|selection_type| Selection::new(selection_type, point, side));
let selection_type = match click_count {
0 => return, //This is a release
1 => Some(SelectionType::Simple),
2 => Some(SelectionType::Semantic),
3 => Some(SelectionType::Lines),
_ => None,
};
term.selection = selection;
cx.focus_parent_view();
cx.notify();
})),
bounds: visible_bounds,
drag: Some(Rc::new(move |_delta, pos, cx| {
let mut term = drag_mutex.lock();
let selection = selection_type
.map(|selection_type| Selection::new(selection_type, point, side));
let (point, side) = mouse_to_cell_data(
pos,
origin,
cur_size,
term.renderable_content().display_offset,
);
term.selection = selection;
cx.focus_parent_view();
cx.notify();
},
)
.on_drag(
MouseButton::Left,
move |_, MouseMovedEvent { position, .. }, cx| {
let mut term = drag_mutex.lock();
if let Some(mut selection) = term.selection.take() {
selection.update(point, side);
term.selection = Some(selection);
}
let (point, side) = mouse_to_cell_data(
position,
origin,
cur_size,
term.renderable_content().display_offset,
);
cx.notify();
})),
..Default::default()
});
if let Some(mut selection) = term.selection.take() {
selection.update(point, side);
term.selection = Some(selection);
}
cx.notify();
},
),
);
}
///Copied (with modifications) from alacritty/src/input.rs > Processor::cell_side()

View file

@ -38,8 +38,10 @@ pub struct Theme {
pub struct Workspace {
pub background: Color,
pub titlebar: Titlebar,
pub tab: Tab,
pub active_tab: Tab,
pub active_pane_active_tab: Tab,
pub active_pane_inactive_tab: Tab,
pub inactive_pane_active_tab: Tab,
pub inactive_pane_inactive_tab: Tab,
pub pane_button: Interactive<IconButton>,
pub pane_divider: Border,
pub leader_border_opacity: f32,

View file

@ -45,12 +45,13 @@ fn editor_focused(EditorFocused(editor): &EditorFocused, cx: &mut MutableAppCont
}
let editor = editor.read(cx);
if editor.selections.newest::<usize>(cx).is_empty() {
if editor.mode() != EditorMode::Full {
vim.switch_mode(Mode::Insert, cx);
}
} else {
vim.switch_mode(Mode::Visual { line: false }, cx);
let editor_mode = editor.mode();
let newest_selection_empty = editor.selections.newest::<usize>(cx).is_empty();
if editor_mode != EditorMode::Full {
vim.switch_mode(Mode::Insert, true, cx);
} else if !newest_selection_empty {
vim.switch_mode(Mode::Visual { line: false }, true, cx);
}
});
}
@ -80,7 +81,7 @@ fn editor_released(EditorReleased(editor): &EditorReleased, cx: &mut MutableAppC
fn editor_local_selections_changed(newest_empty: bool, cx: &mut MutableAppContext) {
Vim::update(cx, |vim, cx| {
if vim.enabled && vim.state.mode == Mode::Normal && !newest_empty {
vim.switch_mode(Mode::Visual { line: false }, cx)
vim.switch_mode(Mode::Visual { line: false }, false, cx)
}
})
}

View file

@ -20,7 +20,7 @@ fn normal_before(_: &mut Workspace, _: &NormalBefore, cx: &mut ViewContext<Works
});
});
});
state.switch_mode(Mode::Normal, cx);
state.switch_mode(Mode::Normal, false, cx);
})
}

View file

@ -91,7 +91,7 @@ fn move_cursor(vim: &mut Vim, motion: Motion, cx: &mut MutableAppContext) {
fn insert_after(_: &mut Workspace, _: &InsertAfter, cx: &mut ViewContext<Workspace>) {
Vim::update(cx, |vim, cx| {
vim.switch_mode(Mode::Insert, cx);
vim.switch_mode(Mode::Insert, false, cx);
vim.update_active_editor(cx, |editor, cx| {
editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
s.move_cursors_with(|map, cursor, goal| {
@ -108,7 +108,7 @@ fn insert_first_non_whitespace(
cx: &mut ViewContext<Workspace>,
) {
Vim::update(cx, |vim, cx| {
vim.switch_mode(Mode::Insert, cx);
vim.switch_mode(Mode::Insert, false, cx);
vim.update_active_editor(cx, |editor, cx| {
editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
s.move_cursors_with(|map, cursor, goal| {
@ -121,7 +121,7 @@ fn insert_first_non_whitespace(
fn insert_end_of_line(_: &mut Workspace, _: &InsertEndOfLine, cx: &mut ViewContext<Workspace>) {
Vim::update(cx, |vim, cx| {
vim.switch_mode(Mode::Insert, cx);
vim.switch_mode(Mode::Insert, false, cx);
vim.update_active_editor(cx, |editor, cx| {
editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
s.move_cursors_with(|map, cursor, goal| {
@ -134,7 +134,7 @@ fn insert_end_of_line(_: &mut Workspace, _: &InsertEndOfLine, cx: &mut ViewConte
fn insert_line_above(_: &mut Workspace, _: &InsertLineAbove, cx: &mut ViewContext<Workspace>) {
Vim::update(cx, |vim, cx| {
vim.switch_mode(Mode::Insert, cx);
vim.switch_mode(Mode::Insert, false, cx);
vim.update_active_editor(cx, |editor, cx| {
editor.transact(cx, |editor, cx| {
let (map, old_selections) = editor.selections.all_display(cx);
@ -166,7 +166,7 @@ fn insert_line_above(_: &mut Workspace, _: &InsertLineAbove, cx: &mut ViewContex
fn insert_line_below(_: &mut Workspace, _: &InsertLineBelow, cx: &mut ViewContext<Workspace>) {
Vim::update(cx, |vim, cx| {
vim.switch_mode(Mode::Insert, cx);
vim.switch_mode(Mode::Insert, false, cx);
vim.update_active_editor(cx, |editor, cx| {
editor.transact(cx, |editor, cx| {
let (map, old_selections) = editor.selections.all_display(cx);

View file

@ -31,7 +31,7 @@ pub fn change_over(vim: &mut Vim, motion: Motion, cx: &mut MutableAppContext) {
editor.insert(&"", cx);
});
});
vim.switch_mode(Mode::Insert, cx)
vim.switch_mode(Mode::Insert, false, cx)
}
// From the docs https://vimhelp.org/change.txt.html#cw
@ -70,7 +70,7 @@ fn change_word(
editor.insert(&"", cx);
});
});
vim.switch_mode(Mode::Insert, cx);
vim.switch_mode(Mode::Insert, false, cx);
});
}

View file

@ -36,7 +36,7 @@ pub fn init(cx: &mut MutableAppContext) {
// Vim Actions
cx.add_action(|_: &mut Workspace, &SwitchMode(mode): &SwitchMode, cx| {
Vim::update(cx, |vim, cx| vim.switch_mode(mode, cx))
Vim::update(cx, |vim, cx| vim.switch_mode(mode, false, cx))
});
cx.add_action(
|_: &mut Workspace, &PushOperator(operator): &PushOperator, cx| {
@ -52,7 +52,7 @@ pub fn init(cx: &mut MutableAppContext) {
if vim.state.mode != Mode::Normal || vim.active_operator().is_some() {
MutableAppContext::defer(cx, |cx| {
Vim::update(cx, |state, cx| {
state.switch_mode(Mode::Normal, cx);
state.switch_mode(Mode::Normal, false, cx);
});
});
} else {
@ -105,37 +105,27 @@ impl Vim {
.map(|ae| ae.update(cx, update))
}
fn switch_mode(&mut self, mode: Mode, cx: &mut MutableAppContext) {
let previous_mode = self.state.mode;
fn switch_mode(&mut self, mode: Mode, leave_selections: bool, cx: &mut MutableAppContext) {
self.state.mode = mode;
self.state.operator_stack.clear();
// Sync editor settings like clip mode
self.sync_vim_settings(cx);
if leave_selections {
return;
}
// Adjust selections
for editor in self.editors.values() {
if let Some(editor) = editor.upgrade(cx) {
editor.update(cx, |editor, cx| {
editor.change_selections(None, cx, |s| {
s.move_with(|map, selection| {
// If empty selections
if self.state.empty_selections_only() {
let new_head = map.clip_point(selection.head(), Bias::Left);
selection.collapse_to(new_head, selection.goal)
} else {
if matches!(mode, Mode::Visual { line: false })
&& !matches!(previous_mode, Mode::Visual { .. })
&& !selection.reversed
&& !selection.is_empty()
{
// Mode wasn't visual mode before, but is now. We need to move the end
// back by one character so that the region to be modifed stays the same
*selection.end.column_mut() =
selection.end.column().saturating_sub(1);
selection.end = map.clip_point(selection.end, Bias::Left);
}
selection.set_head(
map.clip_point(selection.head(), Bias::Left),
selection.goal,
@ -173,7 +163,7 @@ impl Vim {
self.enabled = enabled;
self.state = Default::default();
if enabled {
self.switch_mode(Mode::Normal, cx);
self.switch_mode(Mode::Normal, false, cx);
}
self.sync_vim_settings(cx);
}
@ -266,7 +256,7 @@ mod test {
}
#[gpui::test]
async fn test_buffer_search_switches_mode(cx: &mut gpui::TestAppContext) {
async fn test_buffer_search(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.set_state(
@ -278,7 +268,8 @@ mod test {
);
cx.simulate_keystroke("/");
assert_eq!(cx.mode(), Mode::Visual { line: false });
// We now use a weird insert mode with selection when jumping to a single line editor
assert_eq!(cx.mode(), Mode::Insert);
let search_bar = cx.workspace(|workspace, cx| {
workspace

View file

@ -119,7 +119,7 @@ impl<'a> VimTestContext<'a> {
pub fn set_state(&mut self, text: &str, mode: Mode) {
self.cx.update(|cx| {
Vim::update(cx, |vim, cx| {
vim.switch_mode(mode, cx);
vim.switch_mode(mode, false, cx);
})
});
self.cx.set_state(text);

View file

@ -89,7 +89,7 @@ pub fn change(_: &mut Workspace, _: &VisualChange, cx: &mut ViewContext<Workspac
s.select_anchors(new_selections);
});
});
vim.switch_mode(Mode::Insert, cx);
vim.switch_mode(Mode::Insert, false, cx);
});
}
@ -130,7 +130,7 @@ pub fn delete(_: &mut Workspace, _: &VisualDelete, cx: &mut ViewContext<Workspac
});
});
});
vim.switch_mode(Mode::Normal, cx);
vim.switch_mode(Mode::Normal, false, cx);
});
}
@ -158,7 +158,7 @@ pub fn yank(_: &mut Workspace, _: &VisualYank, cx: &mut ViewContext<Workspace>)
});
});
});
vim.switch_mode(Mode::Normal, cx);
vim.switch_mode(Mode::Normal, false, cx);
});
}
@ -266,7 +266,7 @@ pub fn paste(_: &mut Workspace, _: &VisualPaste, cx: &mut ViewContext<Workspace>
}
});
});
vim.switch_mode(Mode::Normal, cx);
vim.switch_mode(Mode::Normal, false, cx);
});
}

View file

View file

@ -13,8 +13,9 @@ use gpui::{
},
impl_actions, impl_internal_actions,
platform::{CursorStyle, NavigationDirection},
AppContext, AsyncAppContext, Entity, ModelHandle, MutableAppContext, PromptLevel, Quad,
RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle,
AppContext, AsyncAppContext, Entity, ModelHandle, MouseButton, MouseButtonEvent,
MutableAppContext, PromptLevel, Quad, RenderContext, Task, View, ViewContext, ViewHandle,
WeakViewHandle,
};
use project::{Project, ProjectEntryId, ProjectPath};
use serde::Deserialize;
@ -135,6 +136,7 @@ pub enum Event {
pub struct Pane {
items: Vec<Box<dyn ItemHandle>>,
is_active: bool,
active_item_index: usize,
autoscroll: bool,
nav_history: Rc<RefCell<NavHistory>>,
@ -183,6 +185,7 @@ impl Pane {
let split_menu = cx.add_view(|cx| ContextMenu::new(cx));
Self {
items: Vec::new(),
is_active: true,
active_item_index: 0,
autoscroll: false,
nav_history: Rc::new(RefCell::new(NavHistory {
@ -198,6 +201,11 @@ impl Pane {
}
}
pub fn set_active(&mut self, is_active: bool, cx: &mut ViewContext<Self>) {
self.is_active = is_active;
cx.notify();
}
pub fn nav_history_for_item<T: Item>(&self, item: &ViewHandle<T>) -> ItemNavHistory {
ItemNavHistory {
history: self.nav_history.clone(),
@ -864,26 +872,23 @@ impl Pane {
None
};
let is_pane_active = self.is_active;
let mut row = Flex::row().scrollable::<Tabs, _>(1, autoscroll, cx);
for (ix, (item, detail)) in self.items.iter().zip(self.tab_details(cx)).enumerate() {
let detail = if detail == 0 { None } else { Some(detail) };
let is_active = ix == self.active_item_index;
let is_tab_active = ix == self.active_item_index;
row.add_child({
let tab_style = if is_active {
theme.workspace.active_tab.clone()
} else {
theme.workspace.tab.clone()
let mut tab_style = match (is_pane_active, is_tab_active) {
(true, true) => theme.workspace.active_pane_active_tab.clone(),
(true, false) => theme.workspace.active_pane_inactive_tab.clone(),
(false, true) => theme.workspace.inactive_pane_active_tab.clone(),
(false, false) => theme.workspace.inactive_pane_inactive_tab.clone(),
};
let title = item.tab_content(detail, &tab_style, cx);
let mut style = if is_active {
theme.workspace.active_tab.clone()
} else {
theme.workspace.tab.clone()
};
if ix == 0 {
style.container.border.left = false;
tab_style.container.border.left = false;
}
MouseEventHandler::new::<Tab, _, _>(ix, cx, |_, cx| {
@ -893,9 +898,9 @@ impl Pane {
Align::new({
let diameter = 7.0;
let icon_color = if item.has_conflict(cx) {
Some(style.icon_conflict)
Some(tab_style.icon_conflict)
} else if item.is_dirty(cx) {
Some(style.icon_dirty)
Some(tab_style.icon_dirty)
} else {
None
};
@ -927,8 +932,8 @@ impl Pane {
Container::new(Align::new(title).boxed())
.with_style(ContainerStyle {
margin: Margin {
left: style.spacing,
right: style.spacing,
left: tab_style.spacing,
right: tab_style.spacing,
..Default::default()
},
..Default::default()
@ -940,24 +945,25 @@ impl Pane {
ConstrainedBox::new(if mouse_state.hovered {
let item_id = item.id();
enum TabCloseButton {}
let icon = Svg::new("icons/x.svg");
let icon = Svg::new("icons/x_mark_thin_8.svg");
MouseEventHandler::new::<TabCloseButton, _, _>(
item_id,
cx,
|mouse_state, _| {
if mouse_state.hovered {
icon.with_color(style.icon_close_active)
icon.with_color(tab_style.icon_close_active)
.boxed()
} else {
icon.with_color(style.icon_close).boxed()
icon.with_color(tab_style.icon_close)
.boxed()
}
},
)
.with_padding(Padding::uniform(4.))
.with_cursor_style(CursorStyle::PointingHand)
.on_click({
.on_click(MouseButton::Left, {
let pane = pane.clone();
move |_, _, cx| {
move |_, cx| {
cx.dispatch_action(CloseItem {
item_id,
pane: pane.clone(),
@ -968,27 +974,39 @@ impl Pane {
} else {
Empty::new().boxed()
})
.with_width(style.icon_width)
.with_width(tab_style.icon_width)
.boxed(),
)
.boxed(),
)
.boxed(),
)
.with_style(style.container)
.with_style(tab_style.container)
.boxed()
})
.on_mouse_down(move |_, cx| {
.with_cursor_style(if is_tab_active && is_pane_active {
CursorStyle::Arrow
} else {
CursorStyle::PointingHand
})
.on_down(MouseButton::Left, move |_, cx| {
cx.dispatch_action(ActivateItem(ix));
})
.boxed()
})
}
let filler_style = if is_pane_active {
&theme.workspace.active_pane_inactive_tab
} else {
&theme.workspace.inactive_pane_inactive_tab
};
row.add_child(
Empty::new()
.contained()
.with_border(theme.workspace.tab.container.border)
.with_style(filler_style.container)
.with_border(theme.workspace.active_pane_active_tab.container.border)
.flex(0., true)
.named("filler"),
);
@ -1053,10 +1071,12 @@ impl View for Pane {
.with_child(
EventHandler::new(if let Some(active_item) = self.active_item() {
Flex::column()
.with_child(
Flex::row()
.with_child(self.render_tabs(cx).flex(1., true).named("tabs"))
.with_child(
.with_child({
let mut tab_row = Flex::row()
.with_child(self.render_tabs(cx).flex(1., true).named("tabs"));
if self.is_active {
tab_row.add_child(
MouseEventHandler::new::<SplitIcon, _, _>(
0,
cx,
@ -1064,7 +1084,7 @@ impl View for Pane {
let theme = &cx.global::<Settings>().theme.workspace;
let style =
theme.pane_button.style_for(mouse_state, false);
Svg::new("icons/split.svg")
Svg::new("icons/split_12.svg")
.with_color(style.color)
.constrained()
.with_width(style.icon_width)
@ -1079,20 +1099,44 @@ impl View for Pane {
},
)
.with_cursor_style(CursorStyle::PointingHand)
.on_mouse_down(|position, cx| {
cx.dispatch_action(DeploySplitMenu { position });
})
.on_down(
MouseButton::Left,
|MouseButtonEvent { position, .. }, cx| {
cx.dispatch_action(DeploySplitMenu { position });
},
)
.boxed(),
)
}
tab_row
.constrained()
.with_height(cx.global::<Settings>().theme.workspace.tab.height)
.boxed(),
)
.with_height(
cx.global::<Settings>()
.theme
.workspace
.active_pane_active_tab
.height,
)
.boxed()
})
.with_child(ChildView::new(&self.toolbar).boxed())
.with_child(ChildView::new(active_item).flex(1., true).boxed())
.boxed()
} else {
Empty::new().boxed()
enum EmptyPane {}
let theme = cx.global::<Settings>().theme.clone();
MouseEventHandler::new::<EmptyPane, _, _>(0, cx, |_, _| {
Empty::new()
.contained()
.with_background_color(theme.workspace.background)
.boxed()
})
.on_down(MouseButton::Left, |_, cx| {
cx.focus_parent_view();
})
.boxed()
})
.on_navigate_mouse_down(move |direction, cx| {
let this = this.clone();

View file

@ -1,7 +1,7 @@
use crate::StatusItemView;
use gpui::{
elements::*, impl_actions, platform::CursorStyle, AnyViewHandle, AppContext, Entity,
RenderContext, Subscription, View, ViewContext, ViewHandle,
MouseButton, MouseMovedEvent, RenderContext, Subscription, View, ViewContext, ViewHandle,
};
use serde::Deserialize;
use settings::Settings;
@ -187,19 +187,27 @@ impl Sidebar {
..Default::default()
})
.with_cursor_style(CursorStyle::ResizeLeftRight)
.on_mouse_down(|_, _| {}) // This prevents the mouse down event from being propagated elsewhere
.on_drag(move |old_position, new_position, cx| {
let delta = new_position.x() - old_position.x();
let prev_width = *actual_width.borrow();
*custom_width.borrow_mut() = 0f32
.max(match side {
Side::Left => prev_width + delta,
Side::Right => prev_width - delta,
})
.round();
.on_down(MouseButton::Left, |_, _| {}) // This prevents the mouse down event from being propagated elsewhere
.on_drag(
MouseButton::Left,
move |old_position,
MouseMovedEvent {
position: new_position,
..
},
cx| {
let delta = new_position.x() - old_position.x();
let prev_width = *actual_width.borrow();
*custom_width.borrow_mut() = 0f32
.max(match side {
Side::Left => prev_width + delta,
Side::Right => prev_width - delta,
})
.round();
cx.notify();
})
cx.notify();
},
)
.boxed()
}
}
@ -314,9 +322,9 @@ impl View for SidebarButtons {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click({
.on_click(MouseButton::Left, {
let action = action.clone();
move |_, _, cx| cx.dispatch_action(action.clone())
move |_, cx| cx.dispatch_action(action.clone())
})
.with_tooltip::<Self, _>(
ix,

View file

@ -1,7 +1,7 @@
use crate::{ItemHandle, Pane};
use gpui::{
elements::*, platform::CursorStyle, Action, AnyViewHandle, AppContext, ElementBox, Entity,
MutableAppContext, RenderContext, View, ViewContext, ViewHandle, WeakViewHandle,
MouseButton, MutableAppContext, RenderContext, View, ViewContext, ViewHandle, WeakViewHandle,
};
use settings::Settings;
@ -118,7 +118,7 @@ impl View for Toolbar {
.with_child(
Flex::row()
.with_child(nav_button(
"icons/arrow-left.svg",
"icons/arrow_left_16.svg",
button_style,
tooltip_style.clone(),
enable_go_backward,
@ -131,7 +131,7 @@ impl View for Toolbar {
cx,
))
.with_child(nav_button(
"icons/arrow-right.svg",
"icons/arrow_right_16.svg",
button_style,
tooltip_style.clone(),
enable_go_forward,
@ -191,7 +191,9 @@ fn nav_button<A: Action + Clone>(
} else {
CursorStyle::default()
})
.on_click(move |_, _, cx| cx.dispatch_action(action.clone()))
.on_click(MouseButton::Left, move |_, cx| {
cx.dispatch_action(action.clone())
})
.with_tooltip::<A, _>(
0,
action_name.to_string(),

View file

@ -21,8 +21,8 @@ use gpui::{
json::{self, ToJson},
platform::{CursorStyle, WindowOptions},
AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Border, Entity, ImageData,
ModelContext, ModelHandle, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext,
Task, View, ViewContext, ViewHandle, WeakViewHandle,
ModelContext, ModelHandle, MouseButton, MutableAppContext, PathPromptOptions, PromptLevel,
RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle,
};
use language::LanguageRegistry;
use log::error;
@ -1567,7 +1567,11 @@ impl Workspace {
fn activate_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
if self.active_pane != pane {
self.active_pane
.update(cx, |pane, cx| pane.set_active(false, cx));
self.active_pane = pane.clone();
self.active_pane
.update(cx, |pane, cx| pane.set_active(true, cx));
self.status_bar.update(cx, |status_bar, cx| {
status_bar.set_active_pane(&self.active_pane, cx);
});
@ -1630,17 +1634,17 @@ impl Workspace {
pane: ViewHandle<Pane>,
direction: SplitDirection,
cx: &mut ViewContext<Self>,
) -> ViewHandle<Pane> {
let new_pane = self.add_pane(cx);
self.activate_pane(new_pane.clone(), cx);
if let Some(item) = pane.read(cx).active_item() {
) -> Option<ViewHandle<Pane>> {
pane.read(cx).active_item().map(|item| {
let new_pane = self.add_pane(cx);
self.activate_pane(new_pane.clone(), cx);
if let Some(clone) = item.clone_on_split(cx.as_mut()) {
Pane::add_item(self, new_pane.clone(), clone, true, true, cx);
}
}
self.center.split(&pane, &new_pane, direction).unwrap();
cx.notify();
new_pane
self.center.split(&pane, &new_pane, direction).unwrap();
cx.notify();
new_pane
})
}
fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
@ -1811,7 +1815,7 @@ impl Workspace {
Container::new(
Align::new(
ConstrainedBox::new(
Svg::new("icons/offline-14.svg")
Svg::new("icons/cloud_slash_12.svg")
.with_color(theme.workspace.titlebar.offline_icon.color)
.boxed(),
)
@ -1981,7 +1985,7 @@ impl Workspace {
.with_style(style.container)
.boxed()
})
.on_click(|_, _, cx| cx.dispatch_action(Authenticate))
.on_click(MouseButton::Left, |_, cx| cx.dispatch_action(Authenticate))
.with_cursor_style(CursorStyle::PointingHand)
.aligned()
.boxed(),
@ -2032,7 +2036,9 @@ impl Workspace {
if let Some((peer_id, peer_github_login)) = peer {
MouseEventHandler::new::<ToggleFollow, _, _>(replica_id.into(), cx, move |_, _| content)
.with_cursor_style(CursorStyle::PointingHand)
.on_click(move |_, _, cx| cx.dispatch_action(ToggleFollow(peer_id)))
.on_click(MouseButton::Left, move |_, cx| {
cx.dispatch_action(ToggleFollow(peer_id))
})
.with_tooltip::<ToggleFollow, _>(
peer_id.0 as usize,
if is_followed {
@ -3056,16 +3062,17 @@ mod tests {
// multi-entry items: (3, 4)
let left_pane = workspace.update(cx, |workspace, cx| {
let left_pane = workspace.active_pane().clone();
let right_pane = workspace.split_pane(left_pane.clone(), SplitDirection::Right, cx);
workspace.activate_pane(left_pane.clone(), cx);
workspace.add_item(Box::new(cx.add_view(|_| item_2_3.clone())), cx);
for item in &single_entry_items {
workspace.add_item(Box::new(cx.add_view(|_| item.clone())), cx);
}
left_pane.update(cx, |pane, cx| {
pane.activate_item(2, true, true, false, cx);
});
workspace.activate_pane(right_pane.clone(), cx);
workspace.add_item(Box::new(cx.add_view(|_| single_entry_items[1].clone())), cx);
workspace
.split_pane(left_pane.clone(), SplitDirection::Right, cx)
.unwrap();
workspace.add_item(Box::new(cx.add_view(|_| item_3_4.clone())), cx);
left_pane

View file

@ -2,7 +2,7 @@ use crate::OpenBrowser;
use gpui::{
elements::{MouseEventHandler, Text},
platform::CursorStyle,
Element, Entity, RenderContext, View,
Element, Entity, MouseButton, RenderContext, View,
};
use settings::Settings;
use workspace::StatusItemView;
@ -32,7 +32,7 @@ impl View for FeedbackLink {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(|_, _, cx| {
.on_click(MouseButton::Left, |_, cx| {
cx.dispatch_action(OpenBrowser {
url: NEW_ISSUE_URL.into(),
})

View file

@ -251,7 +251,7 @@ pub fn initialize_workspace(
workspace.left_sidebar().update(cx, |sidebar, cx| {
sidebar.add_item(
"icons/folder-tree-solid-14.svg",
"icons/folder_tree_16.svg",
"Project Panel".to_string(),
project_panel.into(),
cx,
@ -259,7 +259,7 @@ pub fn initialize_workspace(
});
workspace.right_sidebar().update(cx, |sidebar, cx| {
sidebar.add_item(
"icons/contacts-solid-14.svg",
"icons/user_group_16.svg",
"Contacts Panel".to_string(),
contact_panel.into(),
cx,