Allow dragging and dropping files in the project panel (#3602)
Also, fix a bug that prevented drag and drop from working in the collab panel.
This commit is contained in:
commit
a9f817fc14
7 changed files with 196 additions and 125 deletions
|
@ -2557,7 +2557,7 @@ impl CollabPanel {
|
|||
let channel = channel.clone();
|
||||
move |cx| {
|
||||
let channel = channel.clone();
|
||||
cx.build_view({ |cx| DraggedChannelView { channel, width } })
|
||||
cx.build_view(|cx| DraggedChannelView { channel, width })
|
||||
}
|
||||
})
|
||||
.drag_over::<DraggedChannelView>(|style| {
|
||||
|
|
|
@ -763,6 +763,11 @@ impl InteractiveBounds {
|
|||
pub fn visibly_contains(&self, point: &Point<Pixels>, cx: &WindowContext) -> bool {
|
||||
self.bounds.contains(point) && cx.was_top_layer(&point, &self.stacking_order)
|
||||
}
|
||||
|
||||
pub fn drag_target_contains(&self, point: &Point<Pixels>, cx: &WindowContext) -> bool {
|
||||
self.bounds.contains(point)
|
||||
&& cx.was_top_layer_under_active_drag(&point, &self.stacking_order)
|
||||
}
|
||||
}
|
||||
|
||||
impl Interactivity {
|
||||
|
@ -888,30 +893,32 @@ impl Interactivity {
|
|||
if cx.active_drag.is_some() {
|
||||
let drop_listeners = mem::take(&mut self.drop_listeners);
|
||||
let interactive_bounds = interactive_bounds.clone();
|
||||
cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& interactive_bounds.visibly_contains(&event.position, &cx)
|
||||
{
|
||||
if let Some(drag_state_type) =
|
||||
cx.active_drag.as_ref().map(|drag| drag.view.entity_type())
|
||||
if !drop_listeners.is_empty() {
|
||||
cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& interactive_bounds.drag_target_contains(&event.position, cx)
|
||||
{
|
||||
for (drop_state_type, listener) in &drop_listeners {
|
||||
if *drop_state_type == drag_state_type {
|
||||
let drag = cx
|
||||
.active_drag
|
||||
.take()
|
||||
.expect("checked for type drag state type above");
|
||||
if let Some(drag_state_type) =
|
||||
cx.active_drag.as_ref().map(|drag| drag.view.entity_type())
|
||||
{
|
||||
for (drop_state_type, listener) in &drop_listeners {
|
||||
if *drop_state_type == drag_state_type {
|
||||
let drag = cx
|
||||
.active_drag
|
||||
.take()
|
||||
.expect("checked for type drag state type above");
|
||||
|
||||
listener(drag.view.clone(), cx);
|
||||
cx.notify();
|
||||
cx.stop_propagation();
|
||||
listener(drag.view.clone(), cx);
|
||||
cx.notify();
|
||||
cx.stop_propagation();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cx.active_drag = None;
|
||||
}
|
||||
} else {
|
||||
cx.active_drag = None;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let click_listeners = mem::take(&mut self.click_listeners);
|
||||
|
|
|
@ -38,6 +38,8 @@ use std::{
|
|||
};
|
||||
use util::ResultExt;
|
||||
|
||||
const ACTIVE_DRAG_Z_INDEX: u32 = 1;
|
||||
|
||||
/// A global stacking order, which is created by stacking successive z-index values.
|
||||
/// Each z-index will always be interpreted in the context of its parent z-index.
|
||||
#[derive(Deref, DerefMut, Ord, PartialOrd, Eq, PartialEq, Clone, Default, Debug)]
|
||||
|
@ -907,6 +909,23 @@ impl<'a> WindowContext<'a> {
|
|||
false
|
||||
}
|
||||
|
||||
pub fn was_top_layer_under_active_drag(
|
||||
&self,
|
||||
point: &Point<Pixels>,
|
||||
level: &StackingOrder,
|
||||
) -> bool {
|
||||
for (stack, bounds) in self.window.rendered_frame.depth_map.iter() {
|
||||
if stack.starts_with(&[ACTIVE_DRAG_Z_INDEX]) {
|
||||
continue;
|
||||
}
|
||||
if bounds.contains(point) {
|
||||
return level.starts_with(stack) || stack.starts_with(level);
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Called during painting to get the current stacking order.
|
||||
pub fn stacking_order(&self) -> &StackingOrder {
|
||||
&self.window.next_frame.z_index_stack
|
||||
|
@ -1238,7 +1257,7 @@ impl<'a> WindowContext<'a> {
|
|||
});
|
||||
|
||||
if let Some(active_drag) = self.app.active_drag.take() {
|
||||
self.with_z_index(1, |cx| {
|
||||
self.with_z_index(ACTIVE_DRAG_Z_INDEX, |cx| {
|
||||
let offset = cx.mouse_position() - active_drag.cursor_offset;
|
||||
let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
|
||||
active_drag.view.draw(offset, available_space, cx);
|
||||
|
|
|
@ -29,6 +29,7 @@ use std::{
|
|||
path::Path,
|
||||
sync::Arc,
|
||||
};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{prelude::*, v_stack, ContextMenu, IconElement, Label, ListItem};
|
||||
use unicase::UniCase;
|
||||
use util::{maybe, ResultExt, TryFutureExt};
|
||||
|
@ -55,7 +56,7 @@ pub struct ProjectPanel {
|
|||
clipboard_entry: Option<ClipboardEntry>,
|
||||
_dragged_entry_destination: Option<Arc<Path>>,
|
||||
_workspace: WeakView<Workspace>,
|
||||
width: Option<f32>,
|
||||
width: Option<Pixels>,
|
||||
pending_serialization: Task<Option<()>>,
|
||||
}
|
||||
|
||||
|
@ -86,7 +87,7 @@ pub enum ClipboardEntry {
|
|||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct EntryDetails {
|
||||
filename: String,
|
||||
icon: Option<Arc<str>>,
|
||||
|
@ -162,6 +163,12 @@ struct SerializedProjectPanel {
|
|||
width: Option<f32>,
|
||||
}
|
||||
|
||||
struct DraggedProjectEntryView {
|
||||
entry_id: ProjectEntryId,
|
||||
details: EntryDetails,
|
||||
width: Pixels,
|
||||
}
|
||||
|
||||
impl ProjectPanel {
|
||||
fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
|
||||
let project = workspace.project().clone();
|
||||
|
@ -236,7 +243,6 @@ impl ProjectPanel {
|
|||
context_menu: None,
|
||||
filename_editor,
|
||||
clipboard_entry: None,
|
||||
// context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)),
|
||||
_dragged_entry_destination: None,
|
||||
_workspace: workspace.weak_handle(),
|
||||
width: None,
|
||||
|
@ -331,7 +337,7 @@ impl ProjectPanel {
|
|||
let panel = ProjectPanel::new(workspace, cx);
|
||||
if let Some(serialized_panel) = serialized_panel {
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.width = serialized_panel.width;
|
||||
panel.width = serialized_panel.width.map(px);
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
|
@ -346,7 +352,9 @@ impl ProjectPanel {
|
|||
KEY_VALUE_STORE
|
||||
.write_kvp(
|
||||
PROJECT_PANEL_KEY.into(),
|
||||
serde_json::to_string(&SerializedProjectPanel { width })?,
|
||||
serde_json::to_string(&SerializedProjectPanel {
|
||||
width: width.map(|p| p.0),
|
||||
})?,
|
||||
)
|
||||
.await?;
|
||||
anyhow::Ok(())
|
||||
|
@ -1003,37 +1011,36 @@ impl ProjectPanel {
|
|||
}
|
||||
}
|
||||
|
||||
// todo!()
|
||||
// fn move_entry(
|
||||
// &mut self,
|
||||
// entry_to_move: ProjectEntryId,
|
||||
// destination: ProjectEntryId,
|
||||
// destination_is_file: bool,
|
||||
// cx: &mut ViewContext<Self>,
|
||||
// ) {
|
||||
// let destination_worktree = self.project.update(cx, |project, cx| {
|
||||
// let entry_path = project.path_for_entry(entry_to_move, cx)?;
|
||||
// let destination_entry_path = project.path_for_entry(destination, cx)?.path.clone();
|
||||
fn move_entry(
|
||||
&mut self,
|
||||
entry_to_move: ProjectEntryId,
|
||||
destination: ProjectEntryId,
|
||||
destination_is_file: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let destination_worktree = self.project.update(cx, |project, cx| {
|
||||
let entry_path = project.path_for_entry(entry_to_move, cx)?;
|
||||
let destination_entry_path = project.path_for_entry(destination, cx)?.path.clone();
|
||||
|
||||
// let mut destination_path = destination_entry_path.as_ref();
|
||||
// if destination_is_file {
|
||||
// destination_path = destination_path.parent()?;
|
||||
// }
|
||||
let mut destination_path = destination_entry_path.as_ref();
|
||||
if destination_is_file {
|
||||
destination_path = destination_path.parent()?;
|
||||
}
|
||||
|
||||
// let mut new_path = destination_path.to_path_buf();
|
||||
// new_path.push(entry_path.path.file_name()?);
|
||||
// if new_path != entry_path.path.as_ref() {
|
||||
// let task = project.rename_entry(entry_to_move, new_path, cx);
|
||||
// cx.foreground_executor().spawn(task).detach_and_log_err(cx);
|
||||
// }
|
||||
let mut new_path = destination_path.to_path_buf();
|
||||
new_path.push(entry_path.path.file_name()?);
|
||||
if new_path != entry_path.path.as_ref() {
|
||||
let task = project.rename_entry(entry_to_move, new_path, cx);
|
||||
cx.foreground_executor().spawn(task).detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
// Some(project.worktree_id_for_entry(destination, cx)?)
|
||||
// });
|
||||
Some(project.worktree_id_for_entry(destination, cx)?)
|
||||
});
|
||||
|
||||
// if let Some(destination_worktree) = destination_worktree {
|
||||
// self.expand_entry(destination_worktree, destination, cx);
|
||||
// }
|
||||
// }
|
||||
if let Some(destination_worktree) = destination_worktree {
|
||||
self.expand_entry(destination_worktree, destination, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn index_for_selection(&self, selection: Selection) -> Option<(usize, usize, usize)> {
|
||||
let mut entry_index = 0;
|
||||
|
@ -1349,15 +1356,15 @@ impl ProjectPanel {
|
|||
&self,
|
||||
entry_id: ProjectEntryId,
|
||||
details: EntryDetails,
|
||||
// dragged_entry_destination: &mut Option<Arc<Path>>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> ListItem {
|
||||
) -> Stateful<Div> {
|
||||
let kind = details.kind;
|
||||
let settings = ProjectPanelSettings::get_global(cx);
|
||||
let show_editor = details.is_editing && !details.is_processing;
|
||||
let is_selected = self
|
||||
.selection
|
||||
.map_or(false, |selection| selection.entry_id == entry_id);
|
||||
let width = self.width.unwrap_or(px(0.));
|
||||
|
||||
let theme = cx.theme();
|
||||
let filename_text_color = details
|
||||
|
@ -1370,52 +1377,69 @@ impl ProjectPanel {
|
|||
})
|
||||
.unwrap_or(theme.status().info);
|
||||
|
||||
ListItem::new(entry_id.to_proto() as usize)
|
||||
.indent_level(details.depth)
|
||||
.indent_step_size(px(settings.indent_size))
|
||||
.selected(is_selected)
|
||||
.child(if let Some(icon) = &details.icon {
|
||||
div().child(IconElement::from_path(icon.to_string()))
|
||||
} else {
|
||||
div()
|
||||
div()
|
||||
.id(entry_id.to_proto() as usize)
|
||||
.on_drag({
|
||||
let details = details.clone();
|
||||
move |cx| {
|
||||
let details = details.clone();
|
||||
cx.build_view(|_| DraggedProjectEntryView {
|
||||
details,
|
||||
width,
|
||||
entry_id,
|
||||
})
|
||||
}
|
||||
})
|
||||
.drag_over::<DraggedProjectEntryView>(|style| {
|
||||
style.bg(cx.theme().colors().ghost_element_hover)
|
||||
})
|
||||
.on_drop(cx.listener(
|
||||
move |this, dragged_view: &View<DraggedProjectEntryView>, cx| {
|
||||
this.move_entry(dragged_view.read(cx).entry_id, entry_id, kind.is_file(), cx);
|
||||
},
|
||||
))
|
||||
.child(
|
||||
if let (Some(editor), true) = (Some(&self.filename_editor), show_editor) {
|
||||
div().h_full().w_full().child(editor.clone())
|
||||
} else {
|
||||
div()
|
||||
.text_color(filename_text_color)
|
||||
.child(Label::new(details.filename.clone()))
|
||||
}
|
||||
.ml_1(),
|
||||
)
|
||||
.on_click(cx.listener(move |this, event: &gpui::ClickEvent, cx| {
|
||||
if event.down.button == MouseButton::Right {
|
||||
return;
|
||||
}
|
||||
if !show_editor {
|
||||
if kind.is_dir() {
|
||||
this.toggle_expanded(entry_id, cx);
|
||||
ListItem::new(entry_id.to_proto() as usize)
|
||||
.indent_level(details.depth)
|
||||
.indent_step_size(px(settings.indent_size))
|
||||
.selected(is_selected)
|
||||
.child(if let Some(icon) = &details.icon {
|
||||
div().child(IconElement::from_path(icon.to_string()))
|
||||
} else {
|
||||
if event.down.modifiers.command {
|
||||
this.split_entry(entry_id, cx);
|
||||
div()
|
||||
})
|
||||
.child(
|
||||
if let (Some(editor), true) = (Some(&self.filename_editor), show_editor) {
|
||||
div().h_full().w_full().child(editor.clone())
|
||||
} else {
|
||||
this.open_entry(entry_id, event.up.click_count > 1, cx);
|
||||
div()
|
||||
.text_color(filename_text_color)
|
||||
.child(Label::new(details.filename.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
.on_secondary_mouse_down(cx.listener(move |this, event: &MouseDownEvent, cx| {
|
||||
this.deploy_context_menu(event.position, entry_id, cx);
|
||||
}))
|
||||
// .on_drop::<ProjectEntryId>(|this, event, cx| {
|
||||
// this.move_entry(
|
||||
// *dragged_entry,
|
||||
// entry_id,
|
||||
// matches!(details.kind, EntryKind::File(_)),
|
||||
// cx,
|
||||
// );
|
||||
// })
|
||||
.ml_1(),
|
||||
)
|
||||
.on_click(cx.listener(move |this, event: &gpui::ClickEvent, cx| {
|
||||
if event.down.button == MouseButton::Right {
|
||||
return;
|
||||
}
|
||||
if !show_editor {
|
||||
if kind.is_dir() {
|
||||
this.toggle_expanded(entry_id, cx);
|
||||
} else {
|
||||
if event.down.modifiers.command {
|
||||
this.split_entry(entry_id, cx);
|
||||
} else {
|
||||
this.open_entry(entry_id, event.up.click_count > 1, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
.on_secondary_mouse_down(cx.listener(
|
||||
move |this, event: &MouseDownEvent, cx| {
|
||||
this.deploy_context_menu(event.position, entry_id, cx);
|
||||
},
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
||||
fn dispatch_context(&self, cx: &ViewContext<Self>) -> KeyContext {
|
||||
|
@ -1430,7 +1454,6 @@ impl ProjectPanel {
|
|||
};
|
||||
|
||||
dispatch_context.add(identifier);
|
||||
|
||||
dispatch_context
|
||||
}
|
||||
}
|
||||
|
@ -1503,6 +1526,30 @@ impl Render for ProjectPanel {
|
|||
}
|
||||
}
|
||||
|
||||
impl Render for DraggedProjectEntryView {
|
||||
type Element = Div;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
let settings = ProjectPanelSettings::get_global(cx);
|
||||
let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone();
|
||||
h_stack()
|
||||
.font(ui_font)
|
||||
.bg(cx.theme().colors().background)
|
||||
.w(self.width)
|
||||
.child(
|
||||
ListItem::new(self.entry_id.to_proto() as usize)
|
||||
.indent_level(self.details.depth)
|
||||
.indent_step_size(px(settings.indent_size))
|
||||
.child(if let Some(icon) = &self.details.icon {
|
||||
div().child(IconElement::from_path(icon.to_string()))
|
||||
} else {
|
||||
div()
|
||||
})
|
||||
.child(Label::new(self.details.filename.clone())),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<Event> for ProjectPanel {}
|
||||
|
||||
impl EventEmitter<PanelEvent> for ProjectPanel {}
|
||||
|
@ -1534,12 +1581,14 @@ impl Panel for ProjectPanel {
|
|||
}
|
||||
|
||||
fn size(&self, cx: &WindowContext) -> f32 {
|
||||
self.width
|
||||
.unwrap_or_else(|| ProjectPanelSettings::get_global(cx).default_width)
|
||||
self.width.map_or_else(
|
||||
|| ProjectPanelSettings::get_global(cx).default_width,
|
||||
|width| width.0,
|
||||
)
|
||||
}
|
||||
|
||||
fn set_size(&mut self, size: Option<f32>, cx: &mut ViewContext<Self>) {
|
||||
self.width = size;
|
||||
self.width = size.map(px);
|
||||
self.serialize(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use crate::{prelude::*, Color, Icon, IconButton, IconSize};
|
||||
use gpui::ClickEvent;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::{Color, Icon, IconButton, IconSize};
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct Disclosure {
|
||||
is_open: bool,
|
||||
on_toggle: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||
on_toggle: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||
}
|
||||
|
||||
impl Disclosure {
|
||||
|
@ -21,7 +17,7 @@ impl Disclosure {
|
|||
|
||||
pub fn on_toggle(
|
||||
mut self,
|
||||
handler: impl Into<Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>>,
|
||||
handler: impl Into<Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>>,
|
||||
) -> Self {
|
||||
self.on_toggle = handler.into();
|
||||
self
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use crate::{h_stack, prelude::*, Disclosure, Icon, IconElement, IconSize, Label};
|
||||
use gpui::{AnyElement, ClickEvent, Div};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::{h_stack, Disclosure, Icon, IconElement, IconSize, Label};
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct ListHeader {
|
||||
label: SharedString,
|
||||
left_icon: Option<Icon>,
|
||||
meta: SmallVec<[AnyElement; 2]>,
|
||||
toggle: Option<bool>,
|
||||
on_toggle: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||
on_toggle: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||
inset: bool,
|
||||
selected: bool,
|
||||
}
|
||||
|
@ -39,7 +35,7 @@ impl ListHeader {
|
|||
mut self,
|
||||
on_toggle: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
|
||||
) -> Self {
|
||||
self.on_toggle = Some(Rc::new(on_toggle));
|
||||
self.on_toggle = Some(Box::new(on_toggle));
|
||||
self
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use crate::{prelude::*, Avatar, Disclosure, Icon, IconElement, IconSize};
|
||||
use gpui::{
|
||||
px, AnyElement, AnyView, ClickEvent, Div, ImageSource, MouseButton, MouseDownEvent, Pixels,
|
||||
Stateful,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::{Avatar, Disclosure, Icon, IconElement, IconSize};
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct ListItem {
|
||||
id: ElementId,
|
||||
|
@ -20,10 +16,10 @@ pub struct ListItem {
|
|||
left_slot: Option<AnyElement>,
|
||||
toggle: Option<bool>,
|
||||
inset: bool,
|
||||
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||
on_toggle: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||
on_click: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||
on_toggle: Option<Box<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>>,
|
||||
on_secondary_mouse_down: Option<Box<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
}
|
||||
|
||||
|
@ -46,7 +42,7 @@ impl ListItem {
|
|||
}
|
||||
|
||||
pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self {
|
||||
self.on_click = Some(Rc::new(handler));
|
||||
self.on_click = Some(Box::new(handler));
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -54,7 +50,15 @@ impl ListItem {
|
|||
mut self,
|
||||
handler: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
|
||||
) -> Self {
|
||||
self.on_secondary_mouse_down = Some(Rc::new(handler));
|
||||
self.on_secondary_mouse_down = Some(Box::new(handler));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_drag(
|
||||
mut self,
|
||||
handler: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
|
||||
) -> Self {
|
||||
self.on_secondary_mouse_down = Some(Box::new(handler));
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -87,7 +91,7 @@ impl ListItem {
|
|||
mut self,
|
||||
on_toggle: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
|
||||
) -> Self {
|
||||
self.on_toggle = Some(Rc::new(on_toggle));
|
||||
self.on_toggle = Some(Box::new(on_toggle));
|
||||
self
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue