Detailed changes
@@ -71,11 +71,12 @@ impl EntityMap {
#[track_caller]
pub fn lease<'a, T>(&mut self, model: &'a Model<T>) -> Lease<'a, T> {
self.assert_valid_context(model);
- let entity = Some(
- self.entities
- .remove(model.entity_id)
- .expect("Circular entity lease. Is the entity already being updated?"),
- );
+ let entity = Some(self.entities.remove(model.entity_id).unwrap_or_else(|| {
+ panic!(
+ "Circular entity lease of {}. Is it already being updated?",
+ std::any::type_name::<T>()
+ )
+ }));
Lease {
model,
entity,
@@ -1124,9 +1124,14 @@ where
}
}
}
+ // if self.hover_style.is_some() {
if bounds.contains_point(&mouse_position) {
+ // eprintln!("div hovered {bounds:?} {mouse_position:?}");
style.refine(&self.hover_style);
+ } else {
+ // eprintln!("div NOT hovered {bounds:?} {mouse_position:?}");
}
+ // }
if let Some(drag) = cx.active_drag.take() {
for (state_type, group_drag_style) in &self.group_drag_over_styles {
@@ -1205,10 +1205,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
InputEvent::MouseMove(_) if !(is_active || lock.kind == WindowKind::PopUp) => return,
- InputEvent::MouseUp(MouseUpEvent {
- button: MouseButton::Left,
- ..
- }) => {
+ InputEvent::MouseUp(MouseUpEvent { .. }) => {
lock.synthetic_drag_counter += 1;
}
@@ -6,39 +6,66 @@ use crate::{v_stack, Label, List, ListEntry, ListItem, ListSeparator, ListSubHea
use gpui::{
overlay, px, Action, AnchorCorner, AnyElement, Bounds, Dismiss, DispatchPhase, Div,
FocusHandle, LayoutId, ManagedView, MouseButton, MouseDownEvent, Pixels, Point, Render, View,
+ VisualContext, WeakView,
};
-pub struct ContextMenu {
- items: Vec<ListItem>,
+pub enum ContextMenuItem<V> {
+ Separator(ListSeparator),
+ Header(ListSubHeader),
+ Entry(
+ ListEntry<ContextMenu<V>>,
+ Rc<dyn Fn(&mut V, &mut ViewContext<V>)>,
+ ),
+}
+
+pub struct ContextMenu<V> {
+ items: Vec<ContextMenuItem<V>>,
focus_handle: FocusHandle,
+ handle: WeakView<V>,
}
-impl ManagedView for ContextMenu {
+impl<V: Render> ManagedView for ContextMenu<V> {
fn focus_handle(&self, cx: &gpui::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> 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
}
@@ -52,9 +79,9 @@ impl ContextMenu {
}
}
-impl Render for ContextMenu {
+impl<V: Render> Render for ContextMenu<V> {
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()
@@ -71,7 +98,25 @@ impl Render 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(Dismiss);
+ }))
+ }
+ })
+ .collect(),
+ )),
)
}
}
@@ -232,6 +277,7 @@ impl<V: 'static, M: ManagedView> Element<V> for MenuHandle<V, M> {
}
})
.detach();
+ cx.focus_view(&new_menu);
*menu.borrow_mut() = Some(new_menu);
*position.borrow_mut() = if attach.is_some() && child_layout_id.is_some() {
@@ -260,16 +306,25 @@ pub use stories::*;
mod stories {
use super::*;
use crate::story::Story;
- use gpui::{actions, Div, Render, VisualContext};
-
- actions!(PrintCurrentDate);
-
- 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(),
- )
+ use gpui::{actions, Div, Render};
+
+ actions!(PrintCurrentDate, PrintBestFood);
+
+ 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())
+ })
})
}
@@ -281,10 +336,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()
@@ -1,4 +1,6 @@
-use gpui::{div, Action};
+use std::rc::Rc;
+
+use gpui::{div, Div, Stateful, StatefulInteractiveComponent};
use crate::settings::user_settings;
use crate::{
@@ -172,35 +174,35 @@ pub enum ListEntrySize {
Medium,
}
-#[derive(Component, Clone)]
-pub enum ListItem {
- Entry(ListEntry),
+#[derive(Clone)]
+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 ListItem {
- fn render<V: 'static>(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+impl<V: 'static> ListItem<V> {
+ fn render(self, view: &mut V, ix: usize, cx: &mut ViewContext<V>) -> impl Component<V> {
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)),
}
@@ -210,7 +212,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 {
@@ -219,8 +221,7 @@ impl ListItem {
}
}
-#[derive(Component)]
-pub struct ListEntry {
+pub struct ListEntry<V> {
disabled: bool,
// TODO: Reintroduce this
// disclosure_control_style: DisclosureControlVisibility,
@@ -231,15 +232,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(),
@@ -247,12 +246,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,
@@ -267,8 +266,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
}
@@ -307,7 +306,7 @@ impl ListEntry {
self
}
- fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<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() {
@@ -328,21 +327,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| {
@@ -391,8 +390,8 @@ impl ListSeparator {
}
#[derive(Component)]
-pub struct List {
- items: Vec<ListItem>,
+pub struct List<V: 'static> {
+ items: Vec<ListItem<V>>,
/// Message to display when the list is empty
/// Defaults to "No items"
empty_message: SharedString,
@@ -400,8 +399,8 @@ pub struct List {
toggle: Toggle,
}
-impl List {
- pub fn new(items: Vec<ListItem>) -> Self {
+impl<V: 'static> List<V> {
+ pub fn new(items: Vec<ListItem<V>>) -> Self {
Self {
items,
empty_message: "No items".into(),
@@ -425,9 +424,14 @@ impl List {
self
}
- fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+ fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Component<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))
@@ -478,7 +478,7 @@ pub fn static_new_notification_items_2<V: 'static>() -> Vec<Notification<V>> {
]
}
-pub fn static_project_panel_project_items() -> Vec<ListItem> {
+pub fn static_project_panel_project_items<V>() -> Vec<ListItem<V>> {
vec![
ListEntry::new(Label::new("zed"))
.left_icon(Icon::FolderOpen.into())
@@ -605,7 +605,7 @@ pub fn static_project_panel_project_items() -> Vec<ListItem> {
.collect()
}
-pub fn static_project_panel_single_items() -> Vec<ListItem> {
+pub fn static_project_panel_single_items<V>() -> Vec<ListItem<V>> {
vec![
ListEntry::new(Label::new("todo.md"))
.left_icon(Icon::FileDoc.into())
@@ -622,7 +622,7 @@ pub fn static_project_panel_single_items() -> Vec<ListItem> {
.collect()
}
-pub fn static_collab_panel_current_call() -> Vec<ListItem> {
+pub fn static_collab_panel_current_call<V>() -> Vec<ListItem<V>> {
vec![
ListEntry::new(Label::new("as-cii")).left_avatar("http://github.com/as-cii.png?s=50"),
ListEntry::new(Label::new("nathansobo"))
@@ -635,7 +635,7 @@ pub fn static_collab_panel_current_call() -> Vec<ListItem> {
.collect()
}
-pub fn static_collab_panel_channels() -> Vec<ListItem> {
+pub fn static_collab_panel_channels<V>() -> Vec<ListItem<V>> {
vec![
ListEntry::new(Label::new("zed"))
.left_icon(Icon::Hash.into())