feat: use libcosmic's context_menu widget
This commit is contained in:
parent
505ecd18fe
commit
9307793661
4 changed files with 66 additions and 111 deletions
31
src/main.rs
31
src/main.rs
|
|
@ -11,7 +11,7 @@ use cosmic::{
|
||||||
cosmic_theme, executor,
|
cosmic_theme, executor,
|
||||||
font::Font,
|
font::Font,
|
||||||
iced::{
|
iced::{
|
||||||
self, Alignment, Background, Color, Length, Limits, Point, Subscription,
|
self, Alignment, Background, Color, Length, Limits, Subscription,
|
||||||
advanced::graphics::text::font_system,
|
advanced::graphics::text::font_system,
|
||||||
clipboard, event,
|
clipboard, event,
|
||||||
futures::{self, SinkExt},
|
futures::{self, SinkExt},
|
||||||
|
|
@ -412,7 +412,7 @@ pub enum Message {
|
||||||
TabClose(segmented_button::Entity),
|
TabClose(segmented_button::Entity),
|
||||||
TabCloseForce(segmented_button::Entity),
|
TabCloseForce(segmented_button::Entity),
|
||||||
TabContextAction(segmented_button::Entity, Action),
|
TabContextAction(segmented_button::Entity, Action),
|
||||||
TabContextMenu(segmented_button::Entity, Option<Point>),
|
TabContextMenu(segmented_button::Entity, bool),
|
||||||
TabNext,
|
TabNext,
|
||||||
TabPrev,
|
TabPrev,
|
||||||
TabSetCursor(segmented_button::Entity, Cursor),
|
TabSetCursor(segmented_button::Entity, Cursor),
|
||||||
|
|
@ -2820,15 +2820,15 @@ impl Application for App {
|
||||||
Message::TabContextAction(entity, action) => {
|
Message::TabContextAction(entity, action) => {
|
||||||
if let Some(Tab::Editor(tab)) = self.tab_model.data_mut::<Tab>(entity) {
|
if let Some(Tab::Editor(tab)) = self.tab_model.data_mut::<Tab>(entity) {
|
||||||
// Close context menu
|
// Close context menu
|
||||||
tab.context_menu = None;
|
tab.context_menu_open = false;
|
||||||
// Run action's message
|
// Run action's message
|
||||||
return self.update(action.message(None));
|
return self.update(action.message(None));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::TabContextMenu(entity, position_opt) => {
|
Message::TabContextMenu(entity, open) => {
|
||||||
if let Some(Tab::Editor(tab)) = self.tab_model.data_mut::<Tab>(entity) {
|
if let Some(Tab::Editor(tab)) = self.tab_model.data_mut::<Tab>(entity) {
|
||||||
// Update context menu
|
// Update context menu
|
||||||
tab.context_menu = position_opt;
|
tab.context_menu_open = open;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::TabNext => {
|
Message::TabNext => {
|
||||||
|
|
@ -3090,24 +3090,25 @@ impl Application for App {
|
||||||
.on_focus(Message::FindFocused(false))
|
.on_focus(Message::FindFocused(false))
|
||||||
.on_auto_scroll(Message::AutoScroll)
|
.on_auto_scroll(Message::AutoScroll)
|
||||||
.on_changed(Message::TabChanged(tab_id))
|
.on_changed(Message::TabChanged(tab_id))
|
||||||
.has_context_menu(tab.context_menu.is_some())
|
.has_context_menu(tab.context_menu_open)
|
||||||
.on_context_menu(move |position_opt| {
|
.on_context_menu(move |open| Message::TabContextMenu(tab_id, open));
|
||||||
Message::TabContextMenu(tab_id, position_opt)
|
|
||||||
});
|
|
||||||
if self.config.highlight_current_line {
|
if self.config.highlight_current_line {
|
||||||
text_box = text_box.highlight_current_line();
|
text_box = text_box.highlight_current_line();
|
||||||
}
|
}
|
||||||
if self.config.line_numbers {
|
if self.config.line_numbers {
|
||||||
text_box = text_box.line_numbers();
|
text_box = text_box.line_numbers();
|
||||||
}
|
}
|
||||||
let mut popover = widget::popover(text_box);
|
|
||||||
let has_selection = tab.editor.lock().unwrap().selection() != Selection::None;
|
let has_selection = tab.editor.lock().unwrap().selection() != Selection::None;
|
||||||
if let Some(point) = tab.context_menu {
|
let mut context_menu = widget::context_menu(
|
||||||
popover = popover
|
text_box,
|
||||||
.popup(menu::context_menu(&self.key_binds, tab_id, has_selection))
|
Some(menu::context_menu(&self.key_binds, tab_id, has_selection)),
|
||||||
.position(widget::popover::Position::Point(point));
|
)
|
||||||
|
.on_close(Message::TabContextMenu(tab_id, false))
|
||||||
|
.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(popover);
|
tab_column = tab_column.push(context_menu);
|
||||||
if self.config.vim_bindings {
|
if self.config.vim_bindings {
|
||||||
let status = {
|
let status = {
|
||||||
let editor = tab.editor.lock().unwrap();
|
let editor = tab.editor.lock().unwrap();
|
||||||
|
|
|
||||||
119
src/menu.rs
119
src/menu.rs
|
|
@ -1,18 +1,15 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use cosmic::widget::menu::Item as MenuItem;
|
use cosmic::widget::menu::Item as MenuItem;
|
||||||
|
use cosmic::widget::menu::action::MenuAction;
|
||||||
use cosmic::widget::menu::key_bind::KeyBind;
|
use cosmic::widget::menu::key_bind::KeyBind;
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
Element,
|
Element,
|
||||||
app::Core,
|
app::Core,
|
||||||
iced::{
|
|
||||||
Background, Border, Length, advanced::widget::text::Style as TextStyle, widget::column,
|
|
||||||
},
|
|
||||||
theme,
|
|
||||||
widget::{
|
widget::{
|
||||||
self, divider,
|
self,
|
||||||
menu::{ItemHeight, ItemWidth, menu_button},
|
menu::{ItemHeight, ItemWidth},
|
||||||
responsive_menu_bar, segmented_button, space,
|
responsive_menu_bar, segmented_button,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use std::{collections::HashMap, path::PathBuf, sync::LazyLock};
|
use std::{collections::HashMap, path::PathBuf, sync::LazyLock};
|
||||||
|
|
@ -116,81 +113,49 @@ fn format_recent_menu_path(path: &PathBuf, home_dir_opt: Option<&PathBuf>) -> St
|
||||||
truncate_middle(&display, RECENT_MENU_LABEL_MAX_CHARS)
|
truncate_middle(&display, RECENT_MENU_LABEL_MAX_CHARS)
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
||||||
key_binds: &HashMap<KeyBind, Action>,
|
key_binds: &HashMap<KeyBind, Action>,
|
||||||
entity: segmented_button::Entity,
|
entity: segmented_button::Entity,
|
||||||
has_selection: bool,
|
has_selection: bool,
|
||||||
) -> Element<'a, Message> {
|
) -> Vec<widget::menu::Tree<Message>> {
|
||||||
fn key_style(theme: &cosmic::Theme) -> TextStyle {
|
let item =
|
||||||
// TODO use wayland popups
|
|label: String, action: Action| MenuItem::Button(label, None, TabAction(entity, action));
|
||||||
let mut color = theme.cosmic().background(false).component.on;
|
let optional = |label: String, action: Action, enabled: bool| {
|
||||||
color.alpha *= 0.75;
|
if enabled {
|
||||||
TextStyle {
|
MenuItem::Button(label, None, TabAction(entity, action))
|
||||||
color: Some(color.into()),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let base_menu_item = |menu_label, menu_action| {
|
|
||||||
let mut key = String::new();
|
|
||||||
for (key_bind, key_action) in key_binds.iter() {
|
|
||||||
if key_action == &menu_action {
|
|
||||||
key = key_bind.to_string();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
menu_button(vec![
|
|
||||||
widget::text(menu_label).into(),
|
|
||||||
space::horizontal().into(),
|
|
||||||
widget::text(key)
|
|
||||||
.class(theme::Text::Custom(key_style))
|
|
||||||
.into(),
|
|
||||||
])
|
|
||||||
};
|
|
||||||
|
|
||||||
let optional_menu_item = |menu_label, menu_action, disabled| {
|
|
||||||
base_menu_item(menu_label, menu_action).on_press_maybe(if disabled {
|
|
||||||
None
|
|
||||||
} else {
|
} else {
|
||||||
Some(Message::TabContextAction(entity, menu_action))
|
MenuItem::ButtonDisabled(label, None, TabAction(entity, action))
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
let menu_item = |menu_label, menu_action| {
|
|
||||||
base_menu_item(menu_label, menu_action)
|
|
||||||
.on_press(Message::TabContextAction(entity, menu_action))
|
|
||||||
};
|
|
||||||
|
|
||||||
widget::container(
|
|
||||||
cosmic::widget::menu::menu_column::MenuColumn::with_children([
|
|
||||||
menu_item(fl!("undo"), Action::Undo).into(),
|
|
||||||
menu_item(fl!("redo"), Action::Redo).into(),
|
|
||||||
divider::horizontal::light().into(),
|
|
||||||
optional_menu_item(fl!("cut"), Action::Cut, !has_selection).into(),
|
|
||||||
optional_menu_item(fl!("copy"), Action::Copy, !has_selection).into(),
|
|
||||||
menu_item(fl!("paste"), Action::Paste).into(),
|
|
||||||
menu_item(fl!("select-all"), Action::SelectAll).into(),
|
|
||||||
]),
|
|
||||||
)
|
|
||||||
.padding(1)
|
|
||||||
//TODO: move style to libcosmic
|
|
||||||
.style(|theme| {
|
|
||||||
let cosmic = theme.cosmic();
|
|
||||||
let component = &cosmic.background(false).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(240.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,
|
||||||
|
vec![
|
||||||
|
item(fl!("undo"), Action::Undo),
|
||||||
|
item(fl!("redo"), Action::Redo),
|
||||||
|
MenuItem::Divider,
|
||||||
|
optional(fl!("cut"), Action::Cut, has_selection),
|
||||||
|
optional(fl!("copy"), Action::Copy, has_selection),
|
||||||
|
item(fl!("paste"), Action::Paste),
|
||||||
|
item(fl!("select-all"), Action::SelectAll),
|
||||||
|
],
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn menu_bar<'a>(
|
pub fn menu_bar<'a>(
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use cosmic::{
|
use cosmic::{iced::advanced::graphics::text::font_system, widget::icon};
|
||||||
iced::{Point, advanced::graphics::text::font_system},
|
|
||||||
widget::icon,
|
|
||||||
};
|
|
||||||
use cosmic_files::mime_icon::{FALLBACK_MIME_ICON, mime_for_path, mime_icon};
|
use cosmic_files::mime_icon::{FALLBACK_MIME_ICON, mime_for_path, mime_icon};
|
||||||
use cosmic_text::{Attrs, Buffer, Cursor, Edit, Selection, Shaping, SyntaxEditor, ViEditor, Wrap};
|
use cosmic_text::{Attrs, Buffer, Cursor, Edit, Selection, Shaping, SyntaxEditor, ViEditor, Wrap};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
@ -51,7 +48,7 @@ pub struct EditorTab {
|
||||||
pub path_opt: Option<PathBuf>,
|
pub path_opt: Option<PathBuf>,
|
||||||
attrs: Attrs<'static>,
|
attrs: Attrs<'static>,
|
||||||
pub editor: Mutex<ViEditor<'static, 'static>>,
|
pub editor: Mutex<ViEditor<'static, 'static>>,
|
||||||
pub context_menu: Option<Point>,
|
pub context_menu_open: bool,
|
||||||
pub zoom_adj: i8,
|
pub zoom_adj: i8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,7 +72,7 @@ impl EditorTab {
|
||||||
path_opt: None,
|
path_opt: None,
|
||||||
attrs,
|
attrs,
|
||||||
editor: Mutex::new(ViEditor::new(editor)),
|
editor: Mutex::new(ViEditor::new(editor)),
|
||||||
context_menu: None,
|
context_menu_open: false,
|
||||||
zoom_adj,
|
zoom_adj,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ pub struct TextBox<'a, Message> {
|
||||||
on_focus: Option<Message>,
|
on_focus: Option<Message>,
|
||||||
click_timing: Duration,
|
click_timing: Duration,
|
||||||
has_context_menu: bool,
|
has_context_menu: bool,
|
||||||
on_context_menu: Option<Box<dyn Fn(Option<Point>) -> Message + 'a>>,
|
on_context_menu: Option<Box<dyn Fn(bool) -> Message + 'a>>,
|
||||||
highlight_current_line: bool,
|
highlight_current_line: bool,
|
||||||
line_numbers: bool,
|
line_numbers: bool,
|
||||||
}
|
}
|
||||||
|
|
@ -107,10 +107,7 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_context_menu(
|
pub fn on_context_menu(mut self, on_context_menu: impl Fn(bool) -> Message + 'a) -> Self {
|
||||||
mut self,
|
|
||||||
on_context_menu: impl Fn(Option<Point>) -> Message + 'a,
|
|
||||||
) -> Self {
|
|
||||||
self.on_context_menu = Some(Box::new(on_context_menu));
|
self.on_context_menu = Some(Box::new(on_context_menu));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
@ -1267,14 +1264,9 @@ 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 {
|
||||||
shell.publish((on_context_menu)(if self.has_context_menu {
|
shell.publish((on_context_menu)(
|
||||||
None
|
!self.has_context_menu && matches!(button, Button::Right),
|
||||||
} else {
|
));
|
||||||
match button {
|
|
||||||
Button::Right => Some(p),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shell.capture_event();
|
shell.capture_event();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue