Add simple context menu
This commit is contained in:
parent
a1b40e1d71
commit
858bad0f50
5 changed files with 205 additions and 89 deletions
54
src/main.rs
54
src/main.rs
|
|
@ -7,7 +7,7 @@ use cosmic::{
|
|||
iced::{
|
||||
clipboard, event, keyboard, subscription,
|
||||
widget::{row, text},
|
||||
window, Alignment, Length,
|
||||
window, Alignment, Length, Point,
|
||||
},
|
||||
style,
|
||||
widget::{self, button, icon, nav_bar, segmented_button, view_switcher},
|
||||
|
|
@ -21,7 +21,7 @@ use std::{
|
|||
sync::Mutex,
|
||||
};
|
||||
|
||||
use config::{AppTheme, Config, CONFIG_VERSION};
|
||||
use config::{Action, AppTheme, Config, CONFIG_VERSION};
|
||||
mod config;
|
||||
|
||||
mod localize;
|
||||
|
|
@ -105,7 +105,7 @@ pub struct Flags {
|
|||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Message {
|
||||
AppTheme(AppTheme),
|
||||
Config(Config),
|
||||
|
|
@ -133,6 +133,8 @@ pub enum Message {
|
|||
TabActivate(segmented_button::Entity),
|
||||
TabChanged(segmented_button::Entity),
|
||||
TabClose(segmented_button::Entity),
|
||||
TabContextAction(segmented_button::Entity, Action),
|
||||
TabContextMenu(segmented_button::Entity, Option<Point>),
|
||||
TabWidth(u16),
|
||||
Todo,
|
||||
ToggleAutoIndent,
|
||||
|
|
@ -829,6 +831,30 @@ impl Application for App {
|
|||
|
||||
return self.update_tab();
|
||||
}
|
||||
Message::TabContextAction(entity, action) => {
|
||||
match self.tab_model.data_mut::<Tab>(entity) {
|
||||
Some(tab) => {
|
||||
// Close context menu
|
||||
tab.context_menu = None;
|
||||
// Hack to ensure editor redraws
|
||||
tab.editor.lock().unwrap().buffer_mut().set_redraw(true);
|
||||
// Run action's message
|
||||
return self.update(action.message());
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
Message::TabContextMenu(entity, position_opt) => {
|
||||
match self.tab_model.data_mut::<Tab>(entity) {
|
||||
Some(tab) => {
|
||||
// Update context menu
|
||||
tab.context_menu = position_opt;
|
||||
// Hack to ensure editor redraws
|
||||
tab.editor.lock().unwrap().buffer_mut().set_redraw(true);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
Message::TabWidth(tab_width) => {
|
||||
self.config.tab_width = tab_width;
|
||||
return self.save_config();
|
||||
|
|
@ -1024,7 +1050,8 @@ impl Application for App {
|
|||
.align_items(Alignment::Center),
|
||||
);
|
||||
|
||||
match self.active_tab() {
|
||||
let tab_id = self.tab_model.active();
|
||||
match self.tab_model.data::<Tab>(tab_id) {
|
||||
Some(tab) => {
|
||||
let status = {
|
||||
let editor = tab.editor.lock().unwrap();
|
||||
|
|
@ -1060,10 +1087,21 @@ impl Application for App {
|
|||
}
|
||||
}
|
||||
};
|
||||
tab_column = tab_column.push(
|
||||
text_box(&tab.editor, self.config.metrics())
|
||||
.on_changed(Message::TabChanged(self.tab_model.active())),
|
||||
);
|
||||
let text_box = text_box(&tab.editor, self.config.metrics())
|
||||
.on_changed(Message::TabChanged(tab_id))
|
||||
.on_context_menu(move |position_opt| {
|
||||
Message::TabContextMenu(tab_id, position_opt)
|
||||
});
|
||||
let tab_element: Element<'_, Message> = match tab.context_menu {
|
||||
Some(position) => widget::popover(
|
||||
text_box.context_menu(position),
|
||||
menu::context_menu(&self.config, tab_id),
|
||||
)
|
||||
.position(position)
|
||||
.into(),
|
||||
None => text_box.into(),
|
||||
};
|
||||
tab_column = tab_column.push(tab_element);
|
||||
tab_column = tab_column.push(text(status).font(cosmic::font::Font::MONOSPACE));
|
||||
}
|
||||
None => {
|
||||
|
|
|
|||
71
src/menu.rs
71
src/menu.rs
|
|
@ -1,17 +1,69 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmic::{
|
||||
cosmic_theme,
|
||||
//TODO: export in cosmic::widget
|
||||
iced::{widget::horizontal_rule, Alignment, Length},
|
||||
iced::{
|
||||
widget::{column, horizontal_rule},
|
||||
Alignment, Length,
|
||||
},
|
||||
theme,
|
||||
widget::{
|
||||
self, horizontal_space,
|
||||
menu::{ItemHeight, ItemWidth, MenuBar, MenuTree},
|
||||
segmented_button,
|
||||
},
|
||||
Element,
|
||||
};
|
||||
|
||||
use crate::{fl, Config, ContextPage, Message};
|
||||
use crate::{fl, Action, Config, ContextPage, Message};
|
||||
|
||||
macro_rules! menu_button {
|
||||
($($x:expr),+ $(,)?) => (
|
||||
widget::button(
|
||||
widget::Row::with_children(
|
||||
vec![$(Element::from($x)),+]
|
||||
)
|
||||
.align_items(Alignment::Center)
|
||||
)
|
||||
.height(Length::Fixed(32.0))
|
||||
.padding([4, 16])
|
||||
.width(Length::Fill)
|
||||
.style(theme::Button::MenuItem)
|
||||
);
|
||||
}
|
||||
|
||||
pub fn context_menu<'a>(config: &Config, entity: segmented_button::Entity) -> Element<'a, Message> {
|
||||
let menu_item = |label, action| {
|
||||
let mut key = String::new();
|
||||
for (key_bind, action) in config.keybinds.iter() {
|
||||
if action == action {
|
||||
key = key_bind.to_string();
|
||||
break;
|
||||
}
|
||||
}
|
||||
menu_button!(
|
||||
widget::text(label),
|
||||
horizontal_space(Length::Fill),
|
||||
widget::text(key)
|
||||
)
|
||||
.on_press(Message::TabContextAction(entity, action))
|
||||
};
|
||||
|
||||
widget::cosmic_container::container(column!(
|
||||
menu_item(fl!("undo"), Action::Undo),
|
||||
menu_item(fl!("redo"), Action::Redo),
|
||||
horizontal_rule(1),
|
||||
menu_item(fl!("cut"), Action::Cut),
|
||||
menu_item(fl!("copy"), Action::Copy),
|
||||
menu_item(fl!("paste"), Action::Paste),
|
||||
menu_item(fl!("select-all"), Action::SelectAll),
|
||||
))
|
||||
.layer(cosmic_theme::Layer::Background)
|
||||
.style(theme::Container::Card)
|
||||
.width(Length::Fixed(240.0))
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn menu_bar<'a>(config: &Config) -> Element<'a, Message> {
|
||||
//TODO: port to libcosmic
|
||||
|
|
@ -21,21 +73,6 @@ pub fn menu_bar<'a>(config: &Config) -> Element<'a, Message> {
|
|||
.style(theme::Button::MenuRoot)
|
||||
};
|
||||
|
||||
macro_rules! menu_button {
|
||||
($($x:expr),+ $(,)?) => (
|
||||
widget::button(
|
||||
widget::Row::with_children(
|
||||
vec![$(Element::from($x)),+]
|
||||
)
|
||||
.align_items(Alignment::Center)
|
||||
)
|
||||
.height(Length::Fixed(32.0))
|
||||
.padding([4, 16])
|
||||
.width(Length::Fill)
|
||||
.style(theme::Button::MenuItem)
|
||||
);
|
||||
}
|
||||
|
||||
let menu_folder =
|
||||
|label| menu_button!(widget::text(label), horizontal_space(Length::Fill), ">");
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmic::widget::{icon, Icon};
|
||||
use cosmic::{
|
||||
iced::Point,
|
||||
widget::{icon, Icon},
|
||||
};
|
||||
use cosmic_text::{Attrs, Buffer, Edit, Shaping, SyntaxEditor, ViEditor, Wrap};
|
||||
use std::{fs, path::PathBuf, sync::Mutex};
|
||||
|
||||
|
|
@ -10,6 +13,7 @@ pub struct Tab {
|
|||
pub path_opt: Option<PathBuf>,
|
||||
attrs: Attrs<'static>,
|
||||
pub editor: Mutex<ViEditor<'static>>,
|
||||
pub context_menu: Option<Point>,
|
||||
}
|
||||
|
||||
impl Tab {
|
||||
|
|
@ -31,6 +35,7 @@ impl Tab {
|
|||
path_opt: None,
|
||||
attrs,
|
||||
editor: Mutex::new(ViEditor::new(editor)),
|
||||
context_menu: None,
|
||||
};
|
||||
|
||||
// Update any other config settings
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@ pub struct TextBox<'a, Message> {
|
|||
metrics: Metrics,
|
||||
padding: Padding,
|
||||
on_changed: Option<Message>,
|
||||
context_menu: Option<Point>,
|
||||
on_context_menu: Option<Box<dyn Fn(Option<Point>) -> Message + 'a>>,
|
||||
}
|
||||
|
||||
impl<'a, Message> TextBox<'a, Message>
|
||||
|
|
@ -74,6 +76,8 @@ where
|
|||
metrics,
|
||||
padding: Padding::new(0.0),
|
||||
on_changed: None,
|
||||
context_menu: None,
|
||||
on_context_menu: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -86,6 +90,19 @@ where
|
|||
self.on_changed = Some(on_changed);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn context_menu(mut self, position: Point) -> Self {
|
||||
self.context_menu = Some(position);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_context_menu(
|
||||
mut self,
|
||||
on_context_menu: impl Fn(Option<Point>) -> Message + 'a,
|
||||
) -> Self {
|
||||
self.on_context_menu = Some(Box::new(on_context_menu));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text_box<'a, Message>(
|
||||
|
|
@ -498,35 +515,50 @@ where
|
|||
status = Status::Captured;
|
||||
}
|
||||
}
|
||||
Event::Mouse(MouseEvent::ButtonPressed(Button::Left)) => {
|
||||
Event::Mouse(MouseEvent::ButtonPressed(button)) => {
|
||||
if let Some(p) = cursor_position.position_in(layout.bounds()) {
|
||||
let x_logical = p.x - self.padding.left;
|
||||
let y_logical = p.y - self.padding.top;
|
||||
let x = x_logical * scale_factor;
|
||||
let y = y_logical * scale_factor;
|
||||
if x >= 0.0 && x < buffer_size.0 && y >= 0.0 && y < buffer_size.1 {
|
||||
editor.action(Action::Click {
|
||||
x: x as i32,
|
||||
y: y as i32,
|
||||
});
|
||||
state.dragging = Some(Dragging::Buffer);
|
||||
} else if scrollbar_rect.contains(Point::new(x_logical, y_logical)) {
|
||||
state.dragging = Some(Dragging::Scrollbar {
|
||||
start_y: y,
|
||||
start_scroll: editor.buffer().scroll(),
|
||||
});
|
||||
} else if x_logical >= scrollbar_rect.x
|
||||
&& x_logical < (scrollbar_rect.x + scrollbar_rect.width)
|
||||
{
|
||||
let mut buffer = editor.buffer_mut();
|
||||
let scroll_offset =
|
||||
((y / buffer.size().1) * buffer.lines.len() as f32) as i32;
|
||||
buffer.set_scroll(scroll_offset);
|
||||
state.dragging = Some(Dragging::Scrollbar {
|
||||
start_y: y,
|
||||
start_scroll: editor.buffer().scroll(),
|
||||
});
|
||||
// Handle left click drag
|
||||
if let Button::Left = button {
|
||||
let x_logical = p.x - self.padding.left;
|
||||
let y_logical = p.y - self.padding.top;
|
||||
let x = x_logical * scale_factor;
|
||||
let y = y_logical * scale_factor;
|
||||
if x >= 0.0 && x < buffer_size.0 && y >= 0.0 && y < buffer_size.1 {
|
||||
editor.action(Action::Click {
|
||||
x: x as i32,
|
||||
y: y as i32,
|
||||
});
|
||||
state.dragging = Some(Dragging::Buffer);
|
||||
} else if scrollbar_rect.contains(Point::new(x_logical, y_logical)) {
|
||||
state.dragging = Some(Dragging::Scrollbar {
|
||||
start_y: y,
|
||||
start_scroll: editor.buffer().scroll(),
|
||||
});
|
||||
} else if x_logical >= scrollbar_rect.x
|
||||
&& x_logical < (scrollbar_rect.x + scrollbar_rect.width)
|
||||
{
|
||||
let mut buffer = editor.buffer_mut();
|
||||
let scroll_offset =
|
||||
((y / buffer.size().1) * buffer.lines.len() as f32) as i32;
|
||||
buffer.set_scroll(scroll_offset);
|
||||
state.dragging = Some(Dragging::Scrollbar {
|
||||
start_y: y,
|
||||
start_scroll: editor.buffer().scroll(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Update context menu state
|
||||
if let Some(on_context_menu) = &self.on_context_menu {
|
||||
shell.publish((on_context_menu)(match self.context_menu {
|
||||
Some(_) => None,
|
||||
None => match button {
|
||||
Button::Right => Some(p),
|
||||
_ => None,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
status = Status::Captured;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue