Make panels independently resizable

This commit is contained in:
Nathan Sobo 2023-05-11 23:25:05 -06:00
parent 5549669316
commit 214354b4da
8 changed files with 135 additions and 154 deletions

View file

@ -108,7 +108,8 @@
"auto_update": true, "auto_update": true,
// Git gutter behavior configuration. // Git gutter behavior configuration.
"project_panel": { "project_panel": {
"dock": "left" "dock": "left",
"default_width": 240
}, },
"git": { "git": {
// Control whether the git gutter is shown. May take 2 values: // Control whether the git gutter is shown. May take 2 values:

View file

@ -187,24 +187,20 @@ pub trait Element<V: View>: 'static {
Tooltip::new::<Tag, V>(id, text, action, style, self.into_any(), cx) Tooltip::new::<Tag, V>(id, text, action, style, self.into_any(), cx)
} }
fn with_resize_handle<Tag: 'static>( fn resizable(
self, self,
element_id: usize, side: HandleSide,
side: Side, size: f32,
handle_size: f32, on_resize: impl 'static + FnMut(&mut V, f32, &mut ViewContext<V>),
initial_size: f32,
cx: &mut ViewContext<V>,
) -> Resizable<V> ) -> Resizable<V>
where where
Self: 'static + Sized, Self: 'static + Sized,
{ {
Resizable::new::<Tag, V>( Resizable::new(
self.into_any(), self.into_any(),
element_id,
side, side,
handle_size, size,
initial_size, on_resize
cx,
) )
} }
} }

View file

