diff --git a/Cargo.toml b/Cargo.toml index 066a9ee..e5a3ff4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,15 +36,17 @@ thiserror = { version = "2.0", optional = true } secstr = { version = "0.5", optional = true } [dependencies.cosmic-files] -git = "https://github.com/pop-os/cosmic-files.git" +path = "../cosmic-files" default-features = false [dependencies.cosmic-text] -version = "0.19" +git = "https://forge.aditua.com/leyoda/cosmic-text.git" +branch = "local/pr-503" features = ["monospace_fallback", "shape-run-cache"] -[dependencies.libcosmic] -git = "https://github.com/pop-os/libcosmic.git" +[dependencies.cosmic] +package = "libcosmic-yoda" +path = "../libcosmic" default-features = false #TODO: a11y feature crashes file chooser dialog features = ["about", "autosize", "multi-window", "tokio", "winit", "surface-message"] @@ -57,9 +59,9 @@ xdgen = "0.1" [features] default = ["dbus-config", "wgpu", "wayland", "password_manager"] -dbus-config = ["libcosmic/dbus-config"] -wgpu = ["libcosmic/wgpu", "cosmic-files/wgpu"] -wayland = ["libcosmic/wayland", "cosmic-files/wayland"] +dbus-config = ["cosmic/dbus-config"] +wgpu = ["cosmic/wgpu", "cosmic-files/wgpu"] +wayland = ["cosmic/wayland", "cosmic-files/wayland"] password_manager = ["secret-service", "thiserror", "secstr"] [profile.release-with-debug] @@ -67,7 +69,29 @@ inherits = "release" debug = true -#[patch.'https://github.com/pop-os/libcosmic'] -#libcosmic = { path = "../libcosmic" } -#cosmic-config = { path = "../libcosmic/cosmic-config" } -#cosmic-theme = { path = "../libcosmic/cosmic-theme" } +[patch.'https://github.com/pop-os/libcosmic'] +cosmic-config = { path = "../libcosmic/cosmic-config" } +cosmic-theme = { path = "../libcosmic/cosmic-theme" } +iced = { path = "../libcosmic/iced" } +iced_accessibility = { path = "../libcosmic/iced/accessibility" } +iced_core = { path = "../libcosmic/iced/core" } +iced_futures = { path = "../libcosmic/iced/futures" } +iced_graphics = { path = "../libcosmic/iced/graphics" } +iced_renderer = { path = "../libcosmic/iced/renderer" } +iced_runtime = { path = "../libcosmic/iced/runtime" } +iced_tiny_skia = { path = "../libcosmic/iced/tiny_skia" } +iced_wgpu = { path = "../libcosmic/iced/wgpu" } +iced_widget = { path = "../libcosmic/iced/widget" } +iced_winit = { path = "../libcosmic/iced/winit" } + +[patch.'https://github.com/pop-os/cosmic-protocols'] +cosmic-protocols = { path = "../cosmic-protocols" } +cosmic-client-toolkit = { path = "../cosmic-protocols/client-toolkit" } + +[patch.'https://github.com/pop-os/cosmic-text.git'] +cosmic-text = { git = "https://forge.aditua.com/leyoda/cosmic-text.git", branch = "local/pr-503" } + +[patch.'https://forge.aditua.com/leyoda/window_clipboard.git'] +window_clipboard = { path = "../window_clipboard" } +dnd = { path = "../window_clipboard/dnd" } +mime = { path = "../window_clipboard/mime" } diff --git a/i18n/en/cosmic_term.ftl b/i18n/en/cosmic_term.ftl index df754bd..686e9b5 100644 --- a/i18n/en/cosmic_term.ftl +++ b/i18n/en/cosmic_term.ftl @@ -70,6 +70,7 @@ tab-new-inherit-working-directory-description = Open new tabs and windows in the add-another-keybinding = Add another keybinding cancel = Cancel close-window = Close window +confirm = Confirm copy-or-sigint = Copy or SIGINT disable = Disable focus-pane-down = Focus pane down @@ -83,6 +84,7 @@ password-manager = Password manager paste-primary = Paste primary replace = Replace reset-to-default = Reset to default +reset = Reset shortcut-capture-hint = Press the key combination shortcut-group-clipboard = Clipboard shortcut-group-other = Other @@ -92,6 +94,9 @@ shortcut-group-zoom = Zoom shortcut-replace-body = { $binding } is already assigned to { $existing }. Replace it with { $new_action }? shortcut-replace-title = Replace shortcut? tab-activate = Activate tab { $number } +rename-tab = Rename tab +tab-name-placeholder = Tab name +tab-rename-title = Rename tab toggle-fullscreen = Toggle fullscreen type-to-search = Type to search... diff --git a/i18n/fr/cosmic_term.ftl b/i18n/fr/cosmic_term.ftl index 6ae7995..8351497 100644 --- a/i18n/fr/cosmic_term.ftl +++ b/i18n/fr/cosmic_term.ftl @@ -121,6 +121,7 @@ copy-link = Copier le lien add-another-keybinding = Ajouter un autre raccourci clavier cancel = Annuler close-window = Fermer la fenêtre +confirm = Confirmer copy-or-sigint = Copier ou SIGINT disable = Désactiver keyboard-shortcuts = Raccourcis clavier @@ -144,7 +145,11 @@ focus-pane-left = Passer au panneau de gauche focus-pane-right = Passer au panneau de droite focus-pane-up = Passer au panneau du dessus reset-to-default = Rétablir les paramètres par défaut +reset = Réinitialiser toggle-fullscreen = Basculer en plein écran +rename-tab = Renommer l'onglet +tab-name-placeholder = Nom de l'onglet +tab-rename-title = Renommer l'onglet comment = Émulateur de terminal pour le bureau COSMIC keywords = Commande;Shell;Terminal;CLI; tab-new-inherit-working-directory = Les nouveaux onglets et fenêtres utilisent le répertoire actuel diff --git a/src/main.rs b/src/main.rs index 4a9453f..52d0635 100644 --- a/src/main.rs +++ b/src/main.rs @@ -281,6 +281,7 @@ pub enum Action { TabNewNoProfile, TabNext, TabPrev, + TabRename, ToggleFullscreen, WindowClose, WindowNew, @@ -334,6 +335,7 @@ impl Action { Self::TabNewNoProfile => Message::TabNewNoProfile, Self::TabNext => Message::TabNext, Self::TabPrev => Message::TabPrev, + Self::TabRename => Message::TabRenameOpen(entity_opt), Self::ToggleFullscreen => Message::ToggleFullscreen, Self::WindowClose => Message::WindowClose, Self::WindowNew => Message::WindowNew, @@ -441,6 +443,12 @@ pub enum Message { TabNewNoProfile, TabNext, TabPrev, + TabRenameOpenForPane(pane_grid::Pane, segmented_button::Entity), + TabRenameOpen(Option), + TabRenameInput(String), + TabRenameCommit, + TabRenameCancel, + TabRenameResetToDefault, TermEvent(pane_grid::Pane, segmented_button::Entity, TermEvent), TermEventTx(mpsc::UnboundedSender<(pane_grid::Pane, segmented_button::Entity, TermEvent)>), ToggleFullscreen, @@ -514,6 +522,8 @@ pub struct App { color_scheme_expanded: Option<(ColorSchemeKind, Option)>, color_scheme_renaming: Option<(ColorSchemeKind, ColorSchemeId, String)>, color_scheme_rename_id: widget::Id, + rename_tab: Option<(segmented_button::Entity, String)>, + rename_tab_id: widget::Id, color_scheme_tab_model: widget::segmented_button::SingleSelectModel, profile_expanded: Option, show_advanced_font_settings: bool, @@ -764,7 +774,9 @@ impl App { } fn update_focus(&self) -> Task { - if self.find { + if self.rename_tab.is_some() { + widget::text_input::focus(self.rename_tab_id.clone()) + } else if self.find { widget::text_input::focus(self.find_search_id.clone()) } else if self.core.window.show_context { // Right now we only care about the KeyboardShortcuts context page, so we use a simple if. @@ -1884,6 +1896,8 @@ impl Application for App { color_scheme_expanded: None, color_scheme_renaming: None, color_scheme_rename_id: widget::Id::unique(), + rename_tab: None, + rename_tab_id: widget::Id::unique(), color_scheme_tab_model: widget::segmented_button::Model::default(), profile_expanded: None, show_advanced_font_settings: false, @@ -3031,6 +3045,64 @@ impl Application for App { } } } + Message::TabRenameOpenForPane(pane, entity) => { + self.pane_model.set_focus(pane); + return self.update(Message::TabRenameOpen(Some(entity))); + } + Message::TabRenameOpen(entity_opt) => { + if let Some(tab_model) = self.pane_model.active() { + let entity = entity_opt.unwrap_or_else(|| tab_model.active()); + let initial = tab_model + .text(entity) + .map(|s| s.to_string()) + .unwrap_or_default(); + self.rename_tab = Some((entity, initial)); + self.pane_model.unfocus_all_terminals(); + return self.update_focus(); + } + } + Message::TabRenameInput(value) => { + if let Some((_, buffer)) = self.rename_tab.as_mut() { + *buffer = value; + } + } + Message::TabRenameCommit => { + if let Some((entity, value)) = self.rename_tab.take() { + let trimmed = value.trim().to_string(); + if let Some(tab_model) = self.pane_model.active_mut() { + if trimmed.is_empty() { + if let Some(terminal) = tab_model.data::>(entity) { + terminal.lock().unwrap().tab_title_override = None; + } + tab_model.text_set(entity, fl!("new-terminal")); + } else { + if let Some(terminal) = tab_model.data::>(entity) { + terminal.lock().unwrap().tab_title_override = Some(trimmed.clone()); + } + tab_model.text_set(entity, trimmed); + } + } + self.pane_model.update_terminal_focus(); + return self.update_title(None); + } + } + Message::TabRenameCancel => { + self.rename_tab = None; + self.pane_model.update_terminal_focus(); + } + Message::TabRenameResetToDefault => { + if let Some((entity, _)) = self.rename_tab.take() + && let Some(tab_model) = self.pane_model.active_mut() + { + if let Some(terminal) = tab_model.data::>(entity) { + terminal.lock().unwrap().tab_title_override = None; + } + tab_model.text_set(entity, fl!("new-terminal")); + self.pane_model.update_terminal_focus(); + return self.update_title(None); + } + self.pane_model.update_terminal_focus(); + } Message::TermEvent(pane, entity, event) => { match event { TermEvent::Bell => { @@ -3356,6 +3428,31 @@ impl Application for App { } fn dialog(&self) -> Option> { + if let Some((_, buffer)) = self.rename_tab.as_ref() { + let input = widget::text_input(fl!("tab-name-placeholder"), buffer) + .id(self.rename_tab_id.clone()) + .on_input(Message::TabRenameInput) + .on_submit(|_| Message::TabRenameCommit); + + return Some( + widget::dialog() + .title(fl!("tab-rename-title")) + .control(input) + .primary_action( + widget::button::suggested(fl!("confirm")) + .on_press(Message::TabRenameCommit), + ) + .secondary_action( + widget::button::standard(fl!("cancel")).on_press(Message::TabRenameCancel), + ) + .tertiary_action( + widget::button::text(fl!("reset")) + .on_press(Message::TabRenameResetToDefault), + ) + .into(), + ); + } + let conflict = self.shortcut_conflict.as_ref()?; let binding = shortcuts::binding_display(&conflict.binding); let existing = shortcuts::action_label(conflict.existing_action); @@ -3430,34 +3527,31 @@ impl Application for App { let pane_grid = PaneGrid::new(&self.pane_model.panes, |pane, tab_model, _is_maximized| { let mut tab_column = widget::column::with_capacity(1); - if tab_model.iter().count() > 1 { - tab_column = tab_column.push( - widget::container( - widget::tab_bar::horizontal(tab_model) - .enable_tab_drag(String::from("x-cosmic-term/tab")) - .on_reorder(move |event| Message::ReorderTab(pane, event)) - .tab_drag_threshold(25.) - .button_height(32) - .button_spacing(space_xxs) - .on_activate(Message::TabActivate) - .on_close(|entity| Message::TabClose(Some(entity))), - ) - .class(style::Container::Custom(Box::new(|theme| { - let cosmic = theme.cosmic(); - cosmic::iced::widget::container::Style { - icon_color: Some(Color::from(cosmic.background.on)), - text_color: Some(Color::from(cosmic.background.on)), - background: Some(iced::Background::Color( - cosmic.background.base.into(), - )), - border: iced::Border::default(), - shadow: iced::Shadow::default(), - snap: true, - } - }))) - .width(Length::Fill), - ); - } + tab_column = tab_column.push( + widget::container( + widget::tab_bar::horizontal(tab_model) + .enable_tab_drag(String::from("x-cosmic-term/tab")) + .on_reorder(move |event| Message::ReorderTab(pane, event)) + .tab_drag_threshold(25.) + .button_height(32) + .button_spacing(space_xxs) + .on_activate(Message::TabActivate) + .on_close(|entity| Message::TabClose(Some(entity))) + .on_context(move |entity| Message::TabRenameOpenForPane(pane, entity)), + ) + .class(style::Container::Custom(Box::new(|theme| { + let cosmic = theme.cosmic(); + cosmic::iced::widget::container::Style { + icon_color: Some(Color::from(cosmic.background.on)), + text_color: Some(Color::from(cosmic.background.on)), + background: Some(iced::Background::Color(cosmic.background.base.into())), + border: iced::Border::default(), + shadow: iced::Shadow::default(), + snap: true, + } + }))) + .width(Length::Fill), + ); let entity = tab_model.active(); let entity_middle_click = tab_model.active(); @@ -3469,7 +3563,7 @@ impl Application for App { if let Some(terminal) = tab_model.data::>(entity) { let mut terminal_box = terminal_box(terminal, &self.key_binds) .id(terminal_id) - .disabled(self.core.window.show_context) + .disabled(self.core.window.show_context || self.rename_tab.is_some()) .on_context_menu(move |menu_state| Message::TabContextMenu(pane, menu_state)) .on_middle_click(move || Message::MiddleClick(pane, Some(entity_middle_click))) .on_open_hyperlink(Some(Box::new(Message::LaunchUrl))) diff --git a/src/menu.rs b/src/menu.rs index b71cbb8..7b03d59 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -95,7 +95,9 @@ pub fn context_menu<'a>( )), 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")] { @@ -227,6 +229,7 @@ pub fn menu_bar<'a>( MenuItem::Folder(fl!("profile"), profile_items), MenuItem::Button(fl!("menu-profiles"), None, Action::Profiles), MenuItem::Divider, + MenuItem::Button(fl!("rename-tab"), None, Action::TabRename), MenuItem::Button(fl!("close-tab"), None, Action::TabClose), MenuItem::Divider, MenuItem::Button(fl!("quit"), None, Action::WindowClose), diff --git a/src/shortcuts.rs b/src/shortcuts.rs index bf5d18c..e02ee00 100644 --- a/src/shortcuts.rs +++ b/src/shortcuts.rs @@ -87,6 +87,7 @@ pub enum KeyBindAction { TabNew, TabNext, TabPrev, + TabRename, ToggleFullscreen, WindowClose, WindowNew, @@ -127,6 +128,7 @@ impl KeyBindAction { Self::TabNew => Some(Action::TabNew), Self::TabNext => Some(Action::TabNext), Self::TabPrev => Some(Action::TabPrev), + Self::TabRename => Some(Action::TabRename), Self::ToggleFullscreen => Some(Action::ToggleFullscreen), Self::WindowClose => Some(Action::WindowClose), Self::WindowNew => Some(Action::WindowNew), @@ -288,6 +290,7 @@ pub fn action_label(action: KeyBindAction) -> String { KeyBindAction::TabNew => fl!("new-tab"), KeyBindAction::TabNext => fl!("next-tab"), KeyBindAction::TabPrev => fl!("previous-tab"), + KeyBindAction::TabRename => fl!("rename-tab"), KeyBindAction::ToggleFullscreen => fl!("toggle-fullscreen"), KeyBindAction::WindowClose => fl!("close-window"), KeyBindAction::WindowNew => fl!("new-window"), @@ -320,6 +323,7 @@ pub fn shortcut_groups() -> Vec { actions: vec![ KeyBindAction::TabNew, KeyBindAction::TabClose, + KeyBindAction::TabRename, KeyBindAction::TabNext, KeyBindAction::TabPrev, KeyBindAction::TabActivate0, @@ -454,6 +458,7 @@ fn fallback_shortcuts() -> Shortcuts { bind!([Ctrl, Shift], "V", Paste); bind!([Shift], "Insert", PastePrimary); bind!([Ctrl, Shift], "W", TabClose); + bind!([Ctrl, Shift], "R", TabRename); bind!([Ctrl], ",", Settings); bind!([], "F11", ToggleFullscreen); diff --git a/src/terminal.rs b/src/terminal.rs index 6cb0ce3..99ea621 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -197,11 +197,12 @@ impl TerminalPaneGrid { } pub fn unfocus_all_terminals(&self) { for (_pane, tab_model) in self.panes.panes.iter() { - let entity = tab_model.active(); - if let Some(terminal) = tab_model.data::>(entity) { - let mut terminal = terminal.lock().unwrap(); - terminal.set_focused(false); - terminal.update(); + for entity in tab_model.iter() { + if let Some(terminal) = tab_model.data::>(entity) { + let mut terminal = terminal.lock().unwrap(); + terminal.set_focused(false); + terminal.update(); + } } } }