merge: epoch-1.9.0 d'upstream

# Conflicts:
#	Cargo.lock
#	src/main.rs
#	src/menu.rs
This commit is contained in:
Lionel DARNIS 2026-09-24 20:52:15 +02:00
commit 083e09f97e
16 changed files with 565 additions and 1191 deletions

1245
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[package] [package]
name = "cosmic-term" name = "cosmic-term"
version = "1.8.0" version = "1.9.0"
authors = ["Jeremy Soller <jeremy@system76.com>"] authors = ["Jeremy Soller <jeremy@system76.com>"]
edition = "2024" edition = "2024"
license = "GPL-3.0-only" license = "GPL-3.0-only"

6
debian/changelog vendored
View file

@ -1,3 +1,9 @@
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 cosmic-term (1.8.0) noble; urgency=medium
* Epoch 1.8.0 version update * Epoch 1.8.0 version update

View file

@ -1,4 +1,6 @@
tar-ignore=.github tar-ignore=.github
tar-ignore=.vscode tar-ignore=target/.rustc_info.json
tar-ignore=target/CACHEDIR.TAG
tar-ignore=target/debug
tar-ignore=target/release
tar-ignore=vendor tar-ignore=vendor
tar-ignore=target

View file

@ -63,6 +63,7 @@ show-pane-borders = Show pane borders
### Advanced ### Advanced
advanced = Advanced advanced = Advanced
show-headerbar = Show header show-headerbar = Show header
window-header = Window header
show-header-description = Reveal the header from the right-click menu 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 = 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 tab-new-inherit-working-directory-description = Open new tabs and windows in the active tab's working directory
@ -152,7 +153,7 @@ next-tab = Next tab
previous-tab = Previous tab previous-tab = Previous tab
split-horizontal = Split horizontal split-horizontal = Split horizontal
split-vertical = Split vertical split-vertical = Split vertical
pane-toggle-maximize = Toggle maximized pane-toggle-maximize = Maximize or restore pane
menu-color-schemes = Color schemes... menu-color-schemes = Color schemes...
menu-settings = Settings... menu-settings = Settings...
menu-about = About COSMIC Terminal... menu-about = About COSMIC Terminal...

0
i18n/fil/cosmic_term.ftl Normal file
View file

View file

@ -4,7 +4,7 @@ repository = Repositori
support = Dukungan support = Dukungan
color-schemes = Skema warna color-schemes = Skema warna
cosmic-terminal = Terminal COSMIC cosmic-terminal = Terminal COSMIC
rename = Ganti nama rename = Ganti Nama
export = Ekspor export = Ekspor
delete = Hapus delete = Hapus
import = Impor import = Impor

View file

@ -1,9 +1,9 @@
cancel = Sefsex cancel = Semmet
name = Isem name = Isem
delete = kkes delete = kkes
rename = Snifel isem rename = Snifel isem
quit = Tuffɣa quit = Ffeɣ
support = Tallalt support = Tallelt
repository = Asarsay repository = Asarsay
settings = Iɣewwaṛen settings = Iɣewwaṛen
appearance = Timeẓri appearance = Timeẓri
@ -14,7 +14,7 @@ paste = Senteḍ
select-all = Fren akk select-all = Fren akk
view = Wali view = Wali
menu-settings = Iɣewwaṛen… menu-settings = Iɣewwaṛen…
match-desktop = Amṣada d tnarit match-desktop = Semṣada akked tnarit
file = Afaylu file = Afaylu
new-window = Asfaylu amaynut new-window = Asfaylu amaynut
edit = Ẓreg edit = Ẓreg

View file

@ -27,7 +27,7 @@ light = Ronî
advanced = Pêşketî advanced = Pêşketî
support = Piştgirî support = Piştgirî
default-font = Cûrenivîs default-font = Cûrenivîs
paste = Pêve bike paste = Pê ve bike
menu-settings = Sazkarî... menu-settings = Sazkarî...
command-line = Rêza fermanê command-line = Rêza fermanê
view = Dîtin 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-right = Balê bikişîne ser destgeha rastê
focus-pane-up = Balê bikişîne ser destgeha jorîn focus-pane-up = Balê bikişîne ser destgeha jorîn
password-manager = Rêveberê borînpeyvê 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 reset-to-default = Vegere rewşa berdest
shortcut-capture-hint = Kombînasyona kilîtan bitikîne 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î? shortcut-replace-body = { $binding } berê ji bo { $existing } hatiye destnîşankirin. Tu dixwazî bi { $new_action } re biguherînî?

View file

@ -1,7 +1,14 @@
settings = Paramètres settings = Configuracion
appearance = Aparéncia appearance = Aparéncia
theme = Tèma theme = Tèma
match-desktop = Assortir al burèu match-desktop = Assortir al burèu
dark = Fosc dark = Escur
light = Clar light = Clar
file = Fichièr file = Fichièr
name = Nom
repository = Depaus
cancel = Anullar
menu-settings = Configuracion...
quit = Quitar
delete = Suprimir
replace = Remplaçar

View file

@ -1,5 +1,5 @@
cosmic-terminal = Terminal COSMIC cosmic-terminal = Terminal COSMIC
comment = Emulator terminala dla pulpitu COSMIC comment = Emulator terminala dla środowiska COSMIC
keywords = Cli;WierszPoleceń;Powłoka;Terminal; keywords = Cli;WierszPoleceń;Powłoka;Terminal;
new-terminal = Nowy terminal new-terminal = Nowy terminal
@ -73,7 +73,7 @@ advanced = Zaawansowane
show-headerbar = Pokaż pasek nagłówka show-headerbar = Pokaż pasek nagłówka
show-header-description = Ukazuje pasek nagłówka z menu wybieranego prawym przyciskiem myszki show-header-description = Ukazuje pasek nagłówka z menu wybieranego prawym przyciskiem myszki
# Find # Find
find-placeholder = Szukaj… find-placeholder = Wyszukaj…
find-previous = Znajdź poprzedni find-previous = Znajdź poprzedni
find-next = Znajdź następny find-next = Znajdź następny
@ -96,7 +96,7 @@ edit = Edytuj
copy = Kopiuj copy = Kopiuj
paste = Wklej paste = Wklej
select-all = Zaznacz wszystko select-all = Zaznacz wszystko
find = Szukaj find = Wyszukaj
clear-scrollback = Wyczyść przewijanie clear-scrollback = Wyczyść przewijanie
## View ## View

View file

@ -39,8 +39,8 @@ settings = การตั้งค่า
appearance = ลักษณะ appearance = ลักษณะ
theme = ธีม theme = ธีม
match-desktop = ใช้ตามธีมหน้าจอหลัก match-desktop = ใช้ตามธีมหน้าจอหลัก
dark = ธีมมืด dark = มืด
light = ธีมสว่าง light = สว่าง
syntax-dark = โทนสีมืด syntax-dark = โทนสีมืด
syntax-light = โทนสีสว่าง syntax-light = โทนสีสว่าง
default-zoom-step = ระยะซูม default-zoom-step = ระยะซูม
@ -109,3 +109,8 @@ pane-toggle-maximize = ขยายให้ใหญ่สุด
menu-color-schemes = โทนสี... menu-color-schemes = โทนสี...
menu-settings = การตั้งค่า... menu-settings = การตั้งค่า...
menu-about = เกี่ยวกับ เทอร์มินัล COSMIC... menu-about = เกี่ยวกับ เทอร์มินัล COSMIC...
cancel = ยกเลิก
reset-to-default = รีเซ็ตเป็นค่าเริ่มต้น
type-to-search = พิมพ์เพื่อค้นหา
add-another-keybinding = เพิ่มคีย์ลัดเพิ่มเติม
replace = แทนที่

View file

@ -10,7 +10,7 @@ new-terminal = 新建終端機
## Color schemes ## Color schemes
color-schemes = 配色方案 color-schemes = 配色方案
rename = 重命名 rename = 重新命名
export = 匯出 export = 匯出
delete = 刪除 delete = 刪除
import = 匯入 import = 匯入