@ -1,4 +1,4 @@
use std::{cell::Cell, rc::Rc}; use std::{cell::RefCell, rc::Rc};
use pathfinder_geometry::vector::{vec2f, Vector2F}; use pathfinder_geometry::vector::{vec2f, Vector2F};
use serde_json::json; use serde_json::json;
@ -7,25 +7,23 @@ use crate::{
geometry::rect::RectF, geometry::rect::RectF,
platform::{CursorStyle, MouseButton}, platform::{CursorStyle, MouseButton},
scene::MouseDrag, scene::MouseDrag,
AnyElement, Axis, Element, ElementStateHandle, LayoutContext, MouseRegion, SceneBuilder, View, AnyElement, Axis, Element, LayoutContext, MouseRegion, SceneBuilder, View,
ViewContext, ViewContext, SizeConstraint,
}; };
use super::{ConstrainedBox, Hook};
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub enum Side { pub enum HandleSide {
Top, Top,
Bottom, Bottom,
Left, Left,
Right, Right,
} }
impl Side { impl HandleSide {
fn axis(&self) -> Axis { fn axis(&self) -> Axis {
match self { match self {
Side::Left | Side::Right => Axis::Horizontal, HandleSide::Left | HandleSide::Right => Axis::Horizontal,
Side::Top | Side::Bottom => Axis::Vertical, HandleSide::Top | HandleSide::Bottom => Axis::Vertical,
} }
} }
@ -33,8 +31,8 @@ impl Side {
/// then top-to-bottom /// then top-to-bottom
fn before_content(self) -> bool { fn before_content(self) -> bool {
match self { match self {
Side::Left | Side::Top => true, HandleSide::Left | HandleSide::Top => true,
Side::Right | Side::Bottom => false, HandleSide::Right | HandleSide::Bottom => false,
} }
} }
@ -55,14 +53,14 @@ impl Side {
fn of_rect(&self, bounds: RectF, handle_size: f32) -> RectF { fn of_rect(&self, bounds: RectF, handle_size: f32) -> RectF {
match self { match self {
Side::Top => RectF::new(bounds.origin(), vec2f(bounds.width(), handle_size)), HandleSide::Top => RectF::new(bounds.origin(), vec2f(bounds.width(), handle_size)),
Side::Left => RectF::new(bounds.origin(), vec2f(handle_size, bounds.height())), HandleSide::Left => RectF::new(bounds.origin(), vec2f(handle_size, bounds.height())),
Side::Bottom => { HandleSide::Bottom => {
let mut origin = bounds.lower_left(); let mut origin = bounds.lower_left();
origin.set_y(origin.y() - handle_size); origin.set_y(origin.y() - handle_size);
RectF::new(origin, vec2f(bounds.width(), handle_size)) RectF::new(origin, vec2f(bounds.width(), handle_size))
} }
Side::Right => { HandleSide::Right => {
let mut origin = bounds.upper_right(); let mut origin = bounds.upper_right();
origin.set_x(origin.x() - handle_size); origin.set_x(origin.x() - handle_size);
RectF::new(origin, vec2f(handle_size, bounds.height())) RectF::new(origin, vec2f(handle_size, bounds.height()))
@ -71,69 +69,44 @@ impl Side {
} }
} }
struct ResizeHandleState { pub struct Resizable<V: View> {
actual_dimension: Cell<f32>, child: AnyElement<V>,
custom_dimension: Cell<f32>, handle_side: HandleSide,
handle_size: f32,
on_resize: Rc<RefCell<dyn FnMut(&mut V, f32, &mut ViewContext<V>)>>
} }
pub struct Resizable<V: View> { const DEFAULT_HANDLE_SIZE: f32 = 4.0;
side: Side,
handle_size: f32,
child: AnyElement<V>,
state: Rc<ResizeHandleState>,
_state_handle: ElementStateHandle<Rc<ResizeHandleState>>,
}
impl<V: View> Resizable<V> { impl<V: View> Resizable<V> {
pub fn new<Tag: 'static, T: View>( pub fn new(
child: AnyElement<V>, child: AnyElement<V>,
element_id: usize, handle_side: HandleSide,
side: Side, size: f32,
handle_size: f32, on_resize: impl 'static + FnMut(&mut V, f32, &mut ViewContext<V>)
initial_size: f32,
cx: &mut ViewContext<V>,
) -> Self { ) -> Self {
let state_handle = cx.element_state::<Tag, Rc<ResizeHandleState>>( let child = match handle_side.axis() {
element_id, Axis::Horizontal => child.constrained().with_max_width(size),
Rc::new(ResizeHandleState { Axis::Vertical => child.constrained().with_max_height(size),
actual_dimension: Cell::new(initial_size),
custom_dimension: Cell::new(initial_size),
}),
);
let state = state_handle.read(cx).clone();
let child = Hook::new({
let constrained = ConstrainedBox::new(child);
match side.axis() {
Axis::Horizontal => constrained.with_max_width(state.custom_dimension.get()),
Axis::Vertical => constrained.with_max_height(state.custom_dimension.get()),
} }
})
.on_after_layout({
let state = state.clone();
move |size, _| {
state.actual_dimension.set(side.relevant_component(size));
}
})
.into_any(); .into_any();
Self { Self {
side,
child, child,
handle_size, handle_side,
state, handle_size: DEFAULT_HANDLE_SIZE,
_state_handle: state_handle, on_resize: Rc::new(RefCell::new(on_resize)),
} }
} }
pub fn current_size(&self) -> f32 { pub fn with_handle_size(mut self, handle_size: f32) -> Self {
self.state.actual_dimension.get() self.handle_size = handle_size;
self
} }
} }
impl<V: View> Element<V> for Resizable<V> { impl<V: View> Element<V> for Resizable<V> {
type LayoutState = (); type LayoutState = SizeConstraint;
type PaintState = (); type PaintState = ();
fn layout( fn layout(
@ -142,7 +115,7 @@ impl<V: View> Element<V> for Resizable<V> {
view: &mut V, view: &mut V,
cx: &mut LayoutContext<V>, cx: &mut LayoutContext<V>,
) -> (Vector2F, Self::LayoutState) { ) -> (Vector2F, Self::LayoutState) {
(self.child.layout(constraint, view, cx), ()) (self.child.layout(constraint, view, cx), constraint)
} }
fn paint( fn paint(
@ -150,34 +123,37 @@ impl<V: View> Element<V> for Resizable<V> {
scene: &mut SceneBuilder, scene: &mut SceneBuilder,
bounds: pathfinder_geometry::rect::RectF, bounds: pathfinder_geometry::rect::RectF,
visible_bounds: pathfinder_geometry::rect::RectF, visible_bounds: pathfinder_geometry::rect::RectF,
_child_size: &mut Self::LayoutState, constraint: &mut SizeConstraint,
view: &mut V, view: &mut V,
cx: &mut ViewContext<V>, cx: &mut ViewContext<V>,
) -> Self::PaintState { ) -> Self::PaintState {
scene.push_stacking_context(None, None); scene.push_stacking_context(None, None);
let handle_region = self.side.of_rect(bounds, self.handle_size); let handle_region = self.handle_side.of_rect(bounds, self.handle_size);
enum ResizeHandle {} enum ResizeHandle {}
scene.push_mouse_region( scene.push_mouse_region(
MouseRegion::new::<ResizeHandle>(cx.view_id(), self.side as usize, handle_region) MouseRegion::new::<ResizeHandle>(cx.view_id(), self.handle_side as usize, handle_region)
.on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere .on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere
.on_drag(MouseButton::Left, { .on_drag(MouseButton::Left, {
let state = self.state.clone(); let bounds = bounds.clone();
let side = self.side; let side = self.handle_side;
move |e, _: &mut V, cx| { let prev_size = side.relevant_component(bounds.size());
let prev_width = state.actual_dimension.get(); let min_size = side.relevant_component(constraint.min);
state let max_size = side.relevant_component(constraint.max);
.custom_dimension let on_resize = self.on_resize.clone();
.set(0f32.max(prev_width + side.compute_delta(e)).round()); move |event, view: &mut V, cx| {
cx.notify(); let new_size = min_size.max(prev_size + side.compute_delta(event)).min(max_size).round();
if new_size != prev_size {
on_resize.borrow_mut()(view, new_size, cx);
}
} }
}), }),
); );
scene.push_cursor_region(crate::CursorRegion { scene.push_cursor_region(crate::CursorRegion {
bounds: handle_region, bounds: handle_region,
style: match self.side.axis() { style: match self.handle_side.axis() {
Axis::Horizontal => CursorStyle::ResizeLeftRight, Axis::Horizontal => CursorStyle::ResizeLeftRight,
Axis::Vertical => CursorStyle::ResizeUpDown, Axis::Vertical => CursorStyle::ResizeUpDown,
}, },

View file

@ -1347,12 +1347,9 @@ impl Entity for ProjectPanel {
impl workspace::dock::Panel for ProjectPanel { impl workspace::dock::Panel for ProjectPanel {
fn position(&self, cx: &gpui::WindowContext) -> DockPosition { fn position(&self, cx: &gpui::WindowContext) -> DockPosition {
let settings = cx.global::<Settings>(); let settings = cx.global::<Settings>();
let dock = settings match settings
.project_panel_overrides .project_panel
.dock .dock {
.or(settings.project_panel_defaults.dock)
.unwrap();
match dock {
settings::ProjectPanelDockPosition::Left => DockPosition::Left, settings::ProjectPanelDockPosition::Left => DockPosition::Left,
settings::ProjectPanelDockPosition::Right => DockPosition::Right, settings::ProjectPanelDockPosition::Right => DockPosition::Right,
} }
@ -1374,6 +1371,10 @@ impl workspace::dock::Panel for ProjectPanel {
}) })
} }
fn default_size(&self, cx: &gpui::WindowContext) -> f32 {
cx.global::<Settings>().project_panel.default_width
}
fn icon_path(&self) -> &'static str { fn icon_path(&self) -> &'static str {
"icons/folder_tree_16.svg" "icons/folder_tree_16.svg"
} }

View file

@ -44,8 +44,7 @@ pub struct Settings {
pub show_call_status_icon: bool, pub show_call_status_icon: bool,
pub vim_mode: bool, pub vim_mode: bool,
pub autosave: Autosave, pub autosave: Autosave,
pub project_panel_defaults: ProjectPanelSettings, pub project_panel: ProjectPanelSettings,
pub project_panel_overrides: ProjectPanelSettings,
pub editor_defaults: EditorSettings, pub editor_defaults: EditorSettings,
pub editor_overrides: EditorSettings, pub editor_overrides: EditorSettings,
pub git: GitSettings, pub git: GitSettings,
@ -158,9 +157,15 @@ pub enum GitGutter {
pub struct GitGutterConfig {} pub struct GitGutterConfig {}
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct ProjectPanelSettings { pub struct ProjectPanelSettings {
pub dock: ProjectPanelDockPosition,
pub default_width: f32,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
pub struct ProjectPanelSettingsContent {
pub dock: Option<ProjectPanelDockPosition>, pub dock: Option<ProjectPanelDockPosition>,
pub default_width: Option<f32>,
} }
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)] #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
@ -253,6 +258,8 @@ impl Default for HourFormat {
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
pub struct TerminalSettings { pub struct TerminalSettings {
pub default_width: Option<f32>,
pub default_height: Option<f32>,
pub shell: Option<Shell>, pub shell: Option<Shell>,
pub working_directory: Option<WorkingDirectory>, pub working_directory: Option<WorkingDirectory>,
pub font_size: Option<f32>, pub font_size: Option<f32>,
@ -387,7 +394,7 @@ pub struct SettingsFileContent {
#[serde(flatten)] #[serde(flatten)]
pub editor: EditorSettings, pub editor: EditorSettings,
#[serde(default)] #[serde(default)]
pub project_panel: ProjectPanelSettings, pub project_panel: ProjectPanelSettingsContent,
#[serde(default)] #[serde(default)]
pub journal: JournalSettings, pub journal: JournalSettings,
#[serde(default)] #[serde(default)]
@ -423,7 +430,6 @@ pub struct Features {
} }
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct FeaturesContent { pub struct FeaturesContent {
pub copilot: Option<bool>, pub copilot: Option<bool>,
} }
@ -482,8 +488,10 @@ impl Settings {
show_call_status_icon: defaults.show_call_status_icon.unwrap(), show_call_status_icon: defaults.show_call_status_icon.unwrap(),
vim_mode: defaults.vim_mode.unwrap(), vim_mode: defaults.vim_mode.unwrap(),
autosave: defaults.autosave.unwrap(), autosave: defaults.autosave.unwrap(),
project_panel_defaults: defaults.project_panel, project_panel: ProjectPanelSettings {
project_panel_overrides: Default::default(), dock: defaults.project_panel.dock.unwrap(),
default_width: defaults.project_panel.default_width.unwrap(),
},
editor_defaults: EditorSettings { editor_defaults: EditorSettings {
tab_size: required(defaults.editor.tab_size), tab_size: required(defaults.editor.tab_size),
hard_tabs: required(defaults.editor.hard_tabs), hard_tabs: required(defaults.editor.hard_tabs),
@ -590,7 +598,8 @@ impl Settings {
} }
} }
self.editor_overrides = data.editor; self.editor_overrides = data.editor;
self.project_panel_overrides = data.project_panel; merge(&mut self.project_panel.dock, data.project_panel.dock);
merge(&mut self.project_panel.default_width, data.project_panel.default_width);
self.git_overrides = data.git.unwrap_or_default(); self.git_overrides = data.git.unwrap_or_default();
self.journal_overrides = data.journal; self.journal_overrides = data.journal;
self.terminal_defaults.font_size = data.terminal.font_size; self.terminal_defaults.font_size = data.terminal.font_size;
@ -778,8 +787,10 @@ impl Settings {
show_call_status_icon: true, show_call_status_icon: true,
vim_mode: false, vim_mode: false,
autosave: Autosave::Off, autosave: Autosave::Off,
project_panel_defaults: Default::default(), project_panel: ProjectPanelSettings {
project_panel_overrides: Default::default(), dock: ProjectPanelDockPosition::Left,
default_width: 240.,
},
editor_defaults: EditorSettings { editor_defaults: EditorSettings {
tab_size: Some(4.try_into().unwrap()), tab_size: Some(4.try_into().unwrap()),
hard_tabs: Some(false), hard_tabs: Some(false),

View file

@ -179,6 +179,14 @@ impl Panel for TerminalPanel {
}); });
} }
fn default_size(&self, cx: &gpui::WindowContext) -> f32 {
let settings = &cx.global::<Settings>().terminal_overrides;
match self.position(cx) {
DockPosition::Left | DockPosition::Right => settings.default_width.unwrap_or(640.),
DockPosition::Bottom => settings.default_height.unwrap_or(320.),
}
}
fn icon_path(&self) -> &'static str { fn icon_path(&self) -> &'static str {
"icons/terminal_12.svg" "icons/terminal_12.svg"
} }

View file

@ -12,6 +12,7 @@ pub trait Panel: View {
fn position(&self, cx: &WindowContext) -> DockPosition; fn position(&self, cx: &WindowContext) -> DockPosition;
fn position_is_valid(&self, position: DockPosition) -> bool; fn position_is_valid(&self, position: DockPosition) -> bool;
fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>); fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>);
fn default_size(&self, cx: &WindowContext) -> f32;
fn icon_path(&self) -> &'static str; fn icon_path(&self) -> &'static str;
fn icon_tooltip(&self) -> String; fn icon_tooltip(&self) -> String;
fn icon_label(&self, _: &AppContext) -> Option<String> { fn icon_label(&self, _: &AppContext) -> Option<String> {
@ -27,6 +28,7 @@ pub trait PanelHandle {
fn position(&self, cx: &WindowContext) -> DockPosition; fn position(&self, cx: &WindowContext) -> DockPosition;
fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool; fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool;
fn set_position(&self, position: DockPosition, cx: &mut WindowContext); fn set_position(&self, position: DockPosition, cx: &mut WindowContext);
fn default_size(&self, cx: &WindowContext) -> f32;
fn icon_path(&self, cx: &WindowContext) -> &'static str; fn icon_path(&self, cx: &WindowContext) -> &'static str;
fn icon_tooltip(&self, cx: &WindowContext) -> String; fn icon_tooltip(&self, cx: &WindowContext) -> String;
fn icon_label(&self, cx: &WindowContext) -> Option<String>; fn icon_label(&self, cx: &WindowContext) -> Option<String>;
@ -54,6 +56,10 @@ where
self.update(cx, |this, cx| this.set_position(position, cx)) self.update(cx, |this, cx| this.set_position(position, cx))
} }
fn default_size(&self, cx: &WindowContext) -> f32 {
self.read(cx).default_size(cx)
}
fn icon_path(&self, cx: &WindowContext) -> &'static str { fn icon_path(&self, cx: &WindowContext) -> &'static str {
self.read(cx).icon_path() self.read(cx).icon_path()
} }
@ -104,17 +110,18 @@ impl DockPosition {
} }
} }
fn to_resizable_side(self) -> Side { fn to_resize_handle_side(self) -> HandleSide {
match self { match self {
Self::Left => Side::Right, Self::Left => HandleSide::Right,
Self::Bottom => Side::Top, Self::Bottom => HandleSide::Top,
Self::Right => Side::Left, Self::Right => HandleSide::Left,
} }
} }
} }
struct PanelEntry { struct PanelEntry {
panel: Rc<dyn PanelHandle>, panel: Rc<dyn PanelHandle>,
size: f32,
context_menu: ViewHandle<ContextMenu>, context_menu: ViewHandle<ContextMenu>,
_subscriptions: [Subscription; 2], _subscriptions: [Subscription; 2],
} }
@ -181,8 +188,10 @@ impl Dock {
]; ];
let dock_view_id = cx.view_id(); let dock_view_id = cx.view_id();
let size = panel.default_size(cx);
self.panel_entries.push(PanelEntry { self.panel_entries.push(PanelEntry {
panel: Rc::new(panel), panel: Rc::new(panel),
size,
context_menu: cx.add_view(|cx| { context_menu: cx.add_view(|cx| {
let mut menu = ContextMenu::new(dock_view_id, cx); let mut menu = ContextMenu::new(dock_view_id, cx);
menu.set_position_mode(OverlayPositionMode::Local); menu.set_position_mode(OverlayPositionMode::Local);
@ -237,6 +246,23 @@ impl Dock {
None None
} }
} }
pub fn active_panel_size(&self) -> Option<f32> {
if self.is_open {
self.panel_entries
.get(self.active_panel_index)
.map(|entry| entry.size)
} else {
None
}
}
pub fn resize_active_panel(&mut self, size: f32, cx: &mut ViewContext<Self>) {
if let Some(entry) = self.panel_entries.get_mut(self.active_panel_index) {
entry.size = size;
cx.notify();
}
}
} }
impl Entity for Dock { impl Entity for Dock {
@ -250,18 +276,14 @@ impl View for Dock {
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> { fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
if let Some(active_panel) = self.active_panel() { if let Some(active_panel) = self.active_panel() {
enum ResizeHandleTag {} let size = self.active_panel_size().unwrap();
let style = &cx.global::<Settings>().theme.workspace.dock; let style = &cx.global::<Settings>().theme.workspace.dock;
ChildView::new(active_panel.as_any(), cx) ChildView::new(active_panel.as_any(), cx)
.contained() .contained()
.with_style(style.container) .with_style(style.container)
.with_resize_handle::<ResizeHandleTag>( .resizable(self.position.to_resize_handle_side(), size, |dock: &mut Self, size, cx| {
self.position as usize, dock.resize_active_panel(size, cx);
self.position.to_resizable_side(), })
4.,
style.initial_size,
cx,
)
.into_any() .into_any()
} else { } else {
Empty::new().into_any() Empty::new().into_any()
@ -464,6 +486,10 @@ pub(crate) mod test {
cx.emit(TestPanelEvent::PositionChanged); cx.emit(TestPanelEvent::PositionChanged);
} }
fn default_size(&self, _: &WindowContext) -> f32 {
300.
}
fn icon_path(&self) -> &'static str { fn icon_path(&self) -> &'static str {
"icons/test_panel.svg" "icons/test_panel.svg"
} }

View file

@ -766,7 +766,7 @@ impl<T: FollowableItem> FollowableItemHandle for ViewHandle<T> {
#[cfg(test)] #[cfg(test)]
pub(crate) mod test { pub(crate) mod test {
use super::{Item, ItemEvent}; use super::{Item, ItemEvent};
use crate::{dock::Panel, ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
use gpui::{ use gpui::{
elements::Empty, AnyElement, AppContext, Element, Entity, ModelHandle, Task, View, elements::Empty, AnyElement, AppContext, Element, Entity, ModelHandle, Task, View,
ViewContext, ViewHandle, WeakViewHandle, ViewContext, ViewHandle, WeakViewHandle,
@ -1059,42 +1059,4 @@ pub(crate) mod test {
Task::Ready(Some(anyhow::Ok(view))) Task::Ready(Some(anyhow::Ok(view)))
} }
} }
impl Panel for TestItem {
fn position(&self, _cx: &gpui::WindowContext) -> crate::dock::DockPosition {
unimplemented!()
}
fn position_is_valid(&self, _position: crate::dock::DockPosition) -> bool {
unimplemented!()
}
fn set_position(
&mut self,
_position: crate::dock::DockPosition,
_cx: &mut ViewContext<Self>,
) {
unimplemented!()
}
fn icon_path(&self) -> &'static str {
unimplemented!()
}
fn icon_tooltip(&self) -> String {
unimplemented!()
}
fn should_change_position_on_event(_: &Self::Event) -> bool {
unimplemented!()
}
fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool {
unimplemented!()
}
fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool {
unimplemented!()
}
}
} }