Skip to content

Commit

Permalink
Merge pull request #20 from c2r0b/dev
Browse files Browse the repository at this point in the history
v0.7.0
  • Loading branch information
c2r0b authored Dec 21, 2023
2 parents ededa72 + 8425745 commit a4161bf
Show file tree
Hide file tree
Showing 17 changed files with 60 additions and 95 deletions.
6 changes: 6 additions & 0 deletions .changes/0.7.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"tauri-plugin-context-menu": "minor"
---

- Add checked items support #10
- Refactoring
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-context-menu"
version = "0.6.2"
version = "0.7.0"
authors = [ "c2r0b" ]
description = "Handle native Context Menu in Tauri"
license = "MIT OR Apache-2.0"
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
A Tauri plugin to display native context menu on Tauri v1.x.
The Tauri API does not support native context menu out of the box, so this plugin is created to fill the gap.

![Screenshot](./assets/screenshot.png)
<img src="./assets/screenshot.png" alt="image" width="400" height="auto">

Official context menu support has been added in Tauri v2.x (see [here](https://github.com/tauri-apps/tauri/issues/4338)), so this plugin is intended to be used with Tauri v1.x only.

Expand Down Expand Up @@ -81,6 +81,7 @@ window.addEventListener("contextmenu", async (e) => {
{
label: "Subitem 2",
disabled: false,
checked: true,
event: "subitem2clicked",
}
]
Expand Down Expand Up @@ -127,6 +128,7 @@ List of options that can be passed to the plugin.
| disabled | `boolean` | `optional` | `false` | Whether the menu item is disabled. |
| event | `string` | `optional` | | Event name to be emitted when the menu item is clicked. | You can pass a function to be executed instead of an event name. |
| payload | `string` | `optional` | | Payload to be passed to the event. | You can pass any type of data. |
| checked | `boolean` | `optional` | | Whether the menu item is checked. |
| subitems | `MenuItem[]` | `optional` | `[]` | List of sub menu items to be displayed. |
| shortcut | `string` | `optional` | | Keyboard shortcut displayed on the right. |
| icon | `MenuItemIcon` | `optional` | | Icon to be displayed on the left. |
Expand Down Expand Up @@ -230,4 +232,4 @@ import { listen } from "@tauri-apps/api/event";
listen("menu-did-close", () => {
alert("menu closed");
});
```
```
Binary file modified assets/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion examples/ts-utility/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ts-utility-example",
"version": "0.6.2",
"version": "0.7.0",
"main": "index.js",
"type": "module",
"scripts": {
Expand Down
2 changes: 2 additions & 0 deletions examples/ts-utility/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,15 @@ onEventShowMenu('contextmenu', async (_e:MouseEvent) => {
subitems: [
{
label: "My first subitem",
checked: true,
event: () => {
alert('My first subitem clicked');
},
shortcut: "ctrl+m"
},
{
label: "My second subitem",
checked: false,
disabled: true
}
]
Expand Down
2 changes: 1 addition & 1 deletion examples/vanilla/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "vanilla-example",
"version": "0.6.2",
"version": "0.7.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
Expand Down
2 changes: 2 additions & 0 deletions examples/vanilla/public/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,12 @@ window.addEventListener('contextmenu', (e) => {
{
label: "My first subitem",
event: "my_first_subitem",
checked: true,
shortcut: "ctrl+m"
},
{
label: "My second subitem",
checked: false,
disabled: true
}
]
Expand Down
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"version": "0.6.2"
"version": "0.7.0"
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tauri-plugin-context-menu",
"version": "0.6.2",
"version": "0.7.0",
"author": "c2r0b",
"description": "",
"homepage": "https://github.com/c2r0b/tauri-plugin-context-menu",
Expand Down
2 changes: 1 addition & 1 deletion plugin/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tauri-plugin-context-menu",
"version": "0.6.2",
"version": "0.7.0",
"author": "c2r0b",
"type": "module",
"description": "",
Expand Down
1 change: 1 addition & 0 deletions plugin/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface Item {
is_separator?: boolean
event?: string|((e?:CallbackEvent) => any)
payload?: any
checked?: boolean
shortcut?: string
icon?: Icon
subitems?: Item[]
Expand Down
72 changes: 2 additions & 70 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
use serde::Deserialize;
use std::sync::Arc;
use tauri::{
plugin::Builder, plugin::Plugin, plugin::TauriPlugin, Invoke, Manager, Runtime, State, Window,
};
use tauri::{plugin::Builder, plugin::TauriPlugin, Runtime, Window};

mod menu_item;

Expand Down Expand Up @@ -35,81 +32,16 @@ pub struct Position {
is_absolute: Option<bool>,
}

pub struct ContextMenu<R: Runtime> {
invoke_handler: Arc<dyn Fn(Invoke<R>) + Send + Sync>,
}

impl<R: Runtime> Default for ContextMenu<R> {
fn default() -> Self {
Self {
invoke_handler: Arc::new(|_| {}),
}
}
}

impl<R: Runtime> Clone for ContextMenu<R> {
fn clone(&self) -> Self {
Self {
invoke_handler: Arc::clone(&self.invoke_handler),
}
}
}

impl<R: Runtime> ContextMenu<R> {
// Method to create a new ContextMenu
pub fn new<F: 'static + Fn(Invoke<R>) + Send + Sync>(handler: F) -> Self {
Self {
invoke_handler: Arc::new(handler),
}
}

#[cfg(target_os = "linux")]
fn show_context_menu(
&self,
window: Window<R>,
pos: Option<Position>,
items: Option<Vec<MenuItem>>,
) {
os::show_context_menu(window, pos, items);
}

#[cfg(any(target_os = "macos", target_os = "windows"))]
fn show_context_menu(
&self,
window: Window<R>,
pos: Option<Position>,
items: Option<Vec<MenuItem>>,
) {
let context_menu = Arc::new(self.clone());
os::show_context_menu(context_menu, window, pos, items);
}
}

impl<R: Runtime> Plugin<R> for ContextMenu<R> {
fn name(&self) -> &'static str {
"context_menu"
}

fn extend_api(&mut self, invoke: Invoke<R>) {
(self.invoke_handler)(invoke);
}
}

#[tauri::command]
fn show_context_menu<R: Runtime>(
manager: State<'_, ContextMenu<R>>,
window: Window<R>,
pos: Option<Position>,
items: Option<Vec<MenuItem>>,
) {
manager.show_context_menu(window, pos, items);
os::show_context_menu(window, pos, items);
}
pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("context_menu")
.invoke_handler(tauri::generate_handler![show_context_menu])
.setup(|app| {
app.manage(ContextMenu::<R>::default());
Ok(())
})
.build()
}
15 changes: 13 additions & 2 deletions src/linux.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use gdk::{keys::Key, Display, ModifierType};
use gtk::{prelude::*, traits::WidgetExt, AccelFlags, AccelGroup, Menu, MenuItem as GtkMenuItem};
use gtk::{prelude::*, traits::WidgetExt, AccelFlags, AccelGroup, Menu};
use std::{mem, thread::sleep, time};
use tauri::{Runtime, Window};

Expand Down Expand Up @@ -109,7 +109,18 @@ fn append_menu_item<R: Runtime>(
if item.is_separator.unwrap_or(false) {
menu.append(&gtk::SeparatorMenuItem::builder().visible(true).build());
} else {
let menu_item = GtkMenuItem::new();
let menu_item = match item.checked {
Some(state) => {
// Create a CheckMenuItem for checkable items
let check_menu_item = gtk::CheckMenuItem::new();
check_menu_item.set_active(state);
check_menu_item.upcast()
}
None => {
// Create a regular MenuItem for non-checkable items
gtk::MenuItem::new()
}
};

// Create a Box to hold the image and label
let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 0);
Expand Down
25 changes: 14 additions & 11 deletions src/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use tauri::{Runtime, Window};

use crate::keymap::{get_key_map, get_modifier_map};
use crate::macos_window_holder::CURRENT_WINDOW;
use crate::{ContextMenu, MenuItem, Position};
use crate::{MenuItem, Position};

extern "C" fn menu_item_action<R: Runtime>(_self: &Object, _cmd: Sel, _item: id) {
// Get the window from the CURRENT_WINDOW static
Expand Down Expand Up @@ -72,7 +72,7 @@ fn register_menu_item_action<R: Runtime>() -> Sel {
selector(selector_name)
}

fn create_custom_menu_item<R: Runtime>(context_menu: &ContextMenu<R>, option: &MenuItem) -> id {
fn create_custom_menu_item<R: Runtime>(option: &MenuItem) -> id {
// If the item is a separator, return a separator item
if option.is_separator.unwrap_or(false) {
let separator: id = unsafe { msg_send![class!(NSMenuItem), separatorItem] };
Expand Down Expand Up @@ -167,22 +167,26 @@ fn create_custom_menu_item<R: Runtime>(context_menu: &ContextMenu<R>, option: &M
let submenu: id = msg_send![class!(NSMenu), new];
let _: () = msg_send![submenu, setAutoenablesItems:NO];
for subitem in subitems.iter() {
let sub_menu_item: id = create_custom_menu_item(&context_menu, subitem);
let sub_menu_item: id = create_custom_menu_item::<R>(subitem);
let _: () = msg_send![submenu, addItem:sub_menu_item];
}
let _: () = msg_send![item, setSubmenu:submenu];
}

// Handle checkable menu items
let state = match option.checked {
Some(true) => 1,
_ => 0,
};
let _: () = msg_send![item, setState:state];

item
};

menu_item
}

fn create_context_menu<R: Runtime>(
context_menu: &ContextMenu<R>,
options: &[MenuItem],
window: &Window<R>,
) -> id {
fn create_context_menu<R: Runtime>(options: &[MenuItem], window: &Window<R>) -> id {
let _: () = CURRENT_WINDOW.set_window(window.clone());
unsafe {
let title = NSString::alloc(nil).init_str("Menu");
Expand All @@ -192,7 +196,7 @@ fn create_context_menu<R: Runtime>(
let _: () = msg_send![menu, setAutoenablesItems:NO];

for option in options.iter().cloned() {
let item: id = create_custom_menu_item(&context_menu, &option);
let item: id = create_custom_menu_item::<R>(&option);
let _: () = msg_send![menu, addItem:item];
}

Expand All @@ -207,15 +211,14 @@ fn create_context_menu<R: Runtime>(
}

pub fn show_context_menu<R: Runtime>(
context_menu: Arc<ContextMenu<R>>,
window: Window<R>,
pos: Option<Position>,
items: Option<Vec<MenuItem>>,
) {
let main_queue = dispatch::Queue::main();
main_queue.exec_async(move || {
let items_slice = items.as_ref().map(|v| v.as_slice()).unwrap_or(&[]);
let menu = create_context_menu(&*context_menu, items_slice, &window);
let menu = create_context_menu(items_slice, &window);
let location = match pos {
// Convert web page coordinates to screen coordinates
Some(pos) if pos.x != 0.0 || pos.y != 0.0 => unsafe {
Expand Down
2 changes: 2 additions & 0 deletions src/menu_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub struct MenuItem {
pub payload: Option<String>,
pub subitems: Option<Vec<MenuItem>>,
pub icon: Option<MenuItemIcon>,
pub checked: Option<bool>,
pub is_separator: Option<bool>,
}

Expand All @@ -29,6 +30,7 @@ impl Default for MenuItem {
payload: None,
subitems: None,
icon: None,
checked: Some(false),
is_separator: Some(false),
}
}
Expand Down
12 changes: 8 additions & 4 deletions src/win.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ use winapi::{
um::winuser::{
AppendMenuW, ClientToScreen, CreatePopupMenu, DestroyMenu, DispatchMessageW, GetCursorPos,
GetMessageW, PostQuitMessage, SetMenuItemBitmaps, TrackPopupMenu, TranslateMessage,
MF_BYCOMMAND, MF_DISABLED, MF_ENABLED, MF_POPUP, MF_SEPARATOR, MF_STRING, MSG,
TPM_LEFTALIGN, TPM_RIGHTBUTTON, TPM_TOPALIGN, WM_COMMAND, WM_HOTKEY,
MF_BYCOMMAND, MF_CHECKED, MF_DISABLED, MF_ENABLED, MF_POPUP, MF_SEPARATOR, MF_STRING, MSG,
TPM_LEFTALIGN, TPM_RIGHTBUTTON, TPM_TOPALIGN, WM_COMMAND,
},
};

use crate::keymap::get_key_map;
use crate::win_image_handler::{convert_to_hbitmap, load_bitmap_from_file};
use crate::{ContextMenu, MenuItem, Position};
use crate::{MenuItem, Position};

const ID_MENU_ITEM_BASE: u32 = 1000;

Expand Down Expand Up @@ -74,6 +74,11 @@ fn append_menu_item(menu: HMENU, item: &MenuItem, counter: &mut u32) -> Result<u
flags |= MF_ENABLED;
}

// Check if the item is checkable and set the initial state
if item.checked.unwrap_or(false) {
flags |= MF_CHECKED;
}

if let Some(subitems) = &item.subitems {
let submenu = unsafe { CreatePopupMenu() };
for subitem in subitems.iter() {
Expand Down Expand Up @@ -137,7 +142,6 @@ pub fn handle_menu_item_click<R: Runtime>(id: u32, window: Window<R>) {
}

pub fn show_context_menu<R: Runtime>(
_context_menu: Arc<ContextMenu<R>>,
window: Window<R>,
pos: Option<Position>,
items: Option<Vec<MenuItem>>,
Expand Down

0 comments on commit a4161bf

Please sign in to comment.