diff --git a/crates/egui-term/examples/custom_bindings.rs b/crates/egui-term/examples/custom_bindings.rs index 4bf5cd4..2c7c36e 100644 --- a/crates/egui-term/examples/custom_bindings.rs +++ b/crates/egui-term/examples/custom_bindings.rs @@ -92,7 +92,6 @@ impl eframe::App for App { active_tab_id: &mut self.active_id, }; let terminal = TerminalView::new(ui, term_ctx, term_opt) - .set_focus(true) .add_bindings(self.custom_terminal_bindings.clone()) .set_size(Vec2::new(ui.available_width(), ui.available_height())); diff --git a/crates/egui-term/examples/tabs.rs b/crates/egui-term/examples/tabs.rs index eb30b09..99dfd0d 100644 --- a/crates/egui-term/examples/tabs.rs +++ b/crates/egui-term/examples/tabs.rs @@ -81,9 +81,8 @@ impl eframe::App for App { default_font_size: 14., active_tab_id: &mut self.active_tab, }; - let terminal = TerminalView::new(ui, term_ctx, term_opt) - .set_focus(true) - .set_size(ui.available_size()); + let terminal = + TerminalView::new(ui, term_ctx, term_opt).set_size(ui.available_size()); ui.add(terminal); } diff --git a/crates/egui-term/examples/themes.rs b/crates/egui-term/examples/themes.rs index 4082c75..c382023 100644 --- a/crates/egui-term/examples/themes.rs +++ b/crates/egui-term/examples/themes.rs @@ -107,7 +107,6 @@ impl eframe::App for App { active_tab_id: &mut self.active_id, }; let terminal = TerminalView::new(ui, term_ctx, term_opt) - .set_focus(true) .set_size(Vec2::new(ui.available_width(), ui.available_height())); ui.add(terminal); diff --git a/crates/egui-term/src/display/mod.rs b/crates/egui-term/src/display/mod.rs index 188bf9b..22da4e2 100644 --- a/crates/egui-term/src/display/mod.rs +++ b/crates/egui-term/src/display/mod.rs @@ -8,12 +8,8 @@ use alacritty_terminal::grid::GridCell; use alacritty_terminal::term::cell::Flags; use alacritty_terminal::term::TermMode; use alacritty_terminal::vte::ansi::{Color, NamedColor}; -use copypasta::ClipboardProvider; use egui::epaint::RectShape; -use egui::{ - Align2, Area, Button, Color32, CornerRadius, CursorIcon, Id, Key, KeyboardShortcut, Modifiers, - Painter, Pos2, Rect, Response, Vec2, WidgetText, -}; +use egui::{Align2, CornerRadius, CursorIcon, Painter, Pos2, Rect, Response, Vec2}; use egui::{Shape, Stroke}; impl TerminalView<'_> { @@ -146,82 +142,3 @@ impl TerminalView<'_> { painter.extend(shapes); } } - -impl TerminalView<'_> { - pub fn context_menu(&mut self, pos: Pos2, layout: &Response, ui: &mut egui::Ui) { - Area::new(Id::new(format!("context_menu_{:?}", self.id()))) - .fixed_pos(pos) - .order(egui::Order::Foreground) - .show(ui.ctx(), |ui| { - egui::Frame::popup(ui.style()).show(ui, |ui| { - let width = 200.; - ui.set_width(width); - // copy btn - self.copy_btn(ui, layout, width); - // paste btn - self.paste_btn(ui, width); - - ui.separator(); - // select all btn - self.select_all_btn(ui, width); - }); - }); - } - - fn copy_btn(&mut self, ui: &mut egui::Ui, layout: &Response, btn_width: f32) { - #[cfg(not(target_os = "macos"))] - let copy_shortcut = KeyboardShortcut::new(Modifiers::CTRL | Modifiers::SHIFT, Key::C); - #[cfg(target_os = "macos")] - let copy_shortcut = KeyboardShortcut::new(Modifiers::MAC_CMD, Key::C); - let copy_shortcut = ui.ctx().format_shortcut(©_shortcut); - let copy_btn = context_btn("Copy", btn_width, Some(copy_shortcut)); - if ui.add(copy_btn).clicked() { - let data = self.term_ctx.selection_content(); - layout.ctx.copy_text(data); - ui.close(); - } - } - - fn paste_btn(&mut self, ui: &mut egui::Ui, btn_width: f32) { - #[cfg(not(target_os = "macos"))] - let paste_shortcut = KeyboardShortcut::new(Modifiers::CTRL | Modifiers::SHIFT, Key::V); - #[cfg(target_os = "macos")] - let paste_shortcut = KeyboardShortcut::new(Modifiers::MAC_CMD, Key::V); - let paste_shortcut = ui.ctx().format_shortcut(&paste_shortcut); - let paste_btn = context_btn("Paste", btn_width, Some(paste_shortcut)); - if ui.add(paste_btn).clicked() { - if let Ok(data) = self.term_ctx.clipboard.get_contents() { - self.term_ctx.write_data(data.into_bytes()); - self.term_ctx.terminal.selection = None; - } - ui.close(); - } - } - - fn select_all_btn(&mut self, ui: &mut egui::Ui, btn_width: f32) { - #[cfg(not(target_os = "macos"))] - let select_all_shortcut = KeyboardShortcut::new(Modifiers::CTRL, Key::A); - #[cfg(target_os = "macos")] - let select_all_shortcut = KeyboardShortcut::new(Modifiers::MAC_CMD, Key::A); - let select_all_shortcut = ui.ctx().format_shortcut(&select_all_shortcut); - let select_all_btn = context_btn("Select All", btn_width, Some(select_all_shortcut)); - if ui.add(select_all_btn).clicked() { - self.term_ctx.select_all(); - ui.close(); - } - } -} - -fn context_btn<'a>( - text: impl Into, - width: f32, - shortcut: Option, -) -> Button<'a> { - let mut btn = Button::new(text) - .fill(Color32::TRANSPARENT) - .min_size((width, 0.).into()); - if let Some(shortcut) = shortcut { - btn = btn.shortcut_text(shortcut); - } - btn -} diff --git a/crates/egui-term/src/input/mod.rs b/crates/egui-term/src/input/mod.rs index ae9a85f..9151c30 100644 --- a/crates/egui-term/src/input/mod.rs +++ b/crates/egui-term/src/input/mod.rs @@ -133,10 +133,6 @@ impl TerminalView<'_> { PointerButton::Primary => { self.left_button_click(state, layout, position, modifiers, pressed) } - PointerButton::Secondary => { - state.context_menu_position = Some(position); - None - } _ => None, } } @@ -149,7 +145,7 @@ impl TerminalView<'_> { modifiers: &Modifiers, pressed: bool, ) -> Option { - if state.context_menu_position.is_some() { + if layout.context_menu_opened() { return None; } let terminal_mode = self.term_ctx.terminal.mode(); diff --git a/crates/egui-term/src/lib.rs b/crates/egui-term/src/lib.rs index fcd299a..5349d6a 100644 --- a/crates/egui-term/src/lib.rs +++ b/crates/egui-term/src/lib.rs @@ -8,6 +8,7 @@ mod scroll_bar; mod ssh; mod theme; mod types; +mod ui; mod view; pub use alacritty::{PtyEvent, TermType, Terminal, TerminalContext}; diff --git a/crates/egui-term/src/ui/menu.rs b/crates/egui-term/src/ui/menu.rs new file mode 100644 index 0000000..68daf73 --- /dev/null +++ b/crates/egui-term/src/ui/menu.rs @@ -0,0 +1,75 @@ +use crate::TerminalView; +use copypasta::ClipboardProvider; +use egui::{Button, Key, KeyboardShortcut, Modifiers, Response, WidgetText}; + +impl TerminalView<'_> { + pub fn context_menu(&mut self, layout: &Response) { + layout.context_menu(|ui| { + let width = 200.; + ui.set_width(width); + // copy btn + self.copy_btn(ui, layout, width); + // paste btn + self.paste_btn(ui, width); + + ui.separator(); + // select all btn + self.select_all_btn(ui, width); + }); + } + + fn copy_btn(&mut self, ui: &mut egui::Ui, layout: &Response, btn_width: f32) { + #[cfg(not(target_os = "macos"))] + let copy_shortcut = KeyboardShortcut::new(Modifiers::CTRL | Modifiers::SHIFT, Key::C); + #[cfg(target_os = "macos")] + let copy_shortcut = KeyboardShortcut::new(Modifiers::MAC_CMD, Key::C); + let copy_shortcut = ui.ctx().format_shortcut(©_shortcut); + let copy_btn = context_btn("Copy", btn_width, Some(copy_shortcut)); + if ui.add(copy_btn).clicked() { + let data = self.term_ctx.selection_content(); + layout.ctx.copy_text(data); + ui.close(); + } + } + + fn paste_btn(&mut self, ui: &mut egui::Ui, btn_width: f32) { + #[cfg(not(target_os = "macos"))] + let paste_shortcut = KeyboardShortcut::new(Modifiers::CTRL | Modifiers::SHIFT, Key::V); + #[cfg(target_os = "macos")] + let paste_shortcut = KeyboardShortcut::new(Modifiers::MAC_CMD, Key::V); + let paste_shortcut = ui.ctx().format_shortcut(&paste_shortcut); + let paste_btn = context_btn("Paste", btn_width, Some(paste_shortcut)); + if ui.add(paste_btn).clicked() { + if let Ok(data) = self.term_ctx.clipboard.get_contents() { + self.term_ctx.write_data(data.into_bytes()); + self.term_ctx.terminal.selection = None; + } + ui.close(); + } + } + + fn select_all_btn(&mut self, ui: &mut egui::Ui, btn_width: f32) { + #[cfg(not(target_os = "macos"))] + let select_all_shortcut = KeyboardShortcut::new(Modifiers::CTRL, Key::A); + #[cfg(target_os = "macos")] + let select_all_shortcut = KeyboardShortcut::new(Modifiers::MAC_CMD, Key::A); + let select_all_shortcut = ui.ctx().format_shortcut(&select_all_shortcut); + let select_all_btn = context_btn("Select All", btn_width, Some(select_all_shortcut)); + if ui.add(select_all_btn).clicked() { + self.term_ctx.select_all(); + ui.close(); + } + } +} + +fn context_btn<'a>( + text: impl Into, + width: f32, + shortcut: Option, +) -> Button<'a> { + let mut btn = Button::new(text).min_size((width, 0.).into()); + if let Some(shortcut) = shortcut { + btn = btn.shortcut_text(shortcut); + } + btn +} diff --git a/crates/egui-term/src/ui/mod.rs b/crates/egui-term/src/ui/mod.rs new file mode 100644 index 0000000..c57668d --- /dev/null +++ b/crates/egui-term/src/ui/mod.rs @@ -0,0 +1 @@ +mod menu; diff --git a/crates/egui-term/src/view.rs b/crates/egui-term/src/view.rs index 8882a81..f8925d8 100644 --- a/crates/egui-term/src/view.rs +++ b/crates/egui-term/src/view.rs @@ -23,7 +23,6 @@ pub struct TerminalViewState { // for terminal pub mouse_point: Point, pub mouse_position: Option, - pub context_menu_position: Option, pub cursor_position: Option, pub scrollbar_state: ScrollbarState, } @@ -79,17 +78,7 @@ impl Widget for TerminalView<'_> { self.has_focus = false; } - // context menu - if let Some(pos) = state.context_menu_position { - if is_in_terminal(pos, layout.rect) { - self.context_menu(pos, &layout, ui); - } - } - - if ui.input(|input_state| input_state.pointer.primary_clicked()) { - state.context_menu_position = None; - ui.close(); - } + self.context_menu(&layout); let background = self.theme().get_color(Color::Named(NamedColor::Background)); @@ -150,7 +139,7 @@ impl<'a> TerminalView<'a> { Self { widget_id, - has_focus: false, + has_focus: true, size: ui.available_size(), term_ctx, options, diff --git a/nxshell/src/app.rs b/nxshell/src/app.rs index 32424fe..e5742cd 100644 --- a/nxshell/src/app.rs +++ b/nxshell/src/app.rs @@ -66,6 +66,7 @@ pub struct NxShell { pub clipboard: ClipboardContext, pub db: DbConn, pub opts: NxShellOptions, + pub toasts: Toasts, } impl NxShell { @@ -89,6 +90,9 @@ impl NxShell { ..Default::default() }, state_manager, + toasts: Toasts::new() + .anchor(Align2::CENTER_CENTER, (10.0, 10.0)) + .direction(egui::Direction::TopDown), }) } @@ -112,10 +116,6 @@ impl eframe::App for NxShell { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { self.recv_event(); - let mut toasts = Toasts::new() - .anchor(Align2::CENTER_CENTER, (10.0, 10.0)) - .direction(egui::Direction::TopDown); - egui::TopBottomPanel::top("main_top_panel").show(ctx, |ui| { self.menubar(ui); }); @@ -131,7 +131,7 @@ impl eframe::App for NxShell { self.search_sessions(ui); ui.separator(); - self.list_sessions(ctx, ui, &mut toasts); + self.list_sessions(ctx, ui); }); egui::TopBottomPanel::bottom("main_bottom_panel").show(ctx, |ui| { ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| { @@ -141,14 +141,14 @@ impl eframe::App for NxShell { if *self.opts.show_add_session_modal.borrow() { self.opts.surrender_focus(); - self.show_add_session_window(ctx, &mut toasts); + self.show_add_session_window(ctx); } egui::CentralPanel::default().show(ctx, |_ui| { self.tab_view(ctx); }); - toasts.show(ctx); + self.toasts.show(ctx); } } @@ -165,7 +165,7 @@ impl NxShell { } } - fn list_sessions(&mut self, ctx: &egui::Context, ui: &mut egui::Ui, toasts: &mut Toasts) { + fn list_sessions(&mut self, ctx: &egui::Context, ui: &mut egui::Ui) { if let Some(sessions) = self.state_manager.sessions.take() { for (group, sessions) in sessions.iter() { CollapsingHeader::new(group) @@ -183,12 +183,12 @@ impl NxShell { if let Err(err) = self.add_shell_tab_with_secret(ctx, session) { - toasts.add(error_toast(err.to_string())); + self.toasts.add(error_toast(err.to_string())); } } Ok(None) => {} Err(err) => { - toasts.add(error_toast(err.to_string())); + self.toasts.add(error_toast(err.to_string())); } } } else if response.secondary_clicked() { diff --git a/nxshell/src/ui/form/session.rs b/nxshell/src/ui/form/session.rs index b86d45f..9d90fb1 100644 --- a/nxshell/src/ui/form/session.rs +++ b/nxshell/src/ui/form/session.rs @@ -8,7 +8,6 @@ use egui::{ use egui_form::garde::GardeReport; use egui_form::{Form, FormField}; use egui_term::{Authentication, SshOptions, TermType}; -use egui_toast::Toasts; use garde::Validate; use orion::aead::{seal, SecretKey}; use std::fmt::Display; @@ -91,7 +90,7 @@ impl SessionState { } impl NxShell { - pub fn show_add_session_window(&mut self, ctx: &Context, toasts: &mut Toasts) { + pub fn show_add_session_window(&mut self, ctx: &Context) { let session_id = Id::new(SessionState::id()); let mut session_state = SessionState::load(ctx, session_id); @@ -114,7 +113,7 @@ impl NxShell { Ok(_) => should_close = true, Err(err) => { error!("failed to add session: {err}"); - toasts.add(error_toast(err.to_string())); + self.toasts.add(error_toast(err.to_string())); } } } diff --git a/nxshell/src/ui/tab_view/mod.rs b/nxshell/src/ui/tab_view/mod.rs index 502fadd..c4671cd 100644 --- a/nxshell/src/ui/tab_view/mod.rs +++ b/nxshell/src/ui/tab_view/mod.rs @@ -118,9 +118,8 @@ impl egui_dock::TabViewer for TabViewer<'_> { active_tab_id: &mut self.options.active_tab_id, }; - let terminal = TerminalView::new(ui, term_ctx, term_opt) - .set_focus(true) - .set_size(ui.available_size()); + let terminal = + TerminalView::new(ui, term_ctx, term_opt).set_size(ui.available_size()); ui.add(terminal); } TabInner::SessionList(_list) => {