Merge branch 'main' into derive-element-redux
This commit is contained in:
commit
0798cfd58c
117 changed files with 7260 additions and 2951 deletions
|
@ -4,58 +4,101 @@ use std::rc::Rc;
|
|||
use crate::prelude::*;
|
||||
use crate::{v_stack, Label, List, ListEntry, ListItem, ListSeparator, ListSubHeader};
|
||||
use gpui::{
|
||||
<<<<<<< HEAD
|
||||
overlay, px, Action, AnchorCorner, AnyElement, Bounds, Dismiss, DispatchPhase, Div,
|
||||
FocusHandle, LayoutId, ManagedView, MouseButton, MouseDownEvent, Pixels, Point, Render,
|
||||
RenderOnce, View,
|
||||
=======
|
||||
overlay, px, Action, AnchorCorner, AnyElement, AppContext, Bounds, DispatchPhase, Div,
|
||||
EventEmitter, FocusHandle, FocusableView, LayoutId, ManagedView, Manager, MouseButton,
|
||||
MouseDownEvent, Pixels, Point, Render, View, VisualContext, WeakView,
|
||||
>>>>>>> main
|
||||
};
|
||||
|
||||
pub struct ContextMenu {
|
||||
items: Vec<ListItem>,
|
||||
focus_handle: FocusHandle,
|
||||
pub enum ContextMenuItem<V> {
|
||||
Separator(ListSeparator),
|
||||
Header(ListSubHeader),
|
||||
Entry(
|
||||
ListEntry<ContextMenu<V>>,
|
||||
Rc<dyn Fn(&mut V, &mut ViewContext<V>)>,
|
||||
),
|
||||
}
|
||||
|
||||
impl ManagedView for ContextMenu {
|
||||
fn focus_handle(&self, cx: &gpui::AppContext) -> FocusHandle {
|
||||
pub struct ContextMenu<V> {
|
||||
items: Vec<ContextMenuItem<V>>,
|
||||
focus_handle: FocusHandle,
|
||||
handle: WeakView<V>,
|
||||
}
|
||||
|
||||
impl<V: Render> FocusableView for ContextMenu<V> {
|
||||
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl ContextMenu {
|
||||
pub fn new(cx: &mut WindowContext) -> Self {
|
||||
Self {
|
||||
items: Default::default(),
|
||||
focus_handle: cx.focus_handle(),
|
||||
}
|
||||
impl<V: Render> EventEmitter<Manager> for ContextMenu<V> {}
|
||||
|
||||
impl<V: Render> ContextMenu<V> {
|
||||
pub fn build(
|
||||
cx: &mut ViewContext<V>,
|
||||
f: impl FnOnce(Self, &mut ViewContext<Self>) -> Self,
|
||||
) -> View<Self> {
|
||||
let handle = cx.view().downgrade();
|
||||
cx.build_view(|cx| {
|
||||
f(
|
||||
Self {
|
||||
handle,
|
||||
items: Default::default(),
|
||||
focus_handle: cx.focus_handle(),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn header(mut self, title: impl Into<SharedString>) -> Self {
|
||||
self.items.push(ListItem::Header(ListSubHeader::new(title)));
|
||||
self.items
|
||||
.push(ContextMenuItem::Header(ListSubHeader::new(title)));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn separator(mut self) -> Self {
|
||||
self.items.push(ListItem::Separator(ListSeparator));
|
||||
self.items.push(ContextMenuItem::Separator(ListSeparator));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn entry(mut self, label: Label, action: Box<dyn Action>) -> Self {
|
||||
self.items.push(ListEntry::new(label).action(action).into());
|
||||
pub fn entry(
|
||||
mut self,
|
||||
view: ListEntry<Self>,
|
||||
on_click: impl Fn(&mut V, &mut ViewContext<V>) + 'static,
|
||||
) -> Self {
|
||||
self.items
|
||||
.push(ContextMenuItem::Entry(view, Rc::new(on_click)));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn action(self, view: ListEntry<Self>, action: Box<dyn Action>) -> Self {
|
||||
// todo: add the keybindings to the list entry
|
||||
self.entry(view, move |_, cx| cx.dispatch_action(action.boxed_clone()))
|
||||
}
|
||||
|
||||
pub fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
|
||||
// todo!()
|
||||
cx.emit(Dismiss);
|
||||
cx.emit(Manager::Dismiss);
|
||||
}
|
||||
|
||||
pub fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
|
||||
cx.emit(Dismiss);
|
||||
cx.emit(Manager::Dismiss);
|
||||
}
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
impl Render<Self> for ContextMenu {
|
||||
=======
|
||||
impl<V: Render> Render for ContextMenu<V> {
|
||||
>>>>>>> main
|
||||
type Element = Div<Self>;
|
||||
// todo!()
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
div().elevation_2(cx).flex().flex_row().child(
|
||||
v_stack()
|
||||
|
@ -72,7 +115,25 @@ impl Render<Self> for ContextMenu {
|
|||
// .bg(cx.theme().colors().elevated_surface_background)
|
||||
// .border()
|
||||
// .border_color(cx.theme().colors().border)
|
||||
.child(List::new(self.items.clone())),
|
||||
.child(List::new(
|
||||
self.items
|
||||
.iter()
|
||||
.map(|item| match item {
|
||||
ContextMenuItem::Separator(separator) => {
|
||||
ListItem::Separator(separator.clone())
|
||||
}
|
||||
ContextMenuItem::Header(header) => ListItem::Header(header.clone()),
|
||||
ContextMenuItem::Entry(entry, callback) => {
|
||||
let callback = callback.clone();
|
||||
let handle = self.handle.clone();
|
||||
ListItem::Entry(entry.clone().on_click(move |this, cx| {
|
||||
handle.update(cx, |view, cx| callback(view, cx)).ok();
|
||||
cx.emit(Manager::Dismiss);
|
||||
}))
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -218,12 +279,13 @@ impl<V: 'static, M: ManagedView> Element<V> for MenuHandle<V, M> {
|
|||
let new_menu = (builder)(view_state, cx);
|
||||
let menu2 = menu.clone();
|
||||
cx.subscribe(&new_menu, move |this, modal, e, cx| match e {
|
||||
&Dismiss => {
|
||||
&Manager::Dismiss => {
|
||||
*menu2.borrow_mut() = None;
|
||||
cx.notify();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
cx.focus_view(&new_menu);
|
||||
*menu.borrow_mut() = Some(new_menu);
|
||||
|
||||
*position.borrow_mut() = if attach.is_some() && child_layout_id.is_some() {
|
||||
|
@ -258,16 +320,25 @@ pub use stories::*;
|
|||
mod stories {
|
||||
use super::*;
|
||||
use crate::story::Story;
|
||||
use gpui::{actions, Div, Render, VisualContext};
|
||||
use gpui::{actions, Div, Render};
|
||||
|
||||
actions!(PrintCurrentDate);
|
||||
actions!(PrintCurrentDate, PrintBestFood);
|
||||
|
||||
fn build_menu(cx: &mut WindowContext, header: impl Into<SharedString>) -> View<ContextMenu> {
|
||||
cx.build_view(|cx| {
|
||||
ContextMenu::new(cx).header(header).separator().entry(
|
||||
Label::new("Print current time"),
|
||||
PrintCurrentDate.boxed_clone(),
|
||||
)
|
||||
fn build_menu<V: Render>(
|
||||
cx: &mut ViewContext<V>,
|
||||
header: impl Into<SharedString>,
|
||||
) -> View<ContextMenu<V>> {
|
||||
let handle = cx.view().clone();
|
||||
ContextMenu::build(cx, |menu, _| {
|
||||
menu.header(header)
|
||||
.separator()
|
||||
.entry(ListEntry::new(Label::new("Print current time")), |v, cx| {
|
||||
println!("dispatching PrintCurrentTime action");
|
||||
cx.dispatch_action(PrintCurrentDate.boxed_clone())
|
||||
})
|
||||
.entry(ListEntry::new(Label::new("Print best food")), |v, cx| {
|
||||
cx.dispatch_action(PrintBestFood.boxed_clone())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -279,10 +350,14 @@ mod stories {
|
|||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container(cx)
|
||||
.on_action(|_, _: &PrintCurrentDate, _| {
|
||||
println!("printing unix time!");
|
||||
if let Ok(unix_time) = std::time::UNIX_EPOCH.elapsed() {
|
||||
println!("Current Unix time is {:?}", unix_time.as_secs());
|
||||
}
|
||||
})
|
||||
.on_action(|_, _: &PrintBestFood, _| {
|
||||
println!("burrito");
|
||||
})
|
||||
.flex()
|
||||
.flex_row()
|
||||
.justify_between()
|
||||
|
|
|
@ -16,8 +16,12 @@ pub enum Icon {
|
|||
ArrowLeft,
|
||||
ArrowRight,
|
||||
ArrowUpRight,
|
||||
AtSign,
|
||||
AudioOff,
|
||||
AudioOn,
|
||||
Bell,
|
||||
BellOff,
|
||||
BellRing,
|
||||
Bolt,
|
||||
Check,
|
||||
ChevronDown,
|
||||
|
@ -26,12 +30,14 @@ pub enum Icon {
|
|||
ChevronUp,
|
||||
Close,
|
||||
Collab,
|
||||
Copilot,
|
||||
Dash,
|
||||
Exit,
|
||||
Envelope,
|
||||
ExclamationTriangle,
|
||||
Exit,
|
||||
File,
|
||||
FileGeneric,
|
||||
FileDoc,
|
||||
FileGeneric,
|
||||
FileGit,
|
||||
FileLock,
|
||||
FileRust,
|
||||
|
@ -44,6 +50,7 @@ pub enum Icon {
|
|||
InlayHint,
|
||||
MagicWand,
|
||||
MagnifyingGlass,
|
||||
MailOpen,
|
||||
Maximize,
|
||||
Menu,
|
||||
MessageBubbles,
|
||||
|
@ -59,13 +66,6 @@ pub enum Icon {
|
|||
SplitMessage,
|
||||
Terminal,
|
||||
XCircle,
|
||||
Copilot,
|
||||
Envelope,
|
||||
Bell,
|
||||
BellOff,
|
||||
BellRing,
|
||||
MailOpen,
|
||||
AtSign,
|
||||
}
|
||||
|
||||
impl Icon {
|
||||
|
@ -75,8 +75,12 @@ impl Icon {
|
|||
Icon::ArrowLeft => "icons/arrow_left.svg",
|
||||
Icon::ArrowRight => "icons/arrow_right.svg",
|
||||
Icon::ArrowUpRight => "icons/arrow_up_right.svg",
|
||||
Icon::AtSign => "icons/at-sign.svg",
|
||||
Icon::AudioOff => "icons/speaker-off.svg",
|
||||
Icon::AudioOn => "icons/speaker-loud.svg",
|
||||
Icon::Bell => "icons/bell.svg",
|
||||
Icon::BellOff => "icons/bell-off.svg",
|
||||
Icon::BellRing => "icons/bell-ring.svg",
|
||||
Icon::Bolt => "icons/bolt.svg",
|
||||
Icon::Check => "icons/check.svg",
|
||||
Icon::ChevronDown => "icons/chevron_down.svg",
|
||||
|
@ -85,12 +89,14 @@ impl Icon {
|
|||
Icon::ChevronUp => "icons/chevron_up.svg",
|
||||
Icon::Close => "icons/x.svg",
|
||||
Icon::Collab => "icons/user_group_16.svg",
|
||||
Icon::Copilot => "icons/copilot.svg",
|
||||
Icon::Dash => "icons/dash.svg",
|
||||
Icon::Exit => "icons/exit.svg",
|
||||
Icon::Envelope => "icons/feedback.svg",
|
||||
Icon::ExclamationTriangle => "icons/warning.svg",
|
||||
Icon::Exit => "icons/exit.svg",
|
||||
Icon::File => "icons/file.svg",
|
||||
Icon::FileGeneric => "icons/file_icons/file.svg",
|
||||
Icon::FileDoc => "icons/file_icons/book.svg",
|
||||
Icon::FileGeneric => "icons/file_icons/file.svg",
|
||||
Icon::FileGit => "icons/file_icons/git.svg",
|
||||
Icon::FileLock => "icons/file_icons/lock.svg",
|
||||
Icon::FileRust => "icons/file_icons/rust.svg",
|
||||
|
@ -103,6 +109,7 @@ impl Icon {
|
|||
Icon::InlayHint => "icons/inlay_hint.svg",
|
||||
Icon::MagicWand => "icons/magic-wand.svg",
|
||||
Icon::MagnifyingGlass => "icons/magnifying_glass.svg",
|
||||
Icon::MailOpen => "icons/mail-open.svg",
|
||||
Icon::Maximize => "icons/maximize.svg",
|
||||
Icon::Menu => "icons/menu.svg",
|
||||
Icon::MessageBubbles => "icons/conversations.svg",
|
||||
|
@ -118,13 +125,6 @@ impl Icon {
|
|||
Icon::SplitMessage => "icons/split_message.svg",
|
||||
Icon::Terminal => "icons/terminal.svg",
|
||||
Icon::XCircle => "icons/error.svg",
|
||||
Icon::Copilot => "icons/copilot.svg",
|
||||
Icon::Envelope => "icons/feedback.svg",
|
||||
Icon::Bell => "icons/bell.svg",
|
||||
Icon::BellOff => "icons/bell-off.svg",
|
||||
Icon::BellRing => "icons/bell-ring.svg",
|
||||
Icon::MailOpen => "icons/mail-open.svg",
|
||||
Icon::AtSign => "icons/at-sign.svg",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,16 +82,22 @@ pub enum ModifierKey {
|
|||
Shift,
|
||||
}
|
||||
|
||||
actions!(NoAction);
|
||||
|
||||
pub fn binding(key: &str) -> gpui::KeyBinding {
|
||||
gpui::KeyBinding::new(key, NoAction {}, None)
|
||||
}
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
mod stories {
|
||||
use super::*;
|
||||
use crate::Story;
|
||||
pub use crate::KeyBinding;
|
||||
use crate::{binding, Story};
|
||||
use gpui::{actions, Div, Render};
|
||||
use itertools::Itertools;
|
||||
|
||||
pub struct KeybindingStory;
|
||||
|
||||
actions!(NoAction);
|
||||
|
@ -100,7 +106,7 @@ mod stories {
|
|||
gpui::KeyBinding::new(key, NoAction {}, None)
|
||||
}
|
||||
|
||||
impl Render<Self> for KeybindingStory {
|
||||
impl Render for KeybindingStory {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use gpui::{div, Action, Div, RenderOnce};
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::settings::user_settings;
|
||||
use crate::{
|
||||
|
@ -232,36 +233,36 @@ pub enum ListEntrySize {
|
|||
}
|
||||
|
||||
#[derive(RenderOnce, Clone)]
|
||||
pub enum ListItem {
|
||||
Entry(ListEntry),
|
||||
pub enum ListItem<V: 'static> {
|
||||
Entry(ListEntry<V>),
|
||||
Separator(ListSeparator),
|
||||
Header(ListSubHeader),
|
||||
}
|
||||
|
||||
impl From<ListEntry> for ListItem {
|
||||
fn from(entry: ListEntry) -> Self {
|
||||
impl<V: 'static> From<ListEntry<V>> for ListItem<V> {
|
||||
fn from(entry: ListEntry<V>) -> Self {
|
||||
Self::Entry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ListSeparator> for ListItem {
|
||||
impl<V: 'static> From<ListSeparator> for ListItem<V> {
|
||||
fn from(entry: ListSeparator) -> Self {
|
||||
Self::Separator(entry)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ListSubHeader> for ListItem {
|
||||
impl<V: 'static> From<ListSubHeader> for ListItem<V> {
|
||||
fn from(entry: ListSubHeader) -> Self {
|
||||
Self::Header(entry)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> Component<V> for ListItem {
|
||||
impl<V: 'static> Component<V> for ListItem<V> {
|
||||
type Rendered = Div<V>;
|
||||
|
||||
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
|
||||
match self {
|
||||
ListItem::Entry(entry) => div().child(entry.render(view, cx)),
|
||||
ListItem::Entry(entry) => div().child(entry.render(ix, cx)),
|
||||
ListItem::Separator(separator) => div().child(separator.render(view, cx)),
|
||||
ListItem::Header(header) => div().child(header.render(view, cx)),
|
||||
}
|
||||
|
@ -273,7 +274,7 @@ impl ListItem {
|
|||
Self::Entry(ListEntry::new(label))
|
||||
}
|
||||
|
||||
pub fn as_entry(&mut self) -> Option<&mut ListEntry> {
|
||||
pub fn as_entry(&mut self) -> Option<&mut ListEntry<V>> {
|
||||
if let Self::Entry(entry) = self {
|
||||
Some(entry)
|
||||
} else {
|
||||
|
@ -283,7 +284,7 @@ impl ListItem {
|
|||
}
|
||||
|
||||
// #[derive(RenderOnce)]
|
||||
pub struct ListEntry {
|
||||
pub struct ListEntry<V> {
|
||||
disabled: bool,
|
||||
// TODO: Reintroduce this
|
||||
// disclosure_control_style: DisclosureControlVisibility,
|
||||
|
@ -294,15 +295,13 @@ pub struct ListEntry {
|
|||
size: ListEntrySize,
|
||||
toggle: Toggle,
|
||||
variant: ListItemVariant,
|
||||
on_click: Option<Box<dyn Action>>,
|
||||
on_click: Option<Rc<dyn Fn(&mut V, &mut ViewContext<V>) + 'static>>,
|
||||
}
|
||||
|
||||
impl Clone for ListEntry {
|
||||
impl<V> Clone for ListEntry<V> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
disabled: self.disabled,
|
||||
// TODO: Reintroduce this
|
||||
// disclosure_control_style: DisclosureControlVisibility,
|
||||
indent_level: self.indent_level,
|
||||
label: self.label.clone(),
|
||||
left_slot: self.left_slot.clone(),
|
||||
|
@ -310,12 +309,12 @@ impl Clone for ListEntry {
|
|||
size: self.size,
|
||||
toggle: self.toggle,
|
||||
variant: self.variant,
|
||||
on_click: self.on_click.as_ref().map(|opt| opt.boxed_clone()),
|
||||
on_click: self.on_click.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ListEntry {
|
||||
impl<V: 'static> ListEntry<V> {
|
||||
pub fn new(label: Label) -> Self {
|
||||
Self {
|
||||
disabled: false,
|
||||
|
@ -330,8 +329,8 @@ impl ListEntry {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn action(mut self, action: impl Into<Box<dyn Action>>) -> Self {
|
||||
self.on_click = Some(action.into());
|
||||
pub fn on_click(mut self, handler: impl Fn(&mut V, &mut ViewContext<V>) + 'static) -> Self {
|
||||
self.on_click = Some(Rc::new(handler));
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -370,7 +369,7 @@ impl ListEntry {
|
|||
self
|
||||
}
|
||||
|
||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
|
||||
fn render(self, ix: usize, cx: &mut ViewContext<V>) -> Stateful<V, Div<V>> {
|
||||
let settings = user_settings(cx);
|
||||
|
||||
let left_content = match self.left_slot.clone() {
|
||||
|
@ -391,21 +390,21 @@ impl ListEntry {
|
|||
ListEntrySize::Medium => div().h_7(),
|
||||
};
|
||||
div()
|
||||
.id(ix)
|
||||
.relative()
|
||||
.hover(|mut style| {
|
||||
style.background = Some(cx.theme().colors().editor_background.into());
|
||||
style
|
||||
})
|
||||
.on_mouse_down(gpui::MouseButton::Left, {
|
||||
let action = self.on_click.map(|action| action.boxed_clone());
|
||||
.on_click({
|
||||
let on_click = self.on_click.clone();
|
||||
|
||||
move |entry: &mut V, event, cx| {
|
||||
if let Some(action) = action.as_ref() {
|
||||
cx.dispatch_action(action.boxed_clone());
|
||||
move |view: &mut V, event, cx| {
|
||||
if let Some(on_click) = &on_click {
|
||||
(on_click)(view, cx)
|
||||
}
|
||||
}
|
||||
})
|
||||
.group("")
|
||||
.bg(cx.theme().colors().surface_background)
|
||||
// TODO: Add focus state
|
||||
// .when(self.state == InteractionState::Focused, |this| {
|
||||
|
@ -458,7 +457,7 @@ impl<V: 'static> Component<V> for ListSeparator {
|
|||
}
|
||||
|
||||
#[derive(RenderOnce)]
|
||||
pub struct List {
|
||||
pub struct List<V: 'static> {
|
||||
items: Vec<ListItem>,
|
||||
/// Message to display when the list is empty
|
||||
/// Defaults to "No items"
|
||||
|
@ -467,7 +466,7 @@ pub struct List {
|
|||
toggle: Toggle,
|
||||
}
|
||||
|
||||
impl<V: 'static> Component<V> for List {
|
||||
impl<V: 'static> Component<V> for List<V> {
|
||||
type Rendered = Div<V>;
|
||||
|
||||
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
|
||||
|
@ -487,7 +486,7 @@ impl<V: 'static> Component<V> for List {
|
|||
}
|
||||
}
|
||||
|
||||
impl List {
|
||||
impl<V: 'static> List<V> {
|
||||
pub fn new(items: Vec<ListItem>) -> Self {
|
||||
Self {
|
||||
items,
|
||||
|
@ -514,7 +513,12 @@ impl List {
|
|||
|
||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
|
||||
let list_content = match (self.items.is_empty(), self.toggle) {
|
||||
(false, _) => div().children(self.items),
|
||||
(false, _) => div().children(
|
||||
self.items
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(ix, item)| item.render(view, ix, cx)),
|
||||
),
|
||||
(true, Toggle::Toggled(false)) => div(),
|
||||
(true, _) => {
|
||||
div().child(Label::new(self.empty_message.clone()).color(TextColor::Muted))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue