Compare commits
No commits in common. "81dc7d3eebb9bccc2ed5f603fb0a3721a3bed298" and "99842bde03c134cb56cf1b4515d257609f6af3dd" have entirely different histories.
81dc7d3eeb
...
99842bde03
16 changed files with 642 additions and 370 deletions
501
Cargo.lock
generated
501
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "cosmic-term"
|
||||
version = "1.9.0"
|
||||
version = "1.8.0"
|
||||
authors = ["Jeremy Soller <jeremy@system76.com>"]
|
||||
edition = "2024"
|
||||
license = "GPL-3.0-only"
|
||||
|
|
|
|||
6
debian/changelog
vendored
6
debian/changelog
vendored
|
|
@ -1,9 +1,3 @@
|
|||
cosmic-term (1.9.0) noble; urgency=medium
|
||||
|
||||
* Epoch 1.9.0 version update
|
||||
|
||||
-- Michael Murphy <michael@mmurphy.dev> Mon, 14 Sep 2026 19:50:47 +0200
|
||||
|
||||
cosmic-term (1.8.0) noble; urgency=medium
|
||||
|
||||
* Epoch 1.8.0 version update
|
||||
|
|
|
|||
6
debian/source/options
vendored
6
debian/source/options
vendored
|
|
@ -1,6 +1,4 @@
|
|||
tar-ignore=.github
|
||||
tar-ignore=target/.rustc_info.json
|
||||
tar-ignore=target/CACHEDIR.TAG
|
||||
tar-ignore=target/debug
|
||||
tar-ignore=target/release
|
||||
tar-ignore=.vscode
|
||||
tar-ignore=vendor
|
||||
tar-ignore=target
|
||||
|
|
@ -63,7 +63,6 @@ show-pane-borders = Show pane borders
|
|||
### Advanced
|
||||
advanced = Advanced
|
||||
show-headerbar = Show header
|
||||
window-header = Window header
|
||||
show-header-description = Reveal the header from the right-click menu
|
||||
tab-new-inherit-working-directory = New tabs and windows use current directory
|
||||
tab-new-inherit-working-directory-description = Open new tabs and windows in the active tab's working directory
|
||||
|
|
@ -153,7 +152,7 @@ next-tab = Next tab
|
|||
previous-tab = Previous tab
|
||||
split-horizontal = Split horizontal
|
||||
split-vertical = Split vertical
|
||||
pane-toggle-maximize = Maximize or restore pane
|
||||
pane-toggle-maximize = Toggle maximized
|
||||
menu-color-schemes = Color schemes...
|
||||
menu-settings = Settings...
|
||||
menu-about = About COSMIC Terminal...
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ repository = Repositori
|
|||
support = Dukungan
|
||||
color-schemes = Skema warna
|
||||
cosmic-terminal = Terminal COSMIC
|
||||
rename = Ganti Nama
|
||||
rename = Ganti nama
|
||||
export = Ekspor
|
||||
delete = Hapus
|
||||
import = Impor
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
cancel = Semmet
|
||||
cancel = Sefsex
|
||||
name = Isem
|
||||
delete = kkes
|
||||
rename = Snifel isem
|
||||
quit = Ffeɣ
|
||||
support = Tallelt
|
||||
quit = Tuffɣa
|
||||
support = Tallalt
|
||||
repository = Asarsay
|
||||
settings = Iɣewwaṛen
|
||||
appearance = Timeẓri
|
||||
|
|
@ -14,7 +14,7 @@ paste = Senteḍ
|
|||
select-all = Fren akk
|
||||
view = Wali
|
||||
menu-settings = Iɣewwaṛen…
|
||||
match-desktop = Semṣada akked tnarit
|
||||
match-desktop = Amṣada d tnarit
|
||||
file = Afaylu
|
||||
new-window = Asfaylu amaynut
|
||||
edit = Ẓreg
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ light = Ronî
|
|||
advanced = Pêşketî
|
||||
support = Piştgirî
|
||||
default-font = Cûrenivîs
|
||||
paste = Pê ve bike
|
||||
paste = Pêve bike
|
||||
menu-settings = Sazkarî...
|
||||
command-line = Rêza fermanê
|
||||
view = Dîtin
|
||||
|
|
@ -100,7 +100,7 @@ focus-pane-left = Balê bikişîne ser destgeha çepê
|
|||
focus-pane-right = Balê bikişîne ser destgeha rastê
|
||||
focus-pane-up = Balê bikişîne ser destgeha jorîn
|
||||
password-manager = Rêveberê borînpeyvê
|
||||
paste-primary = A sereke pê ve bike
|
||||
paste-primary = A sereke pêve bike
|
||||
reset-to-default = Vegere rewşa berdest
|
||||
shortcut-capture-hint = Kombînasyona kilîtan bitikîne
|
||||
shortcut-replace-body = { $binding } berê ji bo { $existing } hatiye destnîşankirin. Tu dixwazî bi { $new_action } re biguherînî?
|
||||
|
|
|
|||
|
|
@ -1,14 +1,7 @@
|
|||
settings = Configuracion
|
||||
settings = Paramètres
|
||||
appearance = Aparéncia
|
||||
theme = Tèma
|
||||
match-desktop = Assortir al burèu
|
||||
dark = Escur
|
||||
dark = Fosc
|
||||
light = Clar
|
||||
file = Fichièr
|
||||
name = Nom
|
||||
repository = Depaus
|
||||
cancel = Anullar
|
||||
menu-settings = Configuracion...
|
||||
quit = Quitar
|
||||
delete = Suprimir
|
||||
replace = Remplaçar
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
cosmic-terminal = Terminal COSMIC
|
||||
comment = Emulator terminala dla środowiska COSMIC
|
||||
comment = Emulator terminala dla pulpitu COSMIC
|
||||
keywords = Cli;WierszPoleceń;Powłoka;Terminal;
|
||||
new-terminal = Nowy terminal
|
||||
|
||||
|
|
@ -73,7 +73,7 @@ advanced = Zaawansowane
|
|||
show-headerbar = Pokaż pasek nagłówka
|
||||
show-header-description = Ukazuje pasek nagłówka z menu wybieranego prawym przyciskiem myszki
|
||||
# Find
|
||||
find-placeholder = Wyszukaj…
|
||||
find-placeholder = Szukaj…
|
||||
find-previous = Znajdź poprzedni
|
||||
find-next = Znajdź następny
|
||||
|
||||
|
|
@ -96,7 +96,7 @@ edit = Edytuj
|
|||
copy = Kopiuj
|
||||
paste = Wklej
|
||||
select-all = Zaznacz wszystko
|
||||
find = Wyszukaj
|
||||
find = Szukaj
|
||||
clear-scrollback = Wyczyść przewijanie
|
||||
|
||||
## View
|
||||
|
|
|
|||
|
|
@ -39,8 +39,8 @@ settings = การตั้งค่า
|
|||
appearance = ลักษณะ
|
||||
theme = ธีม
|
||||
match-desktop = ใช้ตามธีมหน้าจอหลัก
|
||||
dark = มืด
|
||||
light = สว่าง
|
||||
dark = ธีมมืด
|
||||
light = ธีมสว่าง
|
||||
syntax-dark = โทนสีมืด
|
||||
syntax-light = โทนสีสว่าง
|
||||
default-zoom-step = ระยะซูม
|
||||
|
|
@ -109,8 +109,3 @@ pane-toggle-maximize = ขยายให้ใหญ่สุด
|
|||
menu-color-schemes = โทนสี...
|
||||
menu-settings = การตั้งค่า...
|
||||
menu-about = เกี่ยวกับ เทอร์มินัล COSMIC...
|
||||
cancel = ยกเลิก
|
||||
reset-to-default = รีเซ็ตเป็นค่าเริ่มต้น
|
||||
type-to-search = พิมพ์เพื่อค้นหา
|
||||
add-another-keybinding = เพิ่มคีย์ลัดเพิ่มเติม
|
||||
replace = แทนที่
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ new-terminal = 新建終端機
|
|||
## Color schemes
|
||||
|
||||
color-schemes = 配色方案
|
||||
rename = 重新命名
|
||||
rename = 重命名
|
||||
export = 匯出
|
||||
delete = 刪除
|
||||
import = 匯入
|
||||
|
|
|
|||
241
src/main.rs
241
src/main.rs
|
|
@ -566,7 +566,7 @@ pub enum Message {
|
|||
ReorderTab(Pane, ReorderEvent),
|
||||
SaveOutput(Option<segmented_button::Entity>),
|
||||
SaveOutputResult(pane_grid::Pane, segmented_button::Entity, DialogResult),
|
||||
Surface(surface::Action<Message>),
|
||||
Surface(surface::Action),
|
||||
SelectAll(Option<segmented_button::Entity>),
|
||||
ShowAdvancedFontSettings(bool),
|
||||
ShowHeaderBar(bool),
|
||||
|
|
@ -604,6 +604,7 @@ pub enum Message {
|
|||
ZoomIn,
|
||||
ZoomOut,
|
||||
ZoomReset,
|
||||
ContextMenuPopupClosed(window::Id),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
|
|
@ -681,6 +682,14 @@ pub struct App {
|
|||
shortcut_search_regex: Option<regex::Regex>,
|
||||
shortcut_search_value: String,
|
||||
modifiers: Modifiers,
|
||||
context_menu_popup: Option<(
|
||||
window::Id,
|
||||
pane_grid::Pane,
|
||||
segmented_button::Entity,
|
||||
Option<String>,
|
||||
widget::Id,
|
||||
cosmic::iced::Point,
|
||||
)>,
|
||||
#[cfg(feature = "password_manager")]
|
||||
password_mgr: password_manager::PasswordManager,
|
||||
}
|
||||
|
|
@ -2204,6 +2213,7 @@ impl Application for App {
|
|||
shortcut_search_regex: None,
|
||||
shortcut_search_value: String::new(),
|
||||
modifiers: Modifiers::empty(),
|
||||
context_menu_popup: None,
|
||||
#[cfg(feature = "password_manager")]
|
||||
password_mgr: Default::default(),
|
||||
};
|
||||
|
|
@ -3423,7 +3433,16 @@ impl Application for App {
|
|||
return self.update_title(None);
|
||||
}
|
||||
Message::TabContextAction(entity, action) => {
|
||||
// Close context menu popup
|
||||
let mut tasks = Vec::new();
|
||||
if let Some((_popup_id, _, _, _, _, _)) = self.context_menu_popup.take() {
|
||||
#[cfg(feature = "wayland")]
|
||||
if is_wayland() {
|
||||
tasks.push(cosmic::task::message(Message::Surface(
|
||||
cosmic::surface::action::destroy_popup(_popup_id),
|
||||
)));
|
||||
}
|
||||
}
|
||||
// Close terminal context menu state
|
||||
if let Some(tab_model) = self.pane_model.active()
|
||||
&& let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity)
|
||||
|
|
@ -3432,8 +3451,11 @@ impl Application for App {
|
|||
//Some actions need the menu_state,
|
||||
//so only clear the position for them.
|
||||
match action {
|
||||
// these read the link from the menu state and clear it themselves
|
||||
Action::LaunchUrlByMenu | Action::CopyUrlByMenu => {}
|
||||
Action::LaunchUrlByMenu | Action::CopyUrlByMenu => {
|
||||
if let Some(context_menu) = terminal.context_menu.as_mut() {
|
||||
context_menu.position = None;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
terminal.context_menu = None;
|
||||
}
|
||||
|
|
@ -3443,29 +3465,94 @@ impl Application for App {
|
|||
return cosmic::Task::batch(tasks);
|
||||
}
|
||||
Message::TabContextMenu(pane, menu_state) => {
|
||||
#[allow(unused_mut)]
|
||||
let mut tasks = Vec::new();
|
||||
|
||||
// Close existing context menu popup if any
|
||||
if let Some((_popup_id, _, _, _, _, _)) = self.context_menu_popup.take() {
|
||||
#[cfg(feature = "wayland")]
|
||||
if is_wayland() {
|
||||
tasks.push(cosmic::task::message(Message::Surface(
|
||||
cosmic::surface::action::destroy_popup(_popup_id),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
// Clear all terminal context_menu state
|
||||
for (_, tab_model) in self.pane_model.panes.iter() {
|
||||
for entity in tab_model.iter() {
|
||||
if let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) {
|
||||
let mut terminal = terminal.lock().unwrap();
|
||||
if terminal.context_menu.take().is_some() {
|
||||
terminal.active_regex_match = None;
|
||||
terminal.needs_update = true;
|
||||
}
|
||||
terminal.context_menu = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A right press records what is under the cursor
|
||||
if let Some(menu_state) = menu_state
|
||||
&& let Some(tab_model) = self.pane_model.panes.get(pane)
|
||||
{
|
||||
let entity = tab_model.active();
|
||||
if let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) {
|
||||
terminal.lock().unwrap().context_menu = Some(menu_state);
|
||||
if let Some(menu_state) = menu_state {
|
||||
if let Some(_position) = menu_state.position {
|
||||
let local_position = menu_state.local_position.unwrap_or(_position);
|
||||
if let Some(tab_model) = self.pane_model.panes.get(pane) {
|
||||
let entity = tab_model.active();
|
||||
let link = menu_state.link.clone();
|
||||
let popup_id = window::Id::unique();
|
||||
|
||||
if let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) {
|
||||
let mut terminal = terminal.lock().unwrap();
|
||||
terminal.context_menu = Some(menu_state);
|
||||
}
|
||||
|
||||
self.context_menu_popup = Some((
|
||||
popup_id,
|
||||
pane,
|
||||
entity,
|
||||
link,
|
||||
widget::Id::unique(),
|
||||
local_position,
|
||||
));
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
if is_wayland() {
|
||||
let main_window = self.core.main_window_id().unwrap();
|
||||
let pos_x = _position.x as i32;
|
||||
let pos_y = _position.y as i32;
|
||||
|
||||
tasks.push(cosmic::task::message(Message::Surface(
|
||||
cosmic::surface::action::app_popup(
|
||||
|_| Default::default(),
|
||||
move |_app: &mut Self| {
|
||||
use cosmic::cctk::wayland_protocols::xdg::shell::client::xdg_positioner::{Anchor, Gravity};
|
||||
use cosmic::iced::runtime::platform_specific::wayland::popup::{SctkPopupSettings, SctkPositioner};
|
||||
|
||||
SctkPopupSettings {
|
||||
parent: main_window,
|
||||
id: popup_id,
|
||||
positioner: SctkPositioner {
|
||||
size: None,
|
||||
anchor_rect: cosmic::iced::Rectangle {
|
||||
x: pos_x,
|
||||
y: pos_y,
|
||||
width: 1,
|
||||
height: 1,
|
||||
},
|
||||
anchor: Anchor::None,
|
||||
gravity: Gravity::BottomRight,
|
||||
reactive: true,
|
||||
..Default::default()
|
||||
},
|
||||
parent_size: None,
|
||||
grab: true,
|
||||
close_with_children: false,
|
||||
input_zone: None,
|
||||
}
|
||||
}, None),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
self.pane_model.set_focus(pane);
|
||||
}
|
||||
|
||||
return cosmic::Task::batch(tasks);
|
||||
}
|
||||
Message::TabNew => {
|
||||
// The profile from `--profile` only applies to the first tab
|
||||
|
|
@ -3857,8 +3944,26 @@ impl Application for App {
|
|||
self.reset_active_pane_zoom();
|
||||
return self.update_config();
|
||||
}
|
||||
Message::ContextMenuPopupClosed(id) => {
|
||||
if let Some((popup_id, pane, entity, _, _, _)) = &self.context_menu_popup
|
||||
&& id == *popup_id
|
||||
{
|
||||
// Clear link underline on the terminal
|
||||
if let Some(tab_model) = self.pane_model.panes.get(*pane)
|
||||
&& let Some(terminal) = tab_model.data::<Mutex<Terminal>>(*entity)
|
||||
{
|
||||
let mut terminal = terminal.lock().unwrap();
|
||||
terminal.context_menu = None;
|
||||
terminal.active_regex_match = None;
|
||||
terminal.needs_update = true;
|
||||
}
|
||||
self.context_menu_popup = None;
|
||||
}
|
||||
}
|
||||
Message::Surface(a) => {
|
||||
return cosmic::task::message(cosmic::Action::Surface(a));
|
||||
return cosmic::task::message(cosmic::Action::Cosmic(
|
||||
cosmic::app::Action::Surface(a),
|
||||
));
|
||||
}
|
||||
Message::ReorderTab(
|
||||
pane,
|
||||
|
|
@ -4007,7 +4112,32 @@ impl Application for App {
|
|||
]
|
||||
}
|
||||
|
||||
fn on_close_requested(&self, id: window::Id) -> Option<Self::Message> {
|
||||
if let Some((popup_id, _, _, _, _, _)) = &self.context_menu_popup
|
||||
&& id == *popup_id
|
||||
{
|
||||
return Some(Message::ContextMenuPopupClosed(id));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn view_window(&self, window_id: window::Id) -> Element<'_, Message> {
|
||||
if let Some((popup_id, pane, entity, ref link, ref autosize_id, _)) =
|
||||
self.context_menu_popup
|
||||
&& window_id == popup_id
|
||||
{
|
||||
return widget::autosize::autosize(
|
||||
menu::context_menu(
|
||||
&self.config,
|
||||
&self.key_binds,
|
||||
entity,
|
||||
link.clone(),
|
||||
self.terminal_monitors(pane, entity),
|
||||
),
|
||||
autosize_id.clone(),
|
||||
)
|
||||
.into();
|
||||
}
|
||||
match &self.dialog_opt {
|
||||
Some(dialog) => dialog.view(window_id),
|
||||
None => widget::text("Unknown window ID").into(),
|
||||
|
|
@ -4096,36 +4226,51 @@ impl Application for App {
|
|||
terminal_box = terminal_box.on_mouse_enter(move || Message::MouseEnter(pane));
|
||||
}
|
||||
|
||||
// The terminal records what was under the right press; the widget opens the
|
||||
// menu on release, so build it from that state
|
||||
let context_link = terminal
|
||||
.lock()
|
||||
.unwrap()
|
||||
.context_menu
|
||||
.as_ref()
|
||||
.map(|menu_state| menu_state.link.clone());
|
||||
if context_link.is_some() {
|
||||
terminal_box = terminal_box.context_menu_open(true);
|
||||
// If a context menu popup is active for this pane, inform the
|
||||
// terminal_box so it will emit on_context_menu(None) on click
|
||||
// to dismiss the popup.
|
||||
if self.context_menu_popup.is_some() {
|
||||
terminal_box = terminal_box.context_menu(cosmic::iced::Point::ORIGIN);
|
||||
}
|
||||
let mut context_menu = widget::context_menu(
|
||||
terminal_box,
|
||||
context_link.map(|link| {
|
||||
menu::context_menu(
|
||||
&self.config,
|
||||
&self.key_binds,
|
||||
entity,
|
||||
link,
|
||||
self.terminal_monitors(pane, entity),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.item_width(widget::menu::ItemWidth::Uniform(360))
|
||||
.on_close(Message::TabContextMenu(pane, None))
|
||||
.on_surface_action(Message::Surface);
|
||||
if let Some(window_id) = self.core.main_window_id() {
|
||||
context_menu = context_menu.window_id(window_id);
|
||||
}
|
||||
tab_column = tab_column.push(context_menu);
|
||||
|
||||
let use_wayland_popup = {
|
||||
#[cfg(feature = "wayland")]
|
||||
{
|
||||
is_wayland()
|
||||
}
|
||||
#[cfg(not(feature = "wayland"))]
|
||||
{
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
let tab_element: Element<'_, Message> = if !use_wayland_popup {
|
||||
// Fallback: render context menu as an inline popover
|
||||
if let Some((_, popup_pane, popup_entity, ref link, _, point)) =
|
||||
self.context_menu_popup
|
||||
{
|
||||
if pane == popup_pane {
|
||||
let mut popover = widget::popover(terminal_box.context_menu(point));
|
||||
popover = popover
|
||||
.popup(menu::context_menu(
|
||||
&self.config,
|
||||
&self.key_binds,
|
||||
popup_entity,
|
||||
link.clone(),
|
||||
self.terminal_monitors(popup_pane, popup_entity),
|
||||
))
|
||||
.position(widget::popover::Position::Point(point));
|
||||
popover.into()
|
||||
} else {
|
||||
terminal_box.into()
|
||||
}
|
||||
} else {
|
||||
terminal_box.into()
|
||||
}
|
||||
} else {
|
||||
terminal_box.into()
|
||||
};
|
||||
tab_column = tab_column.push(tab_element);
|
||||
}
|
||||
|
||||
//Only draw find in the currently focused pane
|
||||
|
|
@ -4306,6 +4451,14 @@ impl Application for App {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
fn is_wayland() -> bool {
|
||||
matches!(
|
||||
cosmic::app::cosmic::windowing_system(),
|
||||
Some(cosmic::app::cosmic::WindowingSystem::Wayland)
|
||||
)
|
||||
}
|
||||
|
||||
/// Divider color painted behind the pane grid to form pane borders.
|
||||
///
|
||||
/// When blur is active the transparent container must be used, otherwise the
|
||||
|
|
|
|||
185
src/menu.rs
185
src/menu.rs
|
|
@ -1,15 +1,17 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmic::widget::menu::action::MenuAction;
|
||||
use cosmic::iced::Point;
|
||||
use cosmic::widget::menu::key_bind::KeyBind;
|
||||
use cosmic::widget::menu::{Item as MenuItem, menu_button};
|
||||
use cosmic::widget::space;
|
||||
use cosmic::{
|
||||
Element,
|
||||
app::Core,
|
||||
iced::core::Border,
|
||||
iced::{Background, Length},
|
||||
iced::{Background, Length, advanced::widget::text::Style as TextStyle},
|
||||
theme,
|
||||
widget::{
|
||||
self,
|
||||
self, divider,
|
||||
menu::{ItemHeight, ItemWidth},
|
||||
responsive_menu_bar, segmented_button,
|
||||
},
|
||||
|
|
@ -21,92 +23,147 @@ use crate::{Action, ColorSchemeId, ColorSchemeKind, Config, Message, fl, termina
|
|||
static MENU_ID: LazyLock<cosmic::widget::Id> =
|
||||
LazyLock::new(|| cosmic::widget::Id::new("responsive-menu"));
|
||||
|
||||
/// What the terminal found under the right-click
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MenuState {
|
||||
pub position: Option<Point>,
|
||||
pub local_position: Option<Point>,
|
||||
pub link: Option<String>,
|
||||
}
|
||||
|
||||
/// A context menu action dispatched to the tab the menu was opened on.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct TabAction(pub segmented_button::Entity, pub Action);
|
||||
|
||||
impl MenuAction for TabAction {
|
||||
type Message = Message;
|
||||
|
||||
fn message(&self) -> Message {
|
||||
Message::TabContextAction(self.0, self.1)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn context_menu(
|
||||
pub fn context_menu<'a>(
|
||||
config: &Config,
|
||||
key_binds: &HashMap<KeyBind, Action>,
|
||||
entity: segmented_button::Entity,
|
||||
link: Option<String>,
|
||||
monitors: (bool, bool, bool),
|
||||
) -> Vec<widget::menu::Tree<Message>> {
|
||||
let item =
|
||||
|label: String, action: Action| MenuItem::Button(label, None, TabAction(entity, action));
|
||||
let check = |label: String, value: bool, action: Action| {
|
||||
MenuItem::CheckBox(label, None, value, TabAction(entity, action))
|
||||
) -> Element<'a, Message> {
|
||||
let find_key = |action: &Action| -> String {
|
||||
for (key_bind, key_action) in key_binds {
|
||||
if action == key_action {
|
||||
return key_bind.to_string();
|
||||
}
|
||||
}
|
||||
String::new()
|
||||
};
|
||||
fn key_style(theme: &cosmic::Theme) -> TextStyle {
|
||||
let mut color = theme.cosmic().background(theme.transparent).component.on;
|
||||
color.alpha *= 0.75;
|
||||
TextStyle {
|
||||
color: Some(color.into()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
let menu_item = |label, action| {
|
||||
let key = find_key(&action);
|
||||
menu_button(vec![
|
||||
widget::text(label).into(),
|
||||
space::horizontal().into(),
|
||||
widget::text(key)
|
||||
.class(theme::Text::Custom(key_style))
|
||||
.into(),
|
||||
])
|
||||
.on_press(Message::TabContextAction(entity, action))
|
||||
};
|
||||
|
||||
let mut items = vec![
|
||||
item(fl!("new-tab"), Action::TabNew),
|
||||
item(fl!("rename-tab"), Action::TabRename),
|
||||
MenuItem::Divider,
|
||||
item(fl!("copy"), Action::Copy),
|
||||
item(fl!("paste"), Action::Paste),
|
||||
item(fl!("select-all"), Action::SelectAll),
|
||||
MenuItem::Divider,
|
||||
item(fl!("clear-scrollback"), Action::ClearScrollback),
|
||||
item(fl!("save-output"), Action::SaveOutput),
|
||||
MenuItem::Divider,
|
||||
check(
|
||||
let menu_checkbox = |label, value, action| {
|
||||
menu_button(vec![
|
||||
widget::text(label).into(),
|
||||
widget::space::horizontal().into(),
|
||||
widget::toggler(value)
|
||||
.on_toggle(move |_| Message::TabContextAction(entity, action))
|
||||
.size(16.0)
|
||||
.into(),
|
||||
])
|
||||
.on_press(Message::TabContextAction(entity, action))
|
||||
};
|
||||
|
||||
let mut rows = vec![
|
||||
Element::from(menu_item(fl!("copy"), Action::Copy)),
|
||||
Element::from(menu_item(fl!("paste"), Action::Paste)),
|
||||
Element::from(menu_item(fl!("select-all"), Action::SelectAll)),
|
||||
Element::from(divider::horizontal::light()),
|
||||
Element::from(menu_item(fl!("clear-scrollback"), Action::ClearScrollback)),
|
||||
Element::from(menu_item(fl!("save-output"), Action::SaveOutput)),
|
||||
Element::from(divider::horizontal::light()),
|
||||
Element::from(menu_checkbox(
|
||||
fl!("monitor-activity"),
|
||||
monitors.0,
|
||||
Action::Monitor(MonitorKind::Activity),
|
||||
),
|
||||
check(
|
||||
)),
|
||||
Element::from(menu_checkbox(
|
||||
fl!("monitor-silence"),
|
||||
monitors.1,
|
||||
Action::Monitor(MonitorKind::Silence),
|
||||
),
|
||||
check(
|
||||
)),
|
||||
Element::from(menu_checkbox(
|
||||
fl!("monitor-process-finished"),
|
||||
monitors.2,
|
||||
Action::Monitor(MonitorKind::ProcessFinished),
|
||||
),
|
||||
MenuItem::Divider,
|
||||
item(fl!("split-horizontal"), Action::PaneSplitHorizontal),
|
||||
item(fl!("split-vertical"), Action::PaneSplitVertical),
|
||||
item(fl!("pane-toggle-maximize"), Action::PaneToggleMaximized),
|
||||
MenuItem::Divider,
|
||||
MenuItem::CheckBox(
|
||||
fl!("window-header"),
|
||||
None,
|
||||
config.show_headerbar,
|
||||
TabAction(entity, Action::ShowHeaderBar(!config.show_headerbar)),
|
||||
),
|
||||
MenuItem::Divider,
|
||||
)),
|
||||
Element::from(divider::horizontal::light()),
|
||||
Element::from(menu_item(
|
||||
fl!("split-horizontal"),
|
||||
Action::PaneSplitHorizontal,
|
||||
)),
|
||||
Element::from(menu_item(fl!("split-vertical"), Action::PaneSplitVertical)),
|
||||
Element::from(menu_item(
|
||||
fl!("pane-toggle-maximize"),
|
||||
Action::PaneToggleMaximized,
|
||||
)),
|
||||
Element::from(divider::horizontal::light()),
|
||||
Element::from(menu_item(fl!("new-tab"), Action::TabNew)),
|
||||
Element::from(menu_item(fl!("rename-tab"), Action::TabRename)),
|
||||
Element::from(menu_item(fl!("menu-settings"), Action::Settings)),
|
||||
Element::from(menu_item(fl!("menu-about"), Action::About)),
|
||||
];
|
||||
#[cfg(feature = "password_manager")]
|
||||
items.push(item(fl!("menu-password-manager"), Action::PasswordManager));
|
||||
items.push(item(fl!("menu-settings"), Action::Settings));
|
||||
items.push(item(fl!("menu-about"), Action::About));
|
||||
// If we have a link, prepend the link items
|
||||
if link.is_some() {
|
||||
items.insert(0, item(fl!("open-link"), Action::LaunchUrlByMenu));
|
||||
items.insert(1, item(fl!("copy-link"), Action::CopyUrlByMenu));
|
||||
items.insert(2, MenuItem::Divider);
|
||||
{
|
||||
rows.push(Element::from(menu_item(
|
||||
fl!("menu-password-manager"),
|
||||
Action::PasswordManager,
|
||||
)));
|
||||
}
|
||||
rows.push(Element::from(menu_checkbox(
|
||||
fl!("show-headerbar"),
|
||||
config.show_headerbar,
|
||||
Action::ShowHeaderBar(!config.show_headerbar),
|
||||
)));
|
||||
|
||||
let key_binds: HashMap<KeyBind, TabAction> = key_binds
|
||||
.iter()
|
||||
.map(|(key_bind, action)| (key_bind.clone(), TabAction(entity, *action)))
|
||||
.collect();
|
||||
widget::menu::items(&key_binds, items)
|
||||
//If we have a link
|
||||
//prepend the Open Link item
|
||||
if link.is_some() {
|
||||
rows.insert(
|
||||
0,
|
||||
Element::from(menu_item(fl!("open-link"), Action::LaunchUrlByMenu)),
|
||||
);
|
||||
rows.insert(
|
||||
1,
|
||||
Element::from(menu_item(fl!("copy-link"), Action::CopyUrlByMenu)),
|
||||
);
|
||||
rows.insert(2, Element::from(divider::horizontal::light()));
|
||||
}
|
||||
let content = widget::menu::menu_column::MenuColumn::with_children(rows);
|
||||
widget::container(content)
|
||||
.padding(1)
|
||||
//TODO: move style to libcosmic
|
||||
.style(|theme| {
|
||||
let cosmic = theme.cosmic();
|
||||
let component = &cosmic.background(theme.transparent).component;
|
||||
widget::container::Style {
|
||||
icon_color: Some(component.on.into()),
|
||||
text_color: Some(component.on.into()),
|
||||
background: Some(Background::Color(component.base.into())),
|
||||
border: Border {
|
||||
radius: cosmic.radius_s().map(|x| x + 1.0).into(),
|
||||
width: 1.0,
|
||||
color: component.divider.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.width(Length::Fixed(360.0))
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn color_scheme_menu<'a>(
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ pub struct TerminalBox<'a, Message> {
|
|||
show_headerbar: bool,
|
||||
pane_border_radius: Option<Radius>,
|
||||
click_timing: Duration,
|
||||
context_menu_open: bool,
|
||||
context_menu: Option<Point>,
|
||||
on_context_menu: Option<Box<dyn Fn(Option<MenuState>) -> Message + 'a>>,
|
||||
on_mouse_enter: Option<Box<dyn Fn() -> Message + 'a>>,
|
||||
opacity: Option<f32>,
|
||||
|
|
@ -143,7 +143,7 @@ where
|
|||
show_headerbar: true,
|
||||
pane_border_radius: None,
|
||||
click_timing: Duration::from_millis(500),
|
||||
context_menu_open: false,
|
||||
context_menu: None,
|
||||
on_context_menu: None,
|
||||
on_mouse_enter: None,
|
||||
opacity: None,
|
||||
|
|
@ -188,8 +188,8 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
pub fn context_menu_open(mut self, open: bool) -> Self {
|
||||
self.context_menu_open = open;
|
||||
pub fn context_menu(mut self, position: Point) -> Self {
|
||||
self.context_menu = Some(position);
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -253,7 +253,7 @@ where
|
|||
if !state.is_focused {
|
||||
return InputMethod::Disabled;
|
||||
}
|
||||
if self.context_menu_open {
|
||||
if self.context_menu.is_some() {
|
||||
return InputMethod::Disabled;
|
||||
}
|
||||
|
||||
|
|
@ -1392,11 +1392,11 @@ where
|
|||
}
|
||||
// Update context menu state
|
||||
if let Some(on_context_menu) = &self.on_context_menu {
|
||||
match self.context_menu_open {
|
||||
true => {
|
||||
match self.context_menu {
|
||||
Some(_) => {
|
||||
shell.publish(on_context_menu(None));
|
||||
}
|
||||
false => {
|
||||
None => {
|
||||
if *button == Button::Right {
|
||||
let x = p.x - self.padding.left;
|
||||
let y = p.y - self.padding.top;
|
||||
|
|
@ -1414,7 +1414,15 @@ where
|
|||
None,
|
||||
);
|
||||
let link = get_hyperlink(&terminal, location);
|
||||
shell.publish(on_context_menu(Some(MenuState { link })));
|
||||
let abs = cosmic::iced::Point::new(
|
||||
layout.bounds().x + p.x,
|
||||
layout.bounds().y + p.y,
|
||||
);
|
||||
shell.publish(on_context_menu(Some(MenuState {
|
||||
position: Some(abs),
|
||||
local_position: Some(p),
|
||||
link,
|
||||
})));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue