352 lines
10 KiB
Rust
352 lines
10 KiB
Rust
use std::collections::HashMap;
|
|
use std::sync::Arc;
|
|
|
|
use gpui::{
|
|
AnyElement, AnyView, App, AppContext as _, Context, Entity, EntityId, EventEmitter,
|
|
FocusHandle, Focusable, Global, IntoElement, Render, SharedString, WeakEntity, Window,
|
|
};
|
|
use gpui_component::button::Button;
|
|
use gpui_component::menu::PopupMenu;
|
|
|
|
use super::{DockArea, PanelInfo, PanelState, TabPanel};
|
|
use crate::invalid_panel::InvalidPanel;
|
|
use crate::t;
|
|
|
|
pub enum PanelEvent {
|
|
ZoomIn,
|
|
ZoomOut,
|
|
LayoutChanged,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Default)]
|
|
pub enum PanelControl {
|
|
Both,
|
|
#[default]
|
|
Menu,
|
|
Toolbar,
|
|
}
|
|
|
|
impl PanelControl {
|
|
#[inline]
|
|
pub fn toolbar_visible(&self) -> bool {
|
|
matches!(self, PanelControl::Both | PanelControl::Toolbar)
|
|
}
|
|
|
|
#[inline]
|
|
pub fn menu_visible(&self) -> bool {
|
|
matches!(self, PanelControl::Both | PanelControl::Menu)
|
|
}
|
|
}
|
|
|
|
/// The Panel trait used to define the panel.
|
|
#[allow(unused_variables)]
|
|
pub trait Panel: EventEmitter<PanelEvent> + Render + Focusable {
|
|
/// The name of the panel used to serialize, deserialize and identify the panel.
|
|
///
|
|
/// This is used to identify the panel when deserializing the panel.
|
|
/// Once you have defined a panel name, this must not be changed.
|
|
fn panel_name(&self) -> &'static str;
|
|
|
|
/// The name of the tab of the panel, default is `None`.
|
|
///
|
|
/// Used to display in the already collapsed tab panel.
|
|
fn tab_name(&self, cx: &App) -> Option<SharedString> {
|
|
None
|
|
}
|
|
|
|
/// The title of the panel
|
|
fn title(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
|
t("Dock.Unnamed")
|
|
}
|
|
|
|
/// The suffix of the panel title, default is `None`.
|
|
///
|
|
/// This is used to add a suffix element to the panel title.
|
|
fn title_suffix(
|
|
&mut self,
|
|
window: &mut Window,
|
|
cx: &mut Context<Self>,
|
|
) -> Option<impl IntoElement> {
|
|
None::<gpui::Div>
|
|
}
|
|
|
|
/// Whether the panel can be closed, default is `true`.
|
|
///
|
|
/// This method called in Panel render, we should make sure it is fast.
|
|
fn closable(&self, cx: &App) -> bool {
|
|
true
|
|
}
|
|
|
|
/// Return `PanelControl` if the panel is zoomable, default is `PanelControl::Menu`.
|
|
///
|
|
/// This method called in Panel render, we should make sure it is fast.
|
|
fn zoomable(&self, cx: &App) -> Option<PanelControl> {
|
|
Some(PanelControl::Menu)
|
|
}
|
|
|
|
/// Return false to hide panel, true to show panel, default is `true`.
|
|
///
|
|
/// This method called in Panel render, we should make sure it is fast.
|
|
fn visible(&self, cx: &App) -> bool {
|
|
true
|
|
}
|
|
|
|
/// Set active state of the panel.
|
|
///
|
|
/// Called with the frame-end net state when this panel becomes (or stops
|
|
/// being) the displayed tab of its tab group: exactly one notification
|
|
/// per edge, delivered on the next tick after the change — never
|
|
/// same-value repeats nor false→true flips within one frame.
|
|
///
|
|
/// A panel removed from its group is NOT told `false`; [`Panel::on_removed`]
|
|
/// is the deactivation signal. A hidden panel occupying `active_ix` still
|
|
/// receives `true` even though rendering falls back to the first visible
|
|
/// panel, and panels inside a bare `DockItem::Panel` (no tab group) are
|
|
/// outside this contract.
|
|
fn set_active(&mut self, active: bool, window: &mut Window, cx: &mut Context<Self>) {}
|
|
|
|
/// Set zoomed state of the panel.
|
|
///
|
|
/// This method will be called when the panel is zoomed or unzoomed.
|
|
///
|
|
/// Only current Panel will touch this method.
|
|
fn set_zoomed(&mut self, zoomed: bool, window: &mut Window, cx: &mut Context<Self>) {}
|
|
|
|
/// When this Panel is added to a TabPanel, this will be called.
|
|
fn on_added_to(
|
|
&mut self,
|
|
tab_panel: WeakEntity<TabPanel>,
|
|
window: &mut Window,
|
|
cx: &mut Context<Self>,
|
|
) {
|
|
}
|
|
|
|
/// When this Panel is removed from a TabPanel, this will be called.
|
|
fn on_removed(&mut self, window: &mut Window, cx: &mut Context<Self>) {}
|
|
|
|
/// The addition dropdown menu of the panel, default is `None`.
|
|
fn dropdown_menu(
|
|
&mut self,
|
|
this: PopupMenu,
|
|
window: &mut Window,
|
|
cx: &mut Context<Self>,
|
|
) -> PopupMenu {
|
|
this
|
|
}
|
|
|
|
/// The addition toolbar buttons of the panel used to show in the right of the title bar, default is `None`.
|
|
fn toolbar_buttons(
|
|
&mut self,
|
|
window: &mut Window,
|
|
cx: &mut Context<Self>,
|
|
) -> Option<Vec<Button>> {
|
|
None
|
|
}
|
|
|
|
/// Dump the panel, used to serialize the panel.
|
|
fn dump(&self, cx: &App) -> PanelState {
|
|
PanelState::new(self)
|
|
}
|
|
}
|
|
|
|
/// The PanelView trait used to define the panel view.
|
|
#[allow(unused_variables)]
|
|
pub trait PanelView: 'static + Send + Sync {
|
|
fn panel_name(&self, cx: &App) -> &'static str;
|
|
fn panel_id(&self, cx: &App) -> EntityId;
|
|
fn tab_name(&self, cx: &App) -> Option<SharedString>;
|
|
fn title(&self, window: &mut Window, cx: &mut App) -> AnyElement;
|
|
fn title_suffix(&self, window: &mut Window, cx: &mut App) -> Option<AnyElement>;
|
|
fn closable(&self, cx: &App) -> bool;
|
|
fn zoomable(&self, cx: &App) -> Option<PanelControl>;
|
|
fn visible(&self, cx: &App) -> bool;
|
|
fn set_active(&self, active: bool, window: &mut Window, cx: &mut App);
|
|
fn set_zoomed(&self, zoomed: bool, window: &mut Window, cx: &mut App);
|
|
fn on_added_to(&self, tab_panel: WeakEntity<TabPanel>, window: &mut Window, cx: &mut App);
|
|
fn on_removed(&self, window: &mut Window, cx: &mut App);
|
|
fn dropdown_menu(&self, menu: PopupMenu, window: &mut Window, cx: &mut App) -> PopupMenu;
|
|
fn toolbar_buttons(&self, window: &mut Window, cx: &mut App) -> Option<Vec<Button>>;
|
|
fn view(&self) -> AnyView;
|
|
fn focus_handle(&self, cx: &App) -> FocusHandle;
|
|
fn dump(&self, cx: &App) -> PanelState;
|
|
}
|
|
|
|
impl<T: Panel> PanelView for Entity<T> {
|
|
fn panel_name(&self, cx: &App) -> &'static str {
|
|
self.read(cx).panel_name()
|
|
}
|
|
|
|
fn panel_id(&self, _: &App) -> EntityId {
|
|
self.entity_id()
|
|
}
|
|
|
|
fn tab_name(&self, cx: &App) -> Option<SharedString> {
|
|
self.read(cx).tab_name(cx)
|
|
}
|
|
|
|
fn title(&self, window: &mut Window, cx: &mut App) -> AnyElement {
|
|
self.update(cx, |this, cx| this.title(window, cx).into_any_element())
|
|
}
|
|
|
|
fn title_suffix(&self, window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
|
self.update(cx, |this, cx| {
|
|
this.title_suffix(window, cx)
|
|
.map(|el| el.into_any_element())
|
|
})
|
|
}
|
|
|
|
fn closable(&self, cx: &App) -> bool {
|
|
self.read(cx).closable(cx)
|
|
}
|
|
|
|
fn zoomable(&self, cx: &App) -> Option<PanelControl> {
|
|
self.read(cx).zoomable(cx)
|
|
}
|
|
|
|
fn visible(&self, cx: &App) -> bool {
|
|
self.read(cx).visible(cx)
|
|
}
|
|
|
|
fn set_active(&self, active: bool, window: &mut Window, cx: &mut App) {
|
|
self.update(cx, |this, cx| {
|
|
this.set_active(active, window, cx);
|
|
})
|
|
}
|
|
|
|
fn set_zoomed(&self, zoomed: bool, window: &mut Window, cx: &mut App) {
|
|
self.update(cx, |this, cx| {
|
|
this.set_zoomed(zoomed, window, cx);
|
|
})
|
|
}
|
|
|
|
fn on_added_to(&self, tab_panel: WeakEntity<TabPanel>, window: &mut Window, cx: &mut App) {
|
|
self.update(cx, |this, cx| this.on_added_to(tab_panel, window, cx));
|
|
}
|
|
|
|
fn on_removed(&self, window: &mut Window, cx: &mut App) {
|
|
self.update(cx, |this, cx| this.on_removed(window, cx));
|
|
}
|
|
|
|
fn dropdown_menu(&self, menu: PopupMenu, window: &mut Window, cx: &mut App) -> PopupMenu {
|
|
self.update(cx, |this, cx| this.dropdown_menu(menu, window, cx))
|
|
}
|
|
|
|
fn toolbar_buttons(&self, window: &mut Window, cx: &mut App) -> Option<Vec<Button>> {
|
|
self.update(cx, |this, cx| this.toolbar_buttons(window, cx))
|
|
}
|
|
|
|
fn view(&self) -> AnyView {
|
|
self.clone().into()
|
|
}
|
|
|
|
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
|
self.read(cx).focus_handle(cx)
|
|
}
|
|
|
|
fn dump(&self, cx: &App) -> PanelState {
|
|
self.read(cx).dump(cx)
|
|
}
|
|
}
|
|
|
|
impl From<&dyn PanelView> for AnyView {
|
|
fn from(handle: &dyn PanelView) -> Self {
|
|
handle.view()
|
|
}
|
|
}
|
|
|
|
impl<T: Panel> From<&dyn PanelView> for Entity<T> {
|
|
fn from(value: &dyn PanelView) -> Self {
|
|
value.view().downcast::<T>().unwrap()
|
|
}
|
|
}
|
|
|
|
impl PartialEq for dyn PanelView {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
self.view() == other.view()
|
|
}
|
|
}
|
|
|
|
/// The deserializer used by [`PanelRegistry`] to rebuild a panel from its
|
|
/// persisted [`PanelState`].
|
|
type PanelBuilder = dyn Fn(
|
|
WeakEntity<DockArea>,
|
|
&PanelState,
|
|
&PanelInfo,
|
|
&mut Window,
|
|
&mut App,
|
|
) -> Box<dyn PanelView>;
|
|
|
|
pub struct PanelRegistry {
|
|
pub(super) items: HashMap<String, Arc<PanelBuilder>>,
|
|
}
|
|
impl PanelRegistry {
|
|
/// Initialize the panel registry.
|
|
pub(crate) fn init(cx: &mut App) {
|
|
if cx.try_global::<PanelRegistry>().is_none() {
|
|
cx.set_global(PanelRegistry::new());
|
|
}
|
|
}
|
|
|
|
pub fn new() -> Self {
|
|
Self {
|
|
items: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
pub fn global(cx: &App) -> &Self {
|
|
cx.global::<PanelRegistry>()
|
|
}
|
|
|
|
pub fn global_mut(cx: &mut App) -> &mut Self {
|
|
cx.global_mut::<PanelRegistry>()
|
|
}
|
|
|
|
/// Build a panel by name.
|
|
///
|
|
/// If not registered, return InvalidPanel.
|
|
pub fn build_panel(
|
|
panel_name: &str,
|
|
dock_area: WeakEntity<DockArea>,
|
|
panel_state: &PanelState,
|
|
panel_info: &PanelInfo,
|
|
window: &mut Window,
|
|
cx: &mut App,
|
|
) -> Box<dyn PanelView> {
|
|
if let Some(view) = Self::global(cx)
|
|
.items
|
|
.get(panel_name)
|
|
.cloned()
|
|
.map(|f| f(dock_area, panel_state, panel_info, window, cx))
|
|
{
|
|
view
|
|
} else {
|
|
// Show an invalid panel if the panel is not registered.
|
|
Box::new(cx.new(|cx| InvalidPanel::new(panel_name, panel_state.clone(), window, cx)))
|
|
}
|
|
}
|
|
}
|
|
impl Default for PanelRegistry {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
impl Global for PanelRegistry {}
|
|
|
|
/// Register the Panel init by panel_name to global registry.
|
|
pub fn register_panel<F>(cx: &mut App, panel_name: &str, deserialize: F)
|
|
where
|
|
F: Fn(
|
|
WeakEntity<DockArea>,
|
|
&PanelState,
|
|
&PanelInfo,
|
|
&mut Window,
|
|
&mut App,
|
|
) -> Box<dyn PanelView>
|
|
+ 'static,
|
|
{
|
|
PanelRegistry::init(cx);
|
|
PanelRegistry::global_mut(cx)
|
|
.items
|
|
.insert(panel_name.to_string(), Arc::new(deserialize));
|
|
}
|