Skip to content

Commit

Permalink
feat: context menu for basic actions (#861)
Browse files Browse the repository at this point in the history
* Recognize longpress and rightclick

* Move gesture recognition to canvas wrapper

* Create contextmenu and remove right-click gesture

* Move properties to .ui

* Apply formatting

* Store last context menu position

* Correctly position clipboard content

* Qualify gdk::Rectangle

* Add comment to top of imports

* Extract paste functionality into it's own function and create separate actions for the context menu paste

* fix: add new files to meson.build

* fix: change size of rectangle to (4, 4)

* refactor: replace glib::MainContext::default().spawn_local with glib::spawn_future_local
  • Loading branch information
silvasch authored Feb 19, 2024
1 parent a5dc2e9 commit 23ab2a3
Show file tree
Hide file tree
Showing 11 changed files with 350 additions and 174 deletions.
3 changes: 2 additions & 1 deletion crates/rnote-ui/data/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ rnote_ui_gresources_ui_files = files(
'ui/canvasmenu.ui',
'ui/canvaswrapper.ui',
'ui/colorpicker.ui',
'ui/contextmenu.ui',
'ui/filerow.ui',
'ui/iconpicker.ui',
'ui/mainheader.ui',
Expand All @@ -383,4 +384,4 @@ rnote_ui_gresources_files = [
files(
'resources.gresource.xml',
),
]
]
1 change: 1 addition & 0 deletions crates/rnote-ui/data/resources.gresource.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<file compressed="true" preprocess="xml-stripblanks">ui/canvasmenu.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/canvaswrapper.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/colorpicker.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/contextmenu.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/filerow.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/iconpicker.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/mainheader.ui</file>
Expand Down
2 changes: 1 addition & 1 deletion crates/rnote-ui/data/ui/appmenu.ui
Original file line number Diff line number Diff line change
Expand Up @@ -195,4 +195,4 @@
</child>
</object>
</template>
</interface>
</interface>
5 changes: 4 additions & 1 deletion crates/rnote-ui/data/ui/canvaswrapper.ui
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@
<object class="RnCanvas" id="canvas">
<property name="halign">center</property>
<property name="valign">start</property>
<child>
<object class="RnContextMenu" id="contextmenu"></object>
</child>
</object>
</child>
</object>
</child>
</template>
</interface>
</interface>
25 changes: 25 additions & 0 deletions crates/rnote-ui/data/ui/contextmenu.ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="RnContextMenu" parent="GtkWidget">
<child>
<object class="GtkPopoverMenu" id="popover">
<property name="menu-model">menu_model</property>
<property name="has-arrow">false</property>
<menu id="menu_model">
<item>
<attribute name="label" translatable="yes">_Copy</attribute>
<attribute name="action">win.clipboard-copy</attribute>
</item>
<item>
<attribute name="label" translatable="yes">C_ut</attribute>
<attribute name="action">win.clipboard-cut</attribute>
</item>
<item>
<attribute name="label" translatable="yes">_Paste</attribute>
<attribute name="action">win.clipboard-paste-contextmenu</attribute>
</item>
</menu>
</object>
</child>
</template>
</interface>
372 changes: 204 additions & 168 deletions crates/rnote-ui/src/appwindow/actions.rs

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions crates/rnote-ui/src/canvas/imexport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,14 @@ impl RnCanvas {
/// Deserializes the stroke content and inserts it into the engine.
///
/// The data is usually coming from the clipboard, drop source, etc.
pub(crate) async fn insert_stroke_content(&self, json_string: String) -> anyhow::Result<()> {
pub(crate) async fn insert_stroke_content(
&self,
json_string: String,
target_pos: Option<na::Vector2<f64>>,
) -> anyhow::Result<()> {
let (oneshot_sender, oneshot_receiver) =
oneshot::channel::<anyhow::Result<StrokeContent>>();
let pos = self.determine_stroke_import_pos(None);
let pos = self.determine_stroke_import_pos(target_pos);

rayon::spawn(move || {
let result = || -> Result<StrokeContent, anyhow::Error> {
Expand Down
36 changes: 35 additions & 1 deletion crates/rnote-ui/src/canvaswrapper.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Imports
use crate::{RnAppWindow, RnCanvas};
use crate::{RnAppWindow, RnCanvas, RnContextMenu};
use gtk4::{
gdk, glib, glib::clone, graphene, prelude::*, subclass::prelude::*, CompositeTemplate,
CornerType, EventControllerMotion, EventControllerScroll, EventControllerScrollFlags,
Expand Down Expand Up @@ -34,6 +34,7 @@ mod imp {
pub(crate) block_pinch_zoom: Cell<bool>,
pub(crate) inertial_scrolling: Cell<bool>,
pub(crate) pointer_pos: Cell<Option<na::Vector2<f64>>>,
pub(crate) last_contextmenu_pos: Cell<Option<na::Vector2<f64>>>,

pub(crate) pointer_motion_controller: EventControllerMotion,
pub(crate) canvas_drag_gesture: GestureDrag,
Expand All @@ -43,11 +44,14 @@ mod imp {
pub(crate) canvas_alt_drag_gesture: GestureDrag,
pub(crate) canvas_alt_shift_drag_gesture: GestureDrag,
pub(crate) touch_two_finger_long_press_gesture: GestureLongPress,
pub(crate) touch_long_press_gesture: GestureLongPress,

#[template_child]
pub(crate) scroller: TemplateChild<ScrolledWindow>,
#[template_child]
pub(crate) canvas: TemplateChild<RnCanvas>,
#[template_child]
pub(crate) contextmenu: TemplateChild<RnContextMenu>,
}

impl Default for RnCanvasWrapper {
Expand Down Expand Up @@ -109,13 +113,19 @@ mod imp {
.propagation_phase(PropagationPhase::Capture)
.build();

let touch_long_press_gesture = GestureLongPress::builder()
.name("touch_long_press_gesture")
.touch_only(true)
.build();

Self {
connections: RefCell::new(Connections::default()),
canvas_touch_drawing_handler: RefCell::new(None),
show_scrollbars: Cell::new(false),
block_pinch_zoom: Cell::new(false),
inertial_scrolling: Cell::new(true),
pointer_pos: Cell::new(None),
last_contextmenu_pos: Cell::new(None),

pointer_motion_controller,
canvas_drag_gesture,
Expand All @@ -125,9 +135,11 @@ mod imp {
canvas_alt_drag_gesture,
canvas_alt_shift_drag_gesture,
touch_two_finger_long_press_gesture,
touch_long_press_gesture,

scroller: TemplateChild::<ScrolledWindow>::default(),
canvas: TemplateChild::<RnCanvas>::default(),
contextmenu: TemplateChild::<RnContextMenu>::default(),
}
}
}
Expand Down Expand Up @@ -169,6 +181,8 @@ mod imp {
.add_controller(self.canvas_alt_shift_drag_gesture.clone());
self.scroller
.add_controller(self.touch_two_finger_long_press_gesture.clone());
self.canvas
.add_controller(self.touch_long_press_gesture.clone());

// group
self.touch_two_finger_long_press_gesture
Expand Down Expand Up @@ -601,6 +615,18 @@ mod imp {
}),
);
}

{
// Context menu
self.touch_long_press_gesture.connect_pressed(
clone!(@weak obj as canvaswrapper => move |_gesture, x, y| {
let popover = canvaswrapper.contextmenu().popover();
canvaswrapper.imp().last_contextmenu_pos.set(Some(na::vector![x, y]));
popover.set_pointing_to(Some(&gdk::Rectangle::new(x as i32, y as i32, 4, 4)));
popover.popup();
}),
);
}
}
}
}
Expand Down Expand Up @@ -650,6 +676,10 @@ impl RnCanvasWrapper {
self.set_property("inertial-scrolling", inertial_scrolling);
}

pub(crate) fn last_contextmenu_pos(&self) -> Option<na::Vector2<f64>> {
self.imp().last_contextmenu_pos.get()
}

pub(crate) fn scroller(&self) -> ScrolledWindow {
self.imp().scroller.get()
}
Expand All @@ -658,6 +688,10 @@ impl RnCanvasWrapper {
self.imp().canvas.get()
}

pub(crate) fn contextmenu(&self) -> RnContextMenu {
self.imp().contextmenu.get()
}

/// Initializes for the given appwindow. Usually `init()` is only called once,
/// but because this widget can be moved across appwindows through tabs,
/// this function also disconnects and replaces all existing old connections
Expand Down
69 changes: 69 additions & 0 deletions crates/rnote-ui/src/contextmenu.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Imports
use gtk4::{glib, prelude::*, subclass::prelude::*, CompositeTemplate, PopoverMenu, Widget};

mod imp {
use super::*;

#[derive(Default, Debug, CompositeTemplate)]
#[template(resource = "/com/github/flxzt/rnote/ui/contextmenu.ui")]
pub(crate) struct RnContextMenu {
#[template_child]
pub(crate) popover: TemplateChild<PopoverMenu>,
}

#[glib::object_subclass]
impl ObjectSubclass for RnContextMenu {
const NAME: &'static str = "RnContextMenu";
type Type = super::RnContextMenu;
type ParentType = gtk4::Widget;

fn class_init(klass: &mut Self::Class) {
Self::bind_template(klass);
}

fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}

impl ObjectImpl for RnContextMenu {
fn constructed(&self) {
self.parent_constructed();
}

fn dispose(&self) {
self.dispose_template();
while let Some(child) = self.obj().first_child() {
child.unparent();
}
}
}

impl WidgetImpl for RnContextMenu {
fn size_allocate(&self, width: i32, height: i32, baseline: i32) {
self.parent_size_allocate(width, height, baseline);
self.popover.get().present();
}
}
}

glib::wrapper! {
pub(crate) struct RnContextMenu(ObjectSubclass<imp::RnContextMenu>)
@extends Widget;
}

impl Default for RnContextMenu {
fn default() -> Self {
Self::new()
}
}

impl RnContextMenu {
pub(crate) fn new() -> Self {
glib::Object::new()
}

pub(crate) fn popover(&self) -> PopoverMenu {
self.imp().popover.get()
}
}
2 changes: 2 additions & 0 deletions crates/rnote-ui/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub(crate) mod canvasmenu;
pub(crate) mod canvaswrapper;
pub(crate) mod colorpicker;
pub(crate) mod config;
pub(crate) mod contextmenu;
pub(crate) mod dialogs;
pub(crate) mod env;
pub(crate) mod filetype;
Expand Down Expand Up @@ -39,6 +40,7 @@ pub(crate) use canvas::RnCanvas;
pub(crate) use canvasmenu::RnCanvasMenu;
pub(crate) use canvaswrapper::RnCanvasWrapper;
pub(crate) use colorpicker::RnColorPicker;
pub(crate) use contextmenu::RnContextMenu;
pub(crate) use filetype::FileType;
pub(crate) use groupediconpicker::RnGroupedIconPicker;
pub(crate) use iconpicker::RnIconPicker;
Expand Down
1 change: 1 addition & 0 deletions crates/rnote-ui/src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ rnote_ui_sources = files(
'canvasmenu.rs',
'canvaswrapper.rs',
'config.rs',
'contextmenu.rs',
'env.rs',
'filetype.rs',
'globals.rs',
Expand Down

0 comments on commit 23ab2a3

Please sign in to comment.