Merge branch 'main' into copilot2
This commit is contained in:
@@ -7,7 +7,7 @@ use gpui::{
|
||||
IntoElement, Render, View, VisualContext,
|
||||
};
|
||||
use menu::{SelectFirst, SelectLast, SelectNext, SelectPrev};
|
||||
use std::rc::Rc;
|
||||
use std::{rc::Rc, time::Duration};
|
||||
|
||||
pub enum ContextMenuItem {
|
||||
Separator,
|
||||
@@ -16,7 +16,7 @@ pub enum ContextMenuItem {
|
||||
label: SharedString,
|
||||
icon: Option<Icon>,
|
||||
handler: Rc<dyn Fn(&mut WindowContext)>,
|
||||
key_binding: Option<KeyBinding>,
|
||||
action: Option<Box<dyn Action>>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ pub struct ContextMenu {
|
||||
items: Vec<ContextMenuItem>,
|
||||
focus_handle: FocusHandle,
|
||||
selected_index: Option<usize>,
|
||||
delayed: bool,
|
||||
}
|
||||
|
||||
impl FocusableView for ContextMenu {
|
||||
@@ -46,6 +47,7 @@ impl ContextMenu {
|
||||
items: Default::default(),
|
||||
focus_handle: cx.focus_handle(),
|
||||
selected_index: None,
|
||||
delayed: false,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
@@ -70,36 +72,26 @@ impl ContextMenu {
|
||||
self.items.push(ContextMenuItem::Entry {
|
||||
label: label.into(),
|
||||
handler: Rc::new(on_click),
|
||||
key_binding: None,
|
||||
icon: None,
|
||||
action: None,
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn action(
|
||||
mut self,
|
||||
label: impl Into<SharedString>,
|
||||
action: Box<dyn Action>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Self {
|
||||
pub fn action(mut self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self {
|
||||
self.items.push(ContextMenuItem::Entry {
|
||||
label: label.into(),
|
||||
key_binding: KeyBinding::for_action(&*action, cx),
|
||||
action: Some(action.boxed_clone()),
|
||||
handler: Rc::new(move |cx| cx.dispatch_action(action.boxed_clone())),
|
||||
icon: None,
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn link(
|
||||
mut self,
|
||||
label: impl Into<SharedString>,
|
||||
action: Box<dyn Action>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Self {
|
||||
pub fn link(mut self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self {
|
||||
self.items.push(ContextMenuItem::Entry {
|
||||
label: label.into(),
|
||||
key_binding: KeyBinding::for_action(&*action, cx),
|
||||
action: Some(action.boxed_clone()),
|
||||
handler: Rc::new(move |cx| cx.dispatch_action(action.boxed_clone())),
|
||||
icon: Some(Icon::Link),
|
||||
});
|
||||
@@ -161,6 +153,37 @@ impl ContextMenu {
|
||||
self.select_last(&Default::default(), cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_action_dispatch(&mut self, dispatched: &Box<dyn Action>, cx: &mut ViewContext<Self>) {
|
||||
if let Some(ix) = self.items.iter().position(|item| {
|
||||
if let ContextMenuItem::Entry {
|
||||
action: Some(action),
|
||||
..
|
||||
} = item
|
||||
{
|
||||
action.partial_eq(&**dispatched)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) {
|
||||
self.selected_index = Some(ix);
|
||||
self.delayed = true;
|
||||
cx.notify();
|
||||
let action = dispatched.boxed_clone();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.background_executor()
|
||||
.timer(Duration::from_millis(50))
|
||||
.await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
cx.dispatch_action(action);
|
||||
this.cancel(&Default::default(), cx)
|
||||
})
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
} else {
|
||||
cx.propagate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ContextMenuItem {
|
||||
@@ -185,6 +208,22 @@ impl Render for ContextMenu {
|
||||
.on_action(cx.listener(ContextMenu::select_prev))
|
||||
.on_action(cx.listener(ContextMenu::confirm))
|
||||
.on_action(cx.listener(ContextMenu::cancel))
|
||||
.when(!self.delayed, |mut el| {
|
||||
for item in self.items.iter() {
|
||||
if let ContextMenuItem::Entry {
|
||||
action: Some(action),
|
||||
..
|
||||
} = item
|
||||
{
|
||||
el = el.on_boxed_action(
|
||||
action,
|
||||
cx.listener(ContextMenu::on_action_dispatch),
|
||||
);
|
||||
}
|
||||
}
|
||||
el
|
||||
})
|
||||
.on_blur(cx.listener(|this, _, cx| this.cancel(&Default::default(), cx)))
|
||||
.flex_none()
|
||||
.child(
|
||||
List::new().children(self.items.iter().enumerate().map(
|
||||
@@ -196,8 +235,8 @@ impl Render for ContextMenu {
|
||||
ContextMenuItem::Entry {
|
||||
label,
|
||||
handler,
|
||||
key_binding,
|
||||
icon,
|
||||
action,
|
||||
} => {
|
||||
let handler = handler.clone();
|
||||
let dismiss = cx.listener(|_, _, cx| cx.emit(DismissEvent));
|
||||
@@ -218,11 +257,10 @@ impl Render for ContextMenu {
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.child(label_element)
|
||||
.children(
|
||||
key_binding
|
||||
.clone()
|
||||
.map(|binding| div().ml_1().child(binding)),
|
||||
),
|
||||
.children(action.as_ref().and_then(|action| {
|
||||
KeyBinding::for_action(&**action, cx)
|
||||
.map(|binding| div().ml_1().child(binding))
|
||||
})),
|
||||
)
|
||||
.selected(Some(ix) == self.selected_index)
|
||||
.on_click(move |event, cx| {
|
||||
|
||||
@@ -1,15 +1,26 @@
|
||||
use gpui::{rems, svg, IntoElement, Svg};
|
||||
use gpui::{rems, svg, IntoElement, Rems, Svg};
|
||||
use strum::EnumIter;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Default, PartialEq, Copy, Clone)]
|
||||
pub enum IconSize {
|
||||
XSmall,
|
||||
Small,
|
||||
#[default]
|
||||
Medium,
|
||||
}
|
||||
|
||||
impl IconSize {
|
||||
pub fn rems(self) -> Rems {
|
||||
match self {
|
||||
IconSize::XSmall => rems(12. / 16.),
|
||||
IconSize::Small => rems(14. / 16.),
|
||||
IconSize::Medium => rems(16. / 16.),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone, EnumIter)]
|
||||
pub enum Icon {
|
||||
Ai,
|
||||
@@ -173,13 +184,8 @@ impl RenderOnce for IconElement {
|
||||
type Rendered = Svg;
|
||||
|
||||
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
||||
let svg_size = match self.size {
|
||||
IconSize::Small => rems(14. / 16.),
|
||||
IconSize::Medium => rems(16. / 16.),
|
||||
};
|
||||
|
||||
svg()
|
||||
.size(svg_size)
|
||||
.size(self.size.rems())
|
||||
.flex_none()
|
||||
.path(self.path)
|
||||
.text_color(self.color.color(cx))
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
use gpui::{Div, Position};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Default)]
|
||||
pub enum IndicatorStyle {
|
||||
#[default]
|
||||
Dot,
|
||||
Bar,
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct Indicator {
|
||||
position: Position,
|
||||
style: IndicatorStyle,
|
||||
color: Color,
|
||||
}
|
||||
|
||||
impl Indicator {
|
||||
pub fn dot() -> Self {
|
||||
Self {
|
||||
position: Position::Relative,
|
||||
style: IndicatorStyle::Dot,
|
||||
color: Color::Default,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bar() -> Self {
|
||||
Self {
|
||||
position: Position::Relative,
|
||||
style: IndicatorStyle::Dot,
|
||||
color: Color::Default,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn color(mut self, color: Color) -> Self {
|
||||
self.color = color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn absolute(mut self) -> Self {
|
||||
self.position = Position::Absolute;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for Indicator {
|
||||
type Rendered = Div;
|
||||
|
||||
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
||||
div()
|
||||
.flex_none()
|
||||
.map(|this| match self.style {
|
||||
IndicatorStyle::Dot => this.w_1p5().h_1p5().rounded_full(),
|
||||
IndicatorStyle::Bar => this.w_full().h_1p5().rounded_t_md(),
|
||||
})
|
||||
.when(self.position == Position::Absolute, |this| this.absolute())
|
||||
.bg(self.color.color(cx))
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::{h_stack, prelude::*, Icon, IconElement, IconSize};
|
||||
use gpui::{relative, rems, Action, Div, IntoElement, Keystroke};
|
||||
use gpui::{relative, rems, Action, Div, FocusHandle, IntoElement, Keystroke};
|
||||
|
||||
#[derive(IntoElement, Clone)]
|
||||
pub struct KeyBinding {
|
||||
@@ -49,12 +49,21 @@ impl RenderOnce for KeyBinding {
|
||||
|
||||
impl KeyBinding {
|
||||
pub fn for_action(action: &dyn Action, cx: &mut WindowContext) -> Option<Self> {
|
||||
// todo! this last is arbitrary, we want to prefer users key bindings over defaults,
|
||||
// and vim over normal (in vim mode), etc.
|
||||
let key_binding = cx.bindings_for_action(action).last().cloned()?;
|
||||
Some(Self::new(key_binding))
|
||||
}
|
||||
|
||||
// like for_action(), but lets you specify the context from which keybindings
|
||||
// are matched.
|
||||
pub fn for_action_in(
|
||||
action: &dyn Action,
|
||||
focus: &FocusHandle,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<Self> {
|
||||
let key_binding = cx.bindings_for_action_in(action, focus).last().cloned()?;
|
||||
Some(Self::new(key_binding))
|
||||
}
|
||||
|
||||
fn icon_for_key(keystroke: &Keystroke) -> Option<Icon> {
|
||||
let mut icon: Option<Icon> = None;
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use gpui::{
|
||||
px, AnyElement, ClickEvent, Div, ImageSource, MouseButton, MouseDownEvent, Pixels, Stateful,
|
||||
px, AnyElement, AnyView, ClickEvent, Div, ImageSource, MouseButton, MouseDownEvent, Pixels,
|
||||
Stateful,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
@@ -21,6 +22,7 @@ pub struct ListItem {
|
||||
inset: bool,
|
||||
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||
on_toggle: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||
tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView + 'static>>,
|
||||
on_secondary_mouse_down: Option<Rc<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
}
|
||||
@@ -38,6 +40,7 @@ impl ListItem {
|
||||
on_click: None,
|
||||
on_secondary_mouse_down: None,
|
||||
on_toggle: None,
|
||||
tooltip: None,
|
||||
children: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
@@ -55,6 +58,11 @@ impl ListItem {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
|
||||
self.tooltip = Some(Box::new(tooltip));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn inset(mut self, inset: bool) -> Self {
|
||||
self.inset = inset;
|
||||
self
|
||||
@@ -149,6 +157,7 @@ impl RenderOnce for ListItem {
|
||||
(on_mouse_down)(event, cx)
|
||||
})
|
||||
})
|
||||
.when_some(self.tooltip, |this, tooltip| this.tooltip(tooltip))
|
||||
.child(
|
||||
div()
|
||||
.when(self.inset, |this| this.px_2())
|
||||
|
||||
Reference in New Issue
Block a user