Compare commits
20 commits
post-1.6-m
...
local/yoda
| Author | SHA1 | Date | |
|---|---|---|---|
| 81dc7d3eeb | |||
| 083e09f97e | |||
|
|
9129277bed | ||
|
|
78eaba9db1 | ||
|
|
d25c2108c0 | ||
|
|
120b4fdb12 | ||
|
|
4cd8f8d605 | ||
|
|
1d9e5784d2 | ||
|
|
327febcc92 | ||
| 99842bde03 | |||
| 5fc081e257 | |||
| b8fce5a272 | |||
|
|
4f0f0b6e4e | ||
|
|
7945b9ffc6 | ||
| 41203ab5f1 | |||
| f3f87e59da | |||
|
|
909a0d31aa | ||
|
|
7df9265243 | ||
|
|
dfa59f3ca4 | ||
| 78fb2be076 |
19 changed files with 1185 additions and 941 deletions
1424
Cargo.lock
generated
1424
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "cosmic-term"
|
||||
version = "1.6.0"
|
||||
version = "1.9.0"
|
||||
authors = ["Jeremy Soller <jeremy@system76.com>"]
|
||||
edition = "2024"
|
||||
license = "GPL-3.0-only"
|
||||
|
|
|
|||
18
debian/changelog
vendored
18
debian/changelog
vendored
|
|
@ -1,3 +1,21 @@
|
|||
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
|
||||
|
||||
-- Jeremy Soller <jeremy@system76.com> Wed, 02 Sep 2026 08:15:55 -0600
|
||||
|
||||
cosmic-term (1.7.0) noble; urgency=medium
|
||||
|
||||
* Epoch 1.7.0 version update
|
||||
|
||||
-- Jeremy Soller <jeremy@system76.com> Tue, 25 Aug 2026 09:21:20 -0600
|
||||
|
||||
cosmic-term (1.6.0) noble; urgency=medium
|
||||
|
||||
* Epoch 1.6.0 version update
|
||||
|
|
|
|||
6
debian/source/options
vendored
6
debian/source/options
vendored
|
|
@ -1,4 +1,6 @@
|
|||
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=target
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
rename = Omdøb
|
||||
delete = Slet
|
||||
name = Navn
|
||||
cancel = Annuller
|
||||
cancel = Afbryd
|
||||
quit = Afslut
|
||||
find-next = Find næste
|
||||
select-all = Vælg alle
|
||||
|
|
|
|||
|
|
@ -152,3 +152,4 @@ shortcut-group-zoom = Ζουμ
|
|||
shortcut-replace-body = Ο συνδυασμός { $binding } έχει ήδη αντιστοιχιστεί στην ενέργεια «{ $existing }». Θέλετε να αντικατασταθεί με την ενέργεια «{ $new_action }»;
|
||||
tab-new-inherit-working-directory = Οι νέες καρτέλες και παράθυρα χρησιμοποιούν τον τρέχοντα κατάλογο
|
||||
tab-new-inherit-working-directory-description = Άνοιγμα νέων καρτελών και παραθύρων στον τρέχοντα κατάλογο της ενεργής καρτέλας
|
||||
show-pane-borders = Εμφάνιση περιγραμμάτων πλαισίων
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ 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
|
||||
|
|
@ -152,7 +153,7 @@ next-tab = Next tab
|
|||
previous-tab = Previous tab
|
||||
split-horizontal = Split horizontal
|
||||
split-vertical = Split vertical
|
||||
pane-toggle-maximize = Toggle maximized
|
||||
pane-toggle-maximize = Maximize or restore pane
|
||||
menu-color-schemes = Color schemes...
|
||||
menu-settings = Settings...
|
||||
menu-about = About COSMIC Terminal...
|
||||
|
|
|
|||
0
i18n/fil/cosmic_term.ftl
Normal file
0
i18n/fil/cosmic_term.ftl
Normal file
|
|
@ -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 = Sefsex
|
||||
cancel = Semmet
|
||||
name = Isem
|
||||
delete = kkes
|
||||
rename = Snifel isem
|
||||
quit = Tuffɣa
|
||||
support = Tallalt
|
||||
quit = Ffeɣ
|
||||
support = Tallelt
|
||||
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 = Amṣada d tnarit
|
||||
match-desktop = Semṣada akked 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,7 +1,14 @@
|
|||
settings = Paramètres
|
||||
settings = Configuracion
|
||||
appearance = Aparéncia
|
||||
theme = Tèma
|
||||
match-desktop = Assortir al burèu
|
||||
dark = Fosc
|
||||
dark = Escur
|
||||
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 pulpitu COSMIC
|
||||
comment = Emulator terminala dla środowiska 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 = Szukaj…
|
||||
find-placeholder = Wyszukaj…
|
||||
find-previous = Znajdź poprzedni
|
||||
find-next = Znajdź następny
|
||||
|
||||
|
|
@ -96,7 +96,7 @@ edit = Edytuj
|
|||
copy = Kopiuj
|
||||
paste = Wklej
|
||||
select-all = Zaznacz wszystko
|
||||
find = Szukaj
|
||||
find = Wyszukaj
|
||||
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,3 +109,8 @@ 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),
|
||||
Surface(surface::Action<Message>),
|
||||
SelectAll(Option<segmented_button::Entity>),
|
||||
ShowAdvancedFontSettings(bool),
|
||||
ShowHeaderBar(bool),
|
||||
|
|
@ -604,7 +604,6 @@ pub enum Message {
|
|||
ZoomIn,
|
||||
ZoomOut,
|
||||
ZoomReset,
|
||||
ContextMenuPopupClosed(window::Id),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
|
|
@ -682,14 +681,6 @@ 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,
|
||||
}
|
||||
|
|
@ -2213,7 +2204,6 @@ 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(),
|
||||
};
|
||||
|
|
@ -3433,16 +3423,7 @@ 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)
|
||||
|
|
@ -3451,11 +3432,8 @@ impl Application for App {
|
|||
//Some actions need the menu_state,
|
||||
//so only clear the position for them.
|
||||
match action {
|
||||
Action::LaunchUrlByMenu | Action::CopyUrlByMenu => {
|
||||
if let Some(context_menu) = terminal.context_menu.as_mut() {
|
||||
context_menu.position = None;
|
||||
}
|
||||
}
|
||||
// these read the link from the menu state and clear it themselves
|
||||
Action::LaunchUrlByMenu | Action::CopyUrlByMenu => {}
|
||||
_ => {
|
||||
terminal.context_menu = None;
|
||||
}
|
||||
|
|
@ -3465,94 +3443,29 @@ 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();
|
||||
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 {
|
||||
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),
|
||||
)));
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
self.pane_model.set_focus(pane);
|
||||
}
|
||||
|
||||
return cosmic::Task::batch(tasks);
|
||||
}
|
||||
Message::TabNew => {
|
||||
// The profile from `--profile` only applies to the first tab
|
||||
|
|
@ -3944,26 +3857,8 @@ 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::Cosmic(
|
||||
cosmic::app::Action::Surface(a),
|
||||
));
|
||||
return cosmic::task::message(cosmic::Action::Surface(a));
|
||||
}
|
||||
Message::ReorderTab(
|
||||
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> {
|
||||
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(),
|
||||
|
|
@ -4226,51 +4096,36 @@ impl Application for App {
|
|||
terminal_box = terminal_box.on_mouse_enter(move || Message::MouseEnter(pane));
|
||||
}
|
||||
|
||||
// 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);
|
||||
// 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);
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
//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.
|
||||
///
|
||||
/// When blur is active the transparent container must be used, otherwise the
|
||||
|
|
|
|||
185
src/menu.rs
185
src/menu.rs
|
|
@ -1,17 +1,15 @@
|
|||
// 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::{Item as MenuItem, menu_button};
|
||||
use cosmic::widget::space;
|
||||
use cosmic::{
|
||||
Element,
|
||||
app::Core,
|
||||
iced::core::Border,
|
||||
iced::{Background, Length, advanced::widget::text::Style as TextStyle},
|
||||
theme,
|
||||
iced::{Background, Length},
|
||||
widget::{
|
||||
self, divider,
|
||||
self,
|
||||
menu::{ItemHeight, ItemWidth},
|
||||
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> =
|
||||
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>,
|
||||
}
|
||||
|
||||
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,
|
||||
key_binds: &HashMap<KeyBind, Action>,
|
||||
entity: segmented_button::Entity,
|
||||
link: Option<String>,
|
||||
monitors: (bool, bool, bool),
|
||||
) -> 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))
|
||||
) -> 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))
|
||||
};
|
||||
|
||||
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(
|
||||
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(
|
||||
fl!("monitor-activity"),
|
||||
monitors.0,
|
||||
Action::Monitor(MonitorKind::Activity),
|
||||
)),
|
||||
Element::from(menu_checkbox(
|
||||
),
|
||||
check(
|
||||
fl!("monitor-silence"),
|
||||
monitors.1,
|
||||
Action::Monitor(MonitorKind::Silence),
|
||||
)),
|
||||
Element::from(menu_checkbox(
|
||||
),
|
||||
check(
|
||||
fl!("monitor-process-finished"),
|
||||
monitors.2,
|
||||
Action::Monitor(MonitorKind::ProcessFinished),
|
||||
)),
|
||||
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)),
|
||||
),
|
||||
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,
|
||||
];
|
||||
#[cfg(feature = "password_manager")]
|
||||
{
|
||||
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),
|
||||
)));
|
||||
|
||||
//If we have a link
|
||||
//prepend the Open Link item
|
||||
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() {
|
||||
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()));
|
||||
items.insert(0, item(fl!("open-link"), Action::LaunchUrlByMenu));
|
||||
items.insert(1, item(fl!("copy-link"), Action::CopyUrlByMenu));
|
||||
items.insert(2, MenuItem::Divider);
|
||||
}
|
||||
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()
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
pub fn color_scheme_menu<'a>(
|
||||
|
|
|
|||
135
src/terminal.rs
135
src/terminal.rs
|
|
@ -218,11 +218,17 @@ impl TerminalPaneGrid {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct Metadata {
|
||||
pub bg: cosmic_text::Color,
|
||||
pub underline_color: cosmic_text::Color,
|
||||
pub flags: Flags,
|
||||
/// Remplissage natif (élément de bloc Unicode) — voir [`BlockFill`].
|
||||
pub block: Option<BlockFill>,
|
||||
/// Couleur du remplissage natif. Hors bloc, sans effet ; participant à
|
||||
/// l'égalité pour que deux blocs adjacents de couleurs différentes ne
|
||||
/// fusionnent pas en un seul quad.
|
||||
pub block_color: cosmic_text::Color,
|
||||
}
|
||||
|
||||
impl Metadata {
|
||||
|
|
@ -232,6 +238,8 @@ impl Metadata {
|
|||
bg,
|
||||
underline_color,
|
||||
flags,
|
||||
block: None,
|
||||
block_color: underline_color,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -245,6 +253,73 @@ impl Metadata {
|
|||
fn with_flags(self, flags: Flags) -> Self {
|
||||
Self { flags, ..self }
|
||||
}
|
||||
|
||||
fn with_block(mut self, block: Option<BlockFill>, color: cosmic_text::Color) -> Self {
|
||||
self.block = block;
|
||||
self.block_color = color;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Élément de bloc Unicode (U+2580–U+259F) rendu nativement.
|
||||
///
|
||||
/// Ces caractères ne passent PAS par le glyphe de la police : le `█` d'une
|
||||
/// police couvre à peine l'em (≈ 1× la taille de police) alors que la cellule
|
||||
/// fait `line_height` (≈ 1,4×) — d'où des trous entre les rangées et un rendu
|
||||
/// crénelé. À la place, chaque bloc est remplacé par une espace (le glyphe
|
||||
/// disparaît) et peint en quad GPU couvrant exactement la fraction voulue de
|
||||
/// la cellule : bords alignés sur la grille, jointif par construction, net à
|
||||
/// toute échelle. C'est l'approche de kitty/WezTerm.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum BlockFill {
|
||||
/// Un ou deux rectangles en huitièmes de cellule : `(x0, x1, y0, y1)`,
|
||||
/// abscisse et ordonnée depuis le coin haut-gauche. Un rectangle vide
|
||||
/// (`x0 >= x1` ou `y0 >= y1`) est ignoré au tracé — deux rectangles
|
||||
/// suffisent pour toute la famille des quadrants.
|
||||
Rects([(u8, u8, u8, u8); 2]),
|
||||
/// Trame (`░▒▓`) : rectangle plein, alpha en quarts (1–3).
|
||||
Shade(u8),
|
||||
}
|
||||
|
||||
/// Mappe un caractère vers son remplissage natif, s'il est un élément de bloc.
|
||||
pub fn block_fill(c: char) -> Option<BlockFill> {
|
||||
use BlockFill::{Rects, Shade};
|
||||
const NONE: (u8, u8, u8, u8) = (8, 8, 8, 8);
|
||||
match c {
|
||||
'▀' => Some(Rects([(0, 8, 0, 4), NONE])),
|
||||
'▁' => Some(Rects([(0, 8, 7, 8), NONE])),
|
||||
'▂' => Some(Rects([(0, 8, 6, 8), NONE])),
|
||||
'▃' => Some(Rects([(0, 8, 5, 8), NONE])),
|
||||
'▄' => Some(Rects([(0, 8, 4, 8), NONE])),
|
||||
'▅' => Some(Rects([(0, 8, 3, 8), NONE])),
|
||||
'▆' => Some(Rects([(0, 8, 2, 8), NONE])),
|
||||
'▇' => Some(Rects([(0, 8, 1, 8), NONE])),
|
||||
'█' => Some(Rects([(0, 8, 0, 8), NONE])),
|
||||
'▉' => Some(Rects([(0, 7, 0, 8), NONE])),
|
||||
'▊' => Some(Rects([(0, 6, 0, 8), NONE])),
|
||||
'▋' => Some(Rects([(0, 5, 0, 8), NONE])),
|
||||
'▌' => Some(Rects([(0, 4, 0, 8), NONE])),
|
||||
'▍' => Some(Rects([(0, 3, 0, 8), NONE])),
|
||||
'▎' => Some(Rects([(0, 2, 0, 8), NONE])),
|
||||
'▏' => Some(Rects([(0, 1, 0, 8), NONE])),
|
||||
'▐' => Some(Rects([(4, 8, 0, 8), NONE])),
|
||||
'░' => Some(Shade(1)),
|
||||
'▒' => Some(Shade(2)),
|
||||
'▓' => Some(Shade(3)),
|
||||
'▔' => Some(Rects([(0, 8, 0, 1), NONE])),
|
||||
'▕' => Some(Rects([(7, 8, 0, 8), NONE])),
|
||||
'▖' => Some(Rects([(0, 4, 4, 8), NONE])),
|
||||
'▗' => Some(Rects([(4, 8, 4, 8), NONE])),
|
||||
'▘' => Some(Rects([(0, 4, 0, 4), NONE])),
|
||||
'▝' => Some(Rects([(4, 8, 0, 4), NONE])),
|
||||
'▙' => Some(Rects([(0, 4, 0, 8), (4, 8, 4, 8)])),
|
||||
'▚' => Some(Rects([(0, 4, 0, 4), (4, 8, 4, 8)])),
|
||||
'▛' => Some(Rects([(0, 8, 0, 4), (0, 4, 4, 8)])),
|
||||
'▜' => Some(Rects([(0, 8, 0, 4), (4, 8, 4, 8)])),
|
||||
'▞' => Some(Rects([(4, 8, 0, 4), (0, 4, 4, 8)])),
|
||||
'▟' => Some(Rects([(0, 8, 4, 8), (4, 8, 0, 4)])),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Terminal {
|
||||
|
|
@ -905,9 +980,14 @@ impl Terminal {
|
|||
}
|
||||
|
||||
let start = text.len();
|
||||
// Tab skip/stop is handled by alacritty_terminal
|
||||
// Tab skip/stop is handled by alacritty_terminal.
|
||||
// Les éléments de bloc ne deviennent pas des glyphes : la
|
||||
// police ne couvre pas la cellule (trous d'interligne) ;
|
||||
// ils sont peints en quads natifs au tracé, via Metadata.
|
||||
let block = block_fill(indexed.cell.c);
|
||||
text.push(match indexed.cell.c {
|
||||
'\t' => ' ',
|
||||
c if block.is_some() => ' ',
|
||||
c => c,
|
||||
});
|
||||
if let Some(zerowidth) = indexed.cell.zerowidth() {
|
||||
|
|
@ -1025,6 +1105,7 @@ impl Terminal {
|
|||
}
|
||||
|
||||
let metadata = Metadata::new(bg, fg)
|
||||
.with_block(block, fg)
|
||||
.with_flags(flags)
|
||||
.with_underline_color(underline_color);
|
||||
let (meta_idx, _) = self.metadata_set.insert_full(metadata);
|
||||
|
|
@ -1344,3 +1425,53 @@ impl Drop for Terminal {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn block_fill_couvre_toute_la_plage_unicode() {
|
||||
// U+2580–U+259F : chaque élément de bloc a sa géométrie native.
|
||||
for code in 0x2580..=0x259F {
|
||||
let c = char::from_u32(code).unwrap();
|
||||
assert!(
|
||||
block_fill(c).is_some(),
|
||||
"{c} (U+{code:04X}) sans remplissage"
|
||||
);
|
||||
}
|
||||
// Hors de la plage : filets, espaces, lettres — pas de remplissage.
|
||||
for c in [' ', '─', '│', 'M', '●', '\u{25A0}'] {
|
||||
assert!(
|
||||
block_fill(c).is_none(),
|
||||
"{c} ne doit pas être un bloc natif"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_fill_geometry_toujours_valide() {
|
||||
// Fractions croissantes, bornées à la cellule — sinon le quad
|
||||
// déborderait sur la cellule voisine ou serait rejeté au tracé.
|
||||
for c in (char::from_u32(0x2580).unwrap()..=char::from_u32(0x259F).unwrap())
|
||||
.chain(['░', '▒', '▓'].iter().copied())
|
||||
{
|
||||
let Some(fill) = block_fill(c) else { continue };
|
||||
match fill {
|
||||
BlockFill::Rects(rects) => {
|
||||
for (x0, x1, y0, y1) in rects {
|
||||
// Rectangle vide (sentinelle) ou borné à la cellule.
|
||||
let empty = x0 >= x1 || y0 >= y1;
|
||||
assert!(
|
||||
empty || (x1 <= 8 && y1 <= 8),
|
||||
"{c}: géométrie déborde la cellule"
|
||||
);
|
||||
}
|
||||
}
|
||||
BlockFill::Shade(quarters) => {
|
||||
assert!((1..=3).contains(&quarters), "{c}: alpha de trame invalide")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,11 @@ use std::{
|
|||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use crate::{Action, Terminal, TerminalScroll, menu::MenuState, terminal::Metadata};
|
||||
use crate::{
|
||||
Action, Terminal, TerminalScroll,
|
||||
menu::MenuState,
|
||||
terminal::{BlockFill, Metadata},
|
||||
};
|
||||
|
||||
const AUTOSCROLL_INTERVAL: Duration = Duration::from_millis(100);
|
||||
|
||||
|
|
@ -112,7 +116,7 @@ pub struct TerminalBox<'a, Message> {
|
|||
show_headerbar: bool,
|
||||
pane_border_radius: Option<Radius>,
|
||||
click_timing: Duration,
|
||||
context_menu: Option<Point>,
|
||||
context_menu_open: bool,
|
||||
on_context_menu: Option<Box<dyn Fn(Option<MenuState>) -> Message + 'a>>,
|
||||
on_mouse_enter: Option<Box<dyn Fn() -> Message + 'a>>,
|
||||
opacity: Option<f32>,
|
||||
|
|
@ -139,7 +143,7 @@ where
|
|||
show_headerbar: true,
|
||||
pane_border_radius: None,
|
||||
click_timing: Duration::from_millis(500),
|
||||
context_menu: None,
|
||||
context_menu_open: false,
|
||||
on_context_menu: None,
|
||||
on_mouse_enter: None,
|
||||
opacity: None,
|
||||
|
|
@ -184,8 +188,8 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
pub fn context_menu(mut self, position: Point) -> Self {
|
||||
self.context_menu = Some(position);
|
||||
pub fn context_menu_open(mut self, open: bool) -> Self {
|
||||
self.context_menu_open = open;
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -249,7 +253,7 @@ where
|
|||
if !state.is_focused {
|
||||
return InputMethod::Disabled;
|
||||
}
|
||||
if self.context_menu.is_some() {
|
||||
if self.context_menu_open {
|
||||
return InputMethod::Disabled;
|
||||
}
|
||||
|
||||
|
|
@ -590,6 +594,41 @@ where
|
|||
);
|
||||
}
|
||||
|
||||
// Éléments de bloc peints nativement : fractions de la
|
||||
// cellule (ou de la course fusionnée, à géométrie
|
||||
// séparable), bords calés sur la grille — zéro glyphe,
|
||||
// zéro interligne.
|
||||
if let Some(block) = metadata.block {
|
||||
let span_w = self.end_x - self.start_x;
|
||||
let mut color = cosmic_text_to_iced_color(metadata.block_color);
|
||||
let rects = match block {
|
||||
BlockFill::Rects(rects) => rects,
|
||||
BlockFill::Shade(quarters) => {
|
||||
color.a *= f32::from(quarters) / 4.0;
|
||||
[(0, 8, 0, 8), (8, 8, 8, 8)]
|
||||
}
|
||||
};
|
||||
for (x0, x1, y0, y1) in rects {
|
||||
if x0 >= x1 || y0 >= y1 {
|
||||
continue;
|
||||
}
|
||||
let left = self.start_x + span_w * f32::from(x0) / 8.0;
|
||||
let right = self.start_x + span_w * f32::from(x1) / 8.0;
|
||||
let top = self.line_top + self.line_height * f32::from(y0) / 8.0;
|
||||
let bottom = self.line_top + self.line_height * f32::from(y1) / 8.0;
|
||||
renderer.fill_quad(
|
||||
Quad {
|
||||
bounds: Rectangle::new(
|
||||
self.view_position + Vector::new(left.floor(), top),
|
||||
Size::new(right.ceil() - left.floor(), bottom - top),
|
||||
),
|
||||
..Default::default()
|
||||
},
|
||||
color,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if !metadata.flags.is_empty() {
|
||||
let style_line_height = (self.glyph_font_size / 10.0).clamp(1.0, 16.0);
|
||||
|
||||
|
|
@ -1353,11 +1392,11 @@ where
|
|||
}
|
||||
// Update context menu state
|
||||
if let Some(on_context_menu) = &self.on_context_menu {
|
||||
match self.context_menu {
|
||||
Some(_) => {
|
||||
match self.context_menu_open {
|
||||
true => {
|
||||
shell.publish(on_context_menu(None));
|
||||
}
|
||||
None => {
|
||||
false => {
|
||||
if *button == Button::Right {
|
||||
let x = p.x - self.padding.left;
|
||||
let y = p.y - self.padding.top;
|
||||
|
|
@ -1375,15 +1414,7 @@ where
|
|||
None,
|
||||
);
|
||||
let link = get_hyperlink(&terminal, location);
|
||||
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,
|
||||
})));
|
||||
shell.publish(on_context_menu(Some(MenuState { link })));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue