feat: Convert context menu from widget::popover to Wayland popup surface
This commit is contained in:
parent
936e0c0628
commit
0575680860
2 changed files with 120 additions and 87 deletions
|
|
@ -47,7 +47,7 @@ features = ["monospace_fallback", "shape-run-cache"]
|
||||||
git = "https://github.com/pop-os/libcosmic.git"
|
git = "https://github.com/pop-os/libcosmic.git"
|
||||||
default-features = false
|
default-features = false
|
||||||
#TODO: a11y feature crashes file chooser dialog
|
#TODO: a11y feature crashes file chooser dialog
|
||||||
features = ["about", "multi-window", "tokio", "winit", "surface-message"]
|
features = ["about", "autosize", "multi-window", "tokio", "winit", "surface-message"]
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
fork = "0.4"
|
fork = "0.4"
|
||||||
|
|
|
||||||
205
src/main.rs
205
src/main.rs
|
|
@ -453,6 +453,7 @@ pub enum Message {
|
||||||
ZoomIn,
|
ZoomIn,
|
||||||
ZoomOut,
|
ZoomOut,
|
||||||
ZoomReset,
|
ZoomReset,
|
||||||
|
ContextMenuPopupClosed(window::Id),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
|
@ -523,6 +524,7 @@ 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)>,
|
||||||
#[cfg(feature = "password_manager")]
|
#[cfg(feature = "password_manager")]
|
||||||
password_mgr: password_manager::PasswordManager,
|
password_mgr: password_manager::PasswordManager,
|
||||||
}
|
}
|
||||||
|
|
@ -1840,6 +1842,7 @@ 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(),
|
||||||
};
|
};
|
||||||
|
|
@ -2428,40 +2431,18 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::CopyUrlByMenu => {
|
Message::CopyUrlByMenu => {
|
||||||
if let Some(tab_model) = self.pane_model.active() {
|
if let Some((_, _, _, ref link, _)) = self.context_menu_popup {
|
||||||
let entity = tab_model.active();
|
if let Some(url) = link.clone() {
|
||||||
if let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) {
|
return Task::batch([clipboard::write(url), self.update_focus()]);
|
||||||
// Update context menu position
|
|
||||||
let mut terminal = terminal.lock().unwrap();
|
|
||||||
if let Some(url) =
|
|
||||||
terminal.context_menu.as_ref().and_then(|m| m.link.as_ref())
|
|
||||||
{
|
|
||||||
let url = url.to_owned();
|
|
||||||
terminal.context_menu = None;
|
|
||||||
terminal.active_regex_match = None;
|
|
||||||
terminal.needs_update = true;
|
|
||||||
|
|
||||||
return Task::batch([clipboard::write(url), self.update_focus()]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::LaunchUrlByMenu => {
|
Message::LaunchUrlByMenu => {
|
||||||
if let Some(tab_model) = self.pane_model.active() {
|
if let Some((_, _, _, ref link, _)) = self.context_menu_popup {
|
||||||
let entity = tab_model.active();
|
if let Some(url) = link.as_ref() {
|
||||||
if let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) {
|
if let Err(err) = open::that_detached(url) {
|
||||||
// Update context menu position
|
log::warn!("failed to open {:?}: {}", url, err);
|
||||||
let mut terminal = terminal.lock().unwrap();
|
|
||||||
if let Some(url) =
|
|
||||||
terminal.context_menu.as_ref().and_then(|m| m.link.as_ref())
|
|
||||||
{
|
|
||||||
if let Err(err) = open::that_detached(url) {
|
|
||||||
log::warn!("failed to open {:?}: {}", url, err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
terminal.context_menu = None;
|
|
||||||
terminal.active_regex_match = None;
|
|
||||||
terminal.needs_update = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2810,54 +2791,91 @@ impl Application for App {
|
||||||
return self.update_title(None);
|
return self.update_title(None);
|
||||||
}
|
}
|
||||||
Message::TabContextAction(entity, action) => {
|
Message::TabContextAction(entity, action) => {
|
||||||
if let Some(tab_model) = self.pane_model.active() {
|
// Close context menu popup
|
||||||
if let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) {
|
let mut tasks = Vec::new();
|
||||||
// Close context menu
|
if let Some((popup_id, _, _, _, _)) = self.context_menu_popup.take() {
|
||||||
{
|
tasks.push(cosmic::task::message(Message::Surface(
|
||||||
let mut terminal = terminal.lock().unwrap();
|
cosmic::surface::action::destroy_popup(popup_id),
|
||||||
//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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
terminal.context_menu = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Run action's message
|
|
||||||
return self.update(action.message(Some(entity)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
// Also clear terminal context_menu state
|
||||||
Message::TabContextMenu(pane, menu_state) => {
|
if let Some(tab_model) = self.pane_model.active() {
|
||||||
// Close any existing context menues
|
|
||||||
let panes: Vec<_> = self.pane_model.panes.iter().collect();
|
|
||||||
for (_pane, tab_model) in panes {
|
|
||||||
let entity = tab_model.active();
|
|
||||||
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;
|
terminal.context_menu = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
tasks.push(self.update(action.message(Some(entity))));
|
||||||
|
return cosmic::Task::batch(tasks);
|
||||||
|
}
|
||||||
|
Message::TabContextMenu(pane, menu_state) => {
|
||||||
|
let mut tasks = Vec::new();
|
||||||
|
|
||||||
// Show the context menu on the correct pane / terminal
|
// Close existing context menu popup if any
|
||||||
if let Some(tab_model) = self.pane_model.panes.get(pane) {
|
if let Some((popup_id, _, _, _, _)) = self.context_menu_popup.take() {
|
||||||
let entity = tab_model.active();
|
tasks.push(cosmic::task::message(Message::Surface(
|
||||||
if let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) {
|
cosmic::surface::action::destroy_popup(popup_id),
|
||||||
// Update context menu position
|
)));
|
||||||
let mut terminal = terminal.lock().unwrap();
|
}
|
||||||
terminal.context_menu = menu_state;
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shift focus to the pane / terminal
|
if let Some(menu_state) = menu_state {
|
||||||
// with the context menu
|
if let Some(position) = menu_state.position {
|
||||||
self.pane_model.set_focus(pane);
|
if let Some(tab_model) = self.pane_model.panes.get(pane) {
|
||||||
return self.update_title(Some(pane));
|
{
|
||||||
|
let entity = tab_model.active();
|
||||||
|
let link = menu_state.link.clone();
|
||||||
|
let popup_id = window::Id::unique();
|
||||||
|
self.context_menu_popup = Some((popup_id, pane, entity, link, widget::Id::unique()));
|
||||||
|
|
||||||
|
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(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 => {
|
Message::TabNew => {
|
||||||
return self.create_and_focus_new_terminal(
|
return self.create_and_focus_new_terminal(
|
||||||
|
|
@ -3133,6 +3151,13 @@ impl Application for App {
|
||||||
self.reset_terminal_panes_zoom();
|
self.reset_terminal_panes_zoom();
|
||||||
return self.update_config();
|
return self.update_config();
|
||||||
}
|
}
|
||||||
|
Message::ContextMenuPopupClosed(id) => {
|
||||||
|
if let Some((popup_id, _, _, _, _)) = &self.context_menu_popup {
|
||||||
|
if id == *popup_id {
|
||||||
|
self.context_menu_popup = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Message::Surface(a) => {
|
Message::Surface(a) => {
|
||||||
return cosmic::task::message(cosmic::Action::Cosmic(
|
return cosmic::task::message(cosmic::Action::Cosmic(
|
||||||
cosmic::app::Action::Surface(a),
|
cosmic::app::Action::Surface(a),
|
||||||
|
|
@ -3239,7 +3264,24 @@ impl Application for App {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_close_requested(&self, id: window::Id) -> Option<Self::Message> {
|
||||||
|
if let Some((popup_id, _, _, _, _)) = &self.context_menu_popup {
|
||||||
|
if 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 {
|
||||||
|
if window_id == popup_id {
|
||||||
|
return widget::autosize::autosize(
|
||||||
|
menu::context_menu(&self.config, &self.key_binds, entity, link.clone()),
|
||||||
|
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(),
|
||||||
|
|
@ -3307,26 +3349,17 @@ 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));
|
||||||
}
|
}
|
||||||
|
|
||||||
let context_menu = {
|
// If a context menu popup is active for this pane, inform the
|
||||||
let terminal = terminal.lock().unwrap();
|
// terminal_box so it will emit on_context_menu(None) on click
|
||||||
terminal.context_menu.clone()
|
// to dismiss the popup.
|
||||||
};
|
if let Some((_, popup_pane, _, _, _)) = &self.context_menu_popup {
|
||||||
|
if pane == *popup_pane {
|
||||||
|
terminal_box =
|
||||||
|
terminal_box.context_menu(cosmic::iced::Point::ORIGIN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let tab_element: Element<'_, Message> = match context_menu {
|
let tab_element: Element<'_, Message> = terminal_box.into();
|
||||||
Some(menu_state) => match menu_state.position {
|
|
||||||
Some(point) => widget::popover(terminal_box.context_menu(point))
|
|
||||||
.popup(menu::context_menu(
|
|
||||||
&self.config,
|
|
||||||
&self.key_binds,
|
|
||||||
entity,
|
|
||||||
menu_state.link,
|
|
||||||
))
|
|
||||||
.position(widget::popover::Position::Point(point))
|
|
||||||
.into(),
|
|
||||||
None => terminal_box.into(),
|
|
||||||
},
|
|
||||||
None => terminal_box.into(),
|
|
||||||
};
|
|
||||||
tab_column = tab_column.push(tab_element);
|
tab_column = tab_column.push(tab_element);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue