T11: the window is Ely's ColumnShell: sessions, terminal and right columns under a transparent title bar, the terminal's header wearing the program's title; Cmd-B and Cmd-Alt-B toggle the side columns, kept in the settings; Linux asks for client decorations inside Ely's ResizeBorder
This commit is contained in:
5 files changed
+155
-55
No files matched your search
+11
-53
@@ -1,63 +1,21 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use anyhow::Context as _;
|
||||
use ely_gpui_component::{primitives::FocusScope, theme::ActiveTheme};
|
||||
use ely_gpui_component::theme::ActiveTheme;
|
||||
use gpui::{
|
||||
App, AppContext, Bounds, Context, Entity, FocusHandle, Focusable, IntoElement, ParentElement,
|
||||
Render, Styled, Subscription, TitlebarOptions, Window, WindowBounds, WindowHandle,
|
||||
WindowOptions, px, size,
|
||||
App, AppContext, Bounds, Focusable, TitlebarOptions, WindowBounds, WindowDecorations,
|
||||
WindowHandle, WindowOptions, px, size,
|
||||
};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use crate::term::mac;
|
||||
use crate::{
|
||||
settings::SettingsStore,
|
||||
term::{
|
||||
ICONS, Launch, MONO, TerminalEvent, TerminalView, config, input, shell::ShellIntegration,
|
||||
spawn,
|
||||
},
|
||||
term::{ICONS, Launch, MONO, TerminalView, config, input, shell::ShellIntegration, spawn},
|
||||
themes,
|
||||
workspace::{self, TITLE, Workspace},
|
||||
};
|
||||
|
||||
/// The window title while the program names none.
|
||||
const TITLE: &str = "ELYTerminal";
|
||||
|
||||
/// The main window's content.
|
||||
pub struct Workspace {
|
||||
focus: FocusHandle,
|
||||
terminal: Entity<TerminalView>,
|
||||
_events: Subscription,
|
||||
}
|
||||
|
||||
impl Workspace {
|
||||
/// The window wears the terminal's title.
|
||||
fn terminal_event(
|
||||
&mut self,
|
||||
_: &Entity<TerminalView>,
|
||||
event: &TerminalEvent,
|
||||
window: &mut Window,
|
||||
_: &mut Context<Self>,
|
||||
) {
|
||||
if let TerminalEvent::Title(title) = event {
|
||||
let title = if title.is_empty() { TITLE } else { title };
|
||||
if window.window_title() != title {
|
||||
log::debug!("app: the window title is {title:?}");
|
||||
window.set_window_title(title);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Workspace {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
FocusScope::new(&self.focus)
|
||||
.root()
|
||||
.size_full()
|
||||
.bg(cx.theme().colors.bg)
|
||||
.child(self.terminal.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// Fonts, settings, integration scripts and the chosen font, each checked before a window opens.
|
||||
pub fn start(cx: &mut App) -> anyhow::Result<()> {
|
||||
cx.text_system()
|
||||
@@ -82,6 +40,7 @@ pub fn start(cx: &mut App) -> anyhow::Result<()> {
|
||||
SettingsStore::init(cx)?;
|
||||
ShellIntegration::init(cx)?;
|
||||
input::bind_keys(cx);
|
||||
workspace::bind_keys(cx);
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
mac::repeat_held_keys();
|
||||
@@ -103,8 +62,11 @@ pub fn open(launch: Launch, cx: &mut App) -> anyhow::Result<WindowHandle<Workspa
|
||||
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||
titlebar: Some(TitlebarOptions {
|
||||
title: Some(TITLE.into()),
|
||||
..Default::default()
|
||||
appears_transparent: true,
|
||||
traffic_light_position: Some(cx.theme().traffic_light_origin()),
|
||||
}),
|
||||
// Ely's columns draw the controls; only Linux reads this, and must not draw its own.
|
||||
window_decorations: Some(WindowDecorations::Client),
|
||||
..Default::default()
|
||||
};
|
||||
let (pty, events) = spawn(&launch, (80, 24), (8, 16), config(SettingsStore::get(cx)))?;
|
||||
@@ -114,11 +76,7 @@ pub fn open(launch: Launch, cx: &mut App) -> anyhow::Result<WindowHandle<Workspa
|
||||
.detach();
|
||||
let terminal = cx.new(|cx| TerminalView::new(pty, events, window, cx));
|
||||
window.focus(&terminal.focus_handle(cx), cx);
|
||||
cx.new(|cx| Workspace {
|
||||
focus: cx.focus_handle(),
|
||||
_events: cx.subscribe_in(&terminal, window, Workspace::terminal_event),
|
||||
terminal,
|
||||
})
|
||||
cx.new(|cx| Workspace::new(terminal, window, cx))
|
||||
})?;
|
||||
log::info!("app: main window open");
|
||||
Ok(window)
|
||||
|
||||
@@ -4,3 +4,4 @@ pub mod term;
|
||||
#[cfg(test)]
|
||||
mod testlog;
|
||||
pub mod themes;
|
||||
pub mod workspace;
|
||||
@@ -0,0 +1,139 @@
|
||||
use ely_gpui_component::{
|
||||
primitives::FocusScope,
|
||||
shell::{ColumnShell, ResizeBorder, ShellColumn},
|
||||
theme::ActiveTheme,
|
||||
};
|
||||
use gpui::{
|
||||
App, Context, Entity, FocusHandle, InteractiveElement, IntoElement, KeyBinding, ParentElement,
|
||||
Render, SharedString, Styled, Subscription, Window, actions, div,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
settings::{Settings, SettingsStore},
|
||||
term::{TerminalEvent, TerminalView},
|
||||
};
|
||||
|
||||
actions!(elyterminal, [ToggleSessions, TogglePanel]);
|
||||
|
||||
/// The key context of the main window's content.
|
||||
const CONTEXT: &str = "Workspace";
|
||||
|
||||
/// The window's title while the program names none.
|
||||
pub const TITLE: &str = "ELYTerminal";
|
||||
|
||||
/// Cmd-B and Cmd-Alt-B toggle the side columns; Ctrl-Shift-B and Ctrl-Shift-Alt-B off macOS.
|
||||
pub fn bind_keys(cx: &mut App) {
|
||||
let (sessions, panel) = if cfg!(target_os = "macos") {
|
||||
("cmd-b", "cmd-alt-b")
|
||||
} else {
|
||||
("ctrl-shift-b", "ctrl-shift-alt-b")
|
||||
};
|
||||
cx.bind_keys([
|
||||
KeyBinding::new(sessions, ToggleSessions, Some(CONTEXT)),
|
||||
KeyBinding::new(panel, TogglePanel, Some(CONTEXT)),
|
||||
]);
|
||||
}
|
||||
|
||||
/// The main window's content: sessions, the terminal, and Git or Files, in Ely's columns.
|
||||
pub struct Workspace {
|
||||
focus: FocusHandle,
|
||||
terminal: Entity<TerminalView>,
|
||||
/// The program's title, or the app's name while it names none.
|
||||
title: SharedString,
|
||||
_events: Subscription,
|
||||
_settings: Subscription,
|
||||
}
|
||||
|
||||
impl Workspace {
|
||||
pub fn new(
|
||||
terminal: Entity<TerminalView>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
Self {
|
||||
focus: cx.focus_handle(),
|
||||
title: TITLE.into(),
|
||||
_events: cx.subscribe_in(&terminal, window, Self::terminal_event),
|
||||
_settings: cx.observe_global::<SettingsStore>(|_, cx| cx.notify()),
|
||||
terminal,
|
||||
}
|
||||
}
|
||||
|
||||
/// The window and the terminal's header wear the program's title.
|
||||
fn terminal_event(
|
||||
&mut self,
|
||||
_: &Entity<TerminalView>,
|
||||
event: &TerminalEvent,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let TerminalEvent::Title(title) = event {
|
||||
let title = if title.is_empty() { TITLE } else { title };
|
||||
if self.title.as_ref() != title {
|
||||
log::debug!("app: the title is {title:?}");
|
||||
self.title = SharedString::from(title.to_string());
|
||||
window.set_window_title(title);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Shows or hides a side column, as the settings keep it.
|
||||
fn toggle(&mut self, flip: fn(&mut Settings), cx: &mut Context<Self>) {
|
||||
match SettingsStore::update(cx, flip) {
|
||||
Ok(()) => {
|
||||
let settings = SettingsStore::get(cx);
|
||||
log::info!(
|
||||
"app: sessions {}, right column {}",
|
||||
if settings.show_sessions {
|
||||
"shown"
|
||||
} else {
|
||||
"hidden"
|
||||
},
|
||||
if settings.show_panel {
|
||||
"shown"
|
||||
} else {
|
||||
"hidden"
|
||||
}
|
||||
);
|
||||
}
|
||||
Err(error) => log::error!("app: the columns stay: {error:#}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Workspace {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let settings = SettingsStore::get(cx);
|
||||
let width = cx.theme().sidebar_width(false);
|
||||
let mut shell = ColumnShell::new("workspace");
|
||||
if settings.show_sessions {
|
||||
shell = shell.column(ShellColumn::new("sessions").width(width));
|
||||
}
|
||||
shell = shell.column(
|
||||
ShellColumn::new("terminal")
|
||||
.title(self.title.clone())
|
||||
.child(self.terminal.clone()),
|
||||
);
|
||||
if settings.show_panel {
|
||||
shell = shell.column(ShellColumn::new("panel").width(width));
|
||||
}
|
||||
ResizeBorder::new().child(
|
||||
FocusScope::new(&self.focus).root().size_full().child(
|
||||
div()
|
||||
.size_full()
|
||||
.key_context(CONTEXT)
|
||||
.on_action(cx.listener(|this, _: &ToggleSessions, _, cx| {
|
||||
this.toggle(
|
||||
|settings| settings.show_sessions = !settings.show_sessions,
|
||||
cx,
|
||||
)
|
||||
}))
|
||||
.on_action(cx.listener(|this, _: &TogglePanel, _, cx| {
|
||||
this.toggle(|settings| settings.show_panel = !settings.show_panel, cx)
|
||||
}))
|
||||
.child(shell),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user