View file

@ -566,7 +566,7 @@ pub enum Message {
ReorderTab(Pane, ReorderEvent), ReorderTab(Pane, ReorderEvent),
SaveOutput(Option<segmented_button::Entity>), SaveOutput(Option<segmented_button::Entity>),
SaveOutputResult(pane_grid::Pane, segmented_button::Entity, DialogResult), SaveOutputResult(pane_grid::Pane, segmented_button::Entity, DialogResult),
Surface(surface::Action), Surface(surface::Action<Message>),
SelectAll(Option<segmented_button::Entity>), SelectAll(Option<segmented_button::Entity>),
ShowAdvancedFontSettings(bool), ShowAdvancedFontSettings(bool),
ShowHeaderBar(bool), ShowHeaderBar(bool),
@ -604,7 +604,6 @@ pub enum Message {
ZoomIn, ZoomIn,
ZoomOut, ZoomOut,
ZoomReset, ZoomReset,
ContextMenuPopupClosed(window::Id),
} }
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
@ -682,14 +681,6 @@ pub struct App {
shortcut_search_regex: Option<regex::Regex>, shortcut_search_regex: Option<regex::Regex>,
shortcut_search_value: String, shortcut_search_value: String,
modifiers: Modifiers, 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")] #[cfg(feature = "password_manager")]
password_mgr: password_manager::PasswordManager, password_mgr: password_manager::PasswordManager,
} }
@ -2213,7 +2204,6 @@ impl Application for App {
shortcut_search_regex: None, shortcut_search_regex: None,
shortcut_search_value: String::new(), shortcut_search_value: String::new(),
modifiers: Modifiers::empty(), modifiers: Modifiers::empty(),
context_menu_popup: None,
#[cfg(feature = "password_manager")] #[cfg(feature = "password_manager")]
password_mgr: Default::default(), password_mgr: Default::default(),
}; };
@ -3433,16 +3423,7 @@ impl Application for App {
return self.update_title(None); return self.update_title(None);
} }
Message::TabContextAction(entity, action) => { Message::TabContextAction(entity, action) => {
// Close context menu popup
let mut tasks = Vec::new(); 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 // Close terminal context menu state
if let Some(tab_model) = self.pane_model.active() if let Some(tab_model) = self.pane_model.active()
&& let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) && let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity)
@ -3451,11 +3432,8 @@ impl Application for App {
//Some actions need the menu_state, //Some actions need the menu_state,
//so only clear the position for them. //so only clear the position for them.
match action { match action {
Action::LaunchUrlByMenu | Action::CopyUrlByMenu => { // these read the link from the menu state and clear it themselves
if let Some(context_menu) = terminal.context_menu.as_mut() { Action::LaunchUrlByMenu | Action::CopyUrlByMenu => {}
context_menu.position = None;
}
}
_ => { _ => {
terminal.context_menu = None; terminal.context_menu = None;
} }
@ -3465,94 +3443,29 @@ impl Application for App {
return cosmic::Task::batch(tasks); return cosmic::Task::batch(tasks);
} }
Message::TabContextMenu(pane, menu_state) => { 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 // Clear all terminal context_menu state
for (_, tab_model) in self.pane_model.panes.iter() { for (_, tab_model) in self.pane_model.panes.iter() {
for entity in tab_model.iter() { for entity in tab_model.iter() {
if let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) { if let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) {
let mut terminal = terminal.lock().unwrap(); let mut terminal = terminal.lock().unwrap();
terminal.context_menu = None; if terminal.context_menu.take().is_some() {
terminal.active_regex_match = None;
terminal.needs_update = true;
}
} }
} }
} }
if let Some(menu_state) = menu_state { // A right press records what is under the cursor
if let Some(_position) = menu_state.position { if let Some(menu_state) = menu_state
let local_position = menu_state.local_position.unwrap_or(_position); && let Some(tab_model) = self.pane_model.panes.get(pane)
if let Some(tab_model) = self.pane_model.panes.get(pane) { {
let entity = tab_model.active(); let entity = tab_model.active();
let link = menu_state.link.clone(); if let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) {
let popup_id = window::Id::unique(); terminal.lock().unwrap().context_menu = Some(menu_state);
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); self.pane_model.set_focus(pane);
} }
return cosmic::Task::batch(tasks);
} }
Message::TabNew => { Message::TabNew => {
// The profile from `--profile` only applies to the first tab // The profile from `--profile` only applies to the first tab
@ -3944,26 +3857,8 @@ impl Application for App {
self.reset_active_pane_zoom(); self.reset_active_pane_zoom();
return self.update_config(); 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) => { Message::Surface(a) => {
return cosmic::task::message(cosmic::Action::Cosmic( return cosmic::task::message(cosmic::Action::Surface(a));
cosmic::app::Action::Surface(a),
));
} }
Message::ReorderTab( Message::ReorderTab(
pane, pane,
@ -4112,32 +4007,7 @@ 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> { 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 { match &self.dialog_opt {
Some(dialog) => dialog.view(window_id), Some(dialog) => dialog.view(window_id),
None => widget::text("Unknown window ID").into(), None => widget::text("Unknown window ID").into(),
@ -4226,51 +4096,36 @@ impl Application for App {
terminal_box = terminal_box.on_mouse_enter(move || Message::MouseEnter(pane)); terminal_box = terminal_box.on_mouse_enter(move || Message::MouseEnter(pane));
} }
// If a context menu popup is active for this pane, inform the // The terminal records what was under the right press; the widget opens the
// terminal_box so it will emit on_context_menu(None) on click // menu on release, so build it from that state
// to dismiss the popup. let context_link = terminal
if self.context_menu_popup.is_some() { .lock()
terminal_box = terminal_box.context_menu(cosmic::iced::Point::ORIGIN); .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);
} }
let mut context_menu = widget::context_menu(
let use_wayland_popup = { terminal_box,
#[cfg(feature = "wayland")] context_link.map(|link| {
{ menu::context_menu(
is_wayland() &self.config,
} &self.key_binds,
#[cfg(not(feature = "wayland"))] entity,
{ link,
false self.terminal_monitors(pane, entity),
} )
}; }),
)
let tab_element: Element<'_, Message> = if !use_wayland_popup { .item_width(widget::menu::ItemWidth::Uniform(360))
// Fallback: render context menu as an inline popover .on_close(Message::TabContextMenu(pane, None))
if let Some((_, popup_pane, popup_entity, ref link, _, point)) = .on_surface_action(Message::Surface);
self.context_menu_popup if let Some(window_id) = self.core.main_window_id() {
{ context_menu = context_menu.window_id(window_id);
if pane == popup_pane { }
let mut popover = widget::popover(terminal_box.context_menu(point)); tab_column = tab_column.push(context_menu);
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 //Only draw find in the currently focused pane
@ -4451,14 +4306,6 @@ 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. /// Divider color painted behind the pane grid to form pane borders.
/// ///
/// When blur is active the transparent container must be used, otherwise the /// When blur is active the transparent container must be used, otherwise the

View file

@ -1,17 +1,15 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use cosmic::iced::Point; use cosmic::widget::menu::action::MenuAction;
use cosmic::widget::menu::key_bind::KeyBind; use cosmic::widget::menu::key_bind::KeyBind;
use cosmic::widget::menu::{Item as MenuItem, menu_button}; use cosmic::widget::menu::{Item as MenuItem, menu_button};
use cosmic::widget::space;
use cosmic::{ use cosmic::{
Element, Element,
app::Core, app::Core,
iced::core::Border, iced::core::Border,
iced::{Background, Length, advanced::widget::text::Style as TextStyle}, iced::{Background, Length},
theme,
widget::{ widget::{
self, divider, self,
menu::{ItemHeight, ItemWidth}, menu::{ItemHeight, ItemWidth},
responsive_menu_bar, segmented_button, responsive_menu_bar, segmented_button,
}, },
@ -23,147 +21,92 @@ use crate::{Action, ColorSchemeId, ColorSchemeKind, Config, Message, fl, termina
static MENU_ID: LazyLock<cosmic::widget::Id> = static MENU_ID: LazyLock<cosmic::widget::Id> =
LazyLock::new(|| cosmic::widget::Id::new("responsive-menu")); LazyLock::new(|| cosmic::widget::Id::new("responsive-menu"));
/// What the terminal found under the right-click
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MenuState { pub struct MenuState {
pub position: Option<Point>,
pub local_position: Option<Point>,
pub link: Option<String>, pub link: Option<String>,
} }
pub fn context_menu<'a>( /// 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(
config: &Config, config: &Config,
key_binds: &HashMap<KeyBind, Action>, key_binds: &HashMap<KeyBind, Action>,
entity: segmented_button::Entity, entity: segmented_button::Entity,
link: Option<String>, link: Option<String>,
monitors: (bool, bool, bool), monitors: (bool, bool, bool),
) -> Element<'a, Message> { ) -> Vec<widget::menu::Tree<Message>> {
let find_key = |action: &Action| -> String { let item =
for (key_bind, key_action) in key_binds { |label: String, action: Action| MenuItem::Button(label, None, TabAction(entity, action));
if action == key_action { let check = |label: String, value: bool, action: Action| {
return key_bind.to_string(); MenuItem::CheckBox(label, None, value, TabAction(entity, action))
}
}
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 menu_checkbox = |label, value, action| { let mut items = vec![
menu_button(vec![ item(fl!("new-tab"), Action::TabNew),
widget::text(label).into(), item(fl!("rename-tab"), Action::TabRename),
widget::space::horizontal().into(), MenuItem::Divider,
widget::toggler(value) item(fl!("copy"), Action::Copy),
.on_toggle(move |_| Message::TabContextAction(entity, action)) item(fl!("paste"), Action::Paste),
.size(16.0) item(fl!("select-all"), Action::SelectAll),
.into(), MenuItem::Divider,
]) item(fl!("clear-scrollback"), Action::ClearScrollback),
.on_press(Message::TabContextAction(entity, action)) item(fl!("save-output"), Action::SaveOutput),
}; MenuItem::Divider,
check(
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"), fl!("monitor-activity"),
monitors.0, monitors.0,
Action::Monitor(MonitorKind::Activity), Action::Monitor(MonitorKind::Activity),
)), ),
Element::from(menu_checkbox( check(
fl!("monitor-silence"), fl!("monitor-silence"),
monitors.1, monitors.1,
Action::Monitor(MonitorKind::Silence), Action::Monitor(MonitorKind::Silence),
)), ),
Element::from(menu_checkbox( check(
fl!("monitor-process-finished"), fl!("monitor-process-finished"),
monitors.2, monitors.2,
Action::Monitor(MonitorKind::ProcessFinished), Action::Monitor(MonitorKind::ProcessFinished),
)), ),
Element::from(divider::horizontal::light()), MenuItem::Divider,
Element::from(menu_item( item(fl!("split-horizontal"), Action::PaneSplitHorizontal),
fl!("split-horizontal"), item(fl!("split-vertical"), Action::PaneSplitVertical),
Action::PaneSplitHorizontal, item(fl!("pane-toggle-maximize"), Action::PaneToggleMaximized),
)), MenuItem::Divider,
Element::from(menu_item(fl!("split-vertical"), Action::PaneSplitVertical)), MenuItem::CheckBox(
Element::from(menu_item( fl!("window-header"),
fl!("pane-toggle-maximize"), None,
Action::PaneToggleMaximized, config.show_headerbar,
)), TabAction(entity, Action::ShowHeaderBar(!config.show_headerbar)),
Element::from(divider::horizontal::light()), ),
Element::from(menu_item(fl!("new-tab"), Action::TabNew)), MenuItem::Divider,
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")] #[cfg(feature = "password_manager")]
{ items.push(item(fl!("menu-password-manager"), Action::PasswordManager));
rows.push(Element::from(menu_item( items.push(item(fl!("menu-settings"), Action::Settings));
fl!("menu-password-manager"), items.push(item(fl!("menu-about"), Action::About));
Action::PasswordManager, // If we have a link, prepend the link items
)));
}
rows.push(Element::from(menu_checkbox(
fl!("show-headerbar"),
config.show_headerbar,
Action::ShowHeaderBar(!config.show_headerbar),
)));
//If we have a link
//prepend the Open Link item
if link.is_some() { if link.is_some() {
rows.insert( items.insert(0, item(fl!("open-link"), Action::LaunchUrlByMenu));
0, items.insert(1, item(fl!("copy-link"), Action::CopyUrlByMenu));
Element::from(menu_item(fl!("open-link"), Action::LaunchUrlByMenu)), items.insert(2, MenuItem::Divider);
);
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) let key_binds: HashMap<KeyBind, TabAction> = key_binds
.padding(1) .iter()
//TODO: move style to libcosmic .map(|(key_bind, action)| (key_bind.clone(), TabAction(entity, *action)))
.style(|theme| { .collect();
let cosmic = theme.cosmic(); widget::menu::items(&key_binds, items)
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>( pub fn color_scheme_menu<'a>(

View file

@ -116,7 +116,7 @@ pub struct TerminalBox<'a, Message> {
show_headerbar: bool, show_headerbar: bool,
pane_border_radius: Option<Radius>, pane_border_radius: Option<Radius>,
click_timing: Duration, click_timing: Duration,
context_menu: Option<Point>, context_menu_open: bool,
on_context_menu: Option<Box<dyn Fn(Option<MenuState>) -> Message + 'a>>, on_context_menu: Option<Box<dyn Fn(Option<MenuState>) -> Message + 'a>>,
on_mouse_enter: Option<Box<dyn Fn() -> Message + 'a>>, on_mouse_enter: Option<Box<dyn Fn() -> Message + 'a>>,
opacity: Option<f32>, opacity: Option<f32>,
@ -143,7 +143,7 @@ where
show_headerbar: true, show_headerbar: true,
pane_border_radius: None, pane_border_radius: None,
click_timing: Duration::from_millis(500), click_timing: Duration::from_millis(500),
context_menu: None, context_menu_open: false,
on_context_menu: None, on_context_menu: None,
on_mouse_enter: None, on_mouse_enter: None,
opacity: None, opacity: None,
@ -188,8 +188,8 @@ where
self self
} }
pub fn context_menu(mut self, position: Point) -> Self { pub fn context_menu_open(mut self, open: bool) -> Self {
self.context_menu = Some(position); self.context_menu_open = open;
self self
} }
@ -253,7 +253,7 @@ where
if !state.is_focused { if !state.is_focused {
return InputMethod::Disabled; return InputMethod::Disabled;
} }
if self.context_menu.is_some() { if self.context_menu_open {
return InputMethod::Disabled; return InputMethod::Disabled;
} }
@ -1392,11 +1392,11 @@ where
} }
// Update context menu state // Update context menu state
if let Some(on_context_menu) = &self.on_context_menu { if let Some(on_context_menu) = &self.on_context_menu {
match self.context_menu { match self.context_menu_open {
Some(_) => { true => {
shell.publish(on_context_menu(None)); shell.publish(on_context_menu(None));
} }
None => { false => {
if *button == Button::Right { if *button == Button::Right {
let x = p.x - self.padding.left; let x = p.x - self.padding.left;
let y = p.y - self.padding.top; let y = p.y - self.padding.top;
@ -1414,15 +1414,7 @@ where
None, None,
); );
let link = get_hyperlink(&terminal, location); let link = get_hyperlink(&terminal, location);
let abs = cosmic::iced::Point::new( shell.publish(on_context_menu(Some(MenuState { link })));
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,
})));
} }
} }
} }