Added menu
functionality. (#58)
* Added core::Channel and menu functionality. core::Channel may leak memory. * Added leptos example.
This commit is contained in:
parent
aa3a64af11
commit
115009d4bf
44 changed files with 1319 additions and 13 deletions
|
@ -1,4 +1,7 @@
|
|||
// tauri/tooling/api/src/core.ts
|
||||
function transformCallback(callback, once = false) {
|
||||
return window.__TAURI_INTERNALS__.transformCallback(callback, once)
|
||||
}
|
||||
async function invoke(cmd, args = {}) {
|
||||
// NB: `options` ignored as not used here.
|
||||
return window.__TAURI_INTERNALS__.invoke(cmd, args)
|
||||
|
@ -8,5 +11,6 @@ function convertFileSrc(filePath, protocol = 'asset') {
|
|||
}
|
||||
export {
|
||||
invoke,
|
||||
convertFileSrc
|
||||
convertFileSrc,
|
||||
transformCallback,
|
||||
}
|
||||
|
|
64
src/core.rs
64
src/core.rs
|
@ -1,8 +1,10 @@
|
|||
//! Common functionality
|
||||
use std::path::PathBuf;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use futures::{channel::mpsc, future::FusedFuture, Stream, StreamExt};
|
||||
use serde::{de::DeserializeOwned, ser::SerializeStruct, Serialize};
|
||||
use serde_wasm_bindgen as swb;
|
||||
use wasm_bindgen::{prelude::Closure, JsValue};
|
||||
|
||||
pub async fn invoke<T>(command: &str, args: impl Serialize) -> T
|
||||
where
|
||||
|
@ -38,8 +40,62 @@ pub fn convert_file_src_with_protocol(
|
|||
.unwrap()
|
||||
}
|
||||
|
||||
// TODO: Could cause memory leak because handler is never released.
|
||||
#[derive(Debug)]
|
||||
pub struct Channel<T> {
|
||||
id: usize,
|
||||
rx: mpsc::UnboundedReceiver<T>,
|
||||
}
|
||||
|
||||
impl<T> Channel<T> {
|
||||
pub fn new() -> Self
|
||||
where
|
||||
T: DeserializeOwned + 'static,
|
||||
{
|
||||
let (tx, rx) = mpsc::unbounded::<T>();
|
||||
let closure = Closure::<dyn FnMut(JsValue)>::new(move |raw| {
|
||||
let _ = tx.unbounded_send(serde_wasm_bindgen::from_value(raw).unwrap());
|
||||
});
|
||||
|
||||
let id = inner::transform_callback(&closure, false);
|
||||
closure.forget();
|
||||
|
||||
Channel { id, rx }
|
||||
}
|
||||
|
||||
pub fn id(&self) -> usize {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Serialize for Channel<T> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut map = serializer.serialize_struct("Channel", 2)?;
|
||||
map.serialize_field("__TAURI_CHANNEL_MARKER__", &true)?;
|
||||
map.serialize_field("id", &self.id)?;
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Stream for Channel<T> {
|
||||
type Item = T;
|
||||
|
||||
fn poll_next(
|
||||
mut self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Option<Self::Item>> {
|
||||
self.rx.poll_next_unpin(cx)
|
||||
}
|
||||
}
|
||||
|
||||
mod inner {
|
||||
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
|
||||
use wasm_bindgen::{
|
||||
prelude::{wasm_bindgen, Closure},
|
||||
JsValue,
|
||||
};
|
||||
|
||||
#[wasm_bindgen(module = "/src/core.js")]
|
||||
extern "C" {
|
||||
|
@ -48,5 +104,7 @@ mod inner {
|
|||
pub async fn invoke_result(cmd: &str, args: JsValue) -> Result<JsValue, JsValue>;
|
||||
#[wasm_bindgen(js_name = "convertFileSrc")]
|
||||
pub fn convert_file_src(filePath: &str, protocol: &str) -> JsValue;
|
||||
#[wasm_bindgen(js_name = "transformCallback")]
|
||||
pub fn transform_callback(callback: &Closure<dyn FnMut(JsValue)>, once: bool) -> usize;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,6 +95,9 @@ pub mod core;
|
|||
#[cfg(feature = "dpi")]
|
||||
pub mod dpi;
|
||||
|
||||
#[cfg(feature = "menu")]
|
||||
pub mod menu;
|
||||
|
||||
#[cfg(feature = "window")]
|
||||
pub mod window;
|
||||
|
||||
|
|
0
src/menu.js
Normal file
0
src/menu.js
Normal file
315
src/menu.rs
Normal file
315
src/menu.rs
Normal file
|
@ -0,0 +1,315 @@
|
|||
//! # See also
|
||||
//! + `tauri::menu`
|
||||
use crate::{core, window};
|
||||
use serde::{ser::SerializeStruct, Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
type Rid = usize;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Menu {
|
||||
rid: Rid,
|
||||
id: MenuId,
|
||||
channel: Option<core::Channel<Message<String>>>,
|
||||
}
|
||||
|
||||
impl Menu {
|
||||
pub async fn with_id(id: impl Into<MenuId>) -> Self {
|
||||
#[derive(Serialize)]
|
||||
struct Args {
|
||||
kind: String,
|
||||
options: MenuOptions,
|
||||
handler: ChannelId,
|
||||
}
|
||||
|
||||
let options = MenuOptions {
|
||||
id: Some(id.into()),
|
||||
};
|
||||
|
||||
let channel = core::Channel::new();
|
||||
|
||||
let (rid, id) = core::invoke::<(Rid, String)>(
|
||||
"plugin:menu|new",
|
||||
Args {
|
||||
kind: ItemKind::Menu.as_str().to_string(),
|
||||
options,
|
||||
handler: ChannelId::from(&channel),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
Self {
|
||||
rid,
|
||||
id: id.into(),
|
||||
channel: Some(channel),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn default() -> Self {
|
||||
let (rid, id) = core::invoke::<(Rid, String)>("plugin:menu|create_default", ()).await;
|
||||
Self {
|
||||
rid,
|
||||
id: id.into(),
|
||||
channel: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Menu {
|
||||
pub fn rid(&self) -> Rid {
|
||||
self.rid
|
||||
}
|
||||
|
||||
pub fn kind() -> &'static str {
|
||||
ItemKind::Menu.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl Menu {
|
||||
pub async fn append_item(&self, item: &item::MenuItem) -> Result<(), ()> {
|
||||
core::invoke_result(
|
||||
"plugin:menu|append",
|
||||
AppendItemArgs {
|
||||
rid: self.rid,
|
||||
kind: Self::kind().to_string(),
|
||||
items: vec![(item.rid(), item::MenuItem::kind().to_string())],
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Popup this menu as a context menu on the specified window.
|
||||
/// If the position, is provided, it is relative to the window's top-left corner.
|
||||
pub async fn popup(&self) -> Result<(), ()> {
|
||||
#[derive(Serialize)]
|
||||
struct Position {
|
||||
x: isize,
|
||||
y: isize,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Args {
|
||||
rid: Rid,
|
||||
kind: String,
|
||||
window: Option<window::WindowLabel>,
|
||||
at: Option<HashMap<String, Position>>,
|
||||
}
|
||||
|
||||
core::invoke_result(
|
||||
"plugin:menu|popup",
|
||||
Args {
|
||||
rid: self.rid,
|
||||
kind: Self::kind().to_string(),
|
||||
window: None,
|
||||
at: None,
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl Menu {
|
||||
pub fn listen(&mut self) -> Option<&mut core::Channel<Message<String>>> {
|
||||
self.channel.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct AppendItemArgs {
|
||||
rid: Rid,
|
||||
kind: String,
|
||||
items: Vec<(Rid, String)>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone, derive_more::From, Debug)]
|
||||
#[serde(transparent)]
|
||||
pub struct MenuId(pub String);
|
||||
|
||||
impl From<&'static str> for MenuId {
|
||||
fn from(value: &'static str) -> Self {
|
||||
Self(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(derive_more::Deref, Deserialize, Debug)]
|
||||
pub struct Message<T> {
|
||||
id: usize,
|
||||
|
||||
#[deref]
|
||||
message: T,
|
||||
}
|
||||
|
||||
impl<T> Message<T> {
|
||||
pub fn id(&self) -> usize {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct MenuOptions {
|
||||
id: Option<MenuId>,
|
||||
}
|
||||
|
||||
enum ItemKind {
|
||||
MenuItem,
|
||||
Predefined,
|
||||
Check,
|
||||
Icon,
|
||||
Submenu,
|
||||
Menu,
|
||||
}
|
||||
|
||||
impl ItemKind {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::MenuItem => "MenuItem",
|
||||
Self::Predefined => "Predefined",
|
||||
Self::Check => "Check",
|
||||
Self::Icon => "Icon",
|
||||
Self::Submenu => "Submenu",
|
||||
Self::Menu => "Menu",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ChannelId {
|
||||
id: usize,
|
||||
}
|
||||
|
||||
impl ChannelId {
|
||||
pub fn from<T>(channel: &core::Channel<T>) -> Self {
|
||||
Self { id: channel.id() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for ChannelId {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut map = serializer.serialize_struct("ChannelId", 2)?;
|
||||
map.serialize_field("__TAURI_CHANNEL_MARKER__", &true)?;
|
||||
map.serialize_field("id", &self.id)?;
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
|
||||
pub mod item {
|
||||
use super::{ChannelId, ItemKind, MenuId, Message, Rid};
|
||||
use crate::core;
|
||||
use futures::{Stream, StreamExt};
|
||||
use serde::Serialize;
|
||||
|
||||
pub struct MenuItem {
|
||||
rid: Rid,
|
||||
id: MenuId,
|
||||
channel: core::Channel<Message<String>>,
|
||||
}
|
||||
|
||||
impl MenuItem {
|
||||
pub async fn with_id(text: impl Into<String>, id: impl Into<MenuId>) -> Self {
|
||||
let mut options = MenuItemOptions::new(text);
|
||||
options.set_id(id);
|
||||
|
||||
Self::with_options(options).await
|
||||
}
|
||||
|
||||
pub async fn with_options(options: MenuItemOptions) -> Self {
|
||||
#[derive(Serialize)]
|
||||
struct Args {
|
||||
kind: String,
|
||||
options: MenuItemOptions,
|
||||
handler: ChannelId,
|
||||
}
|
||||
|
||||
let channel = core::Channel::new();
|
||||
|
||||
let (rid, id) = core::invoke::<(Rid, String)>(
|
||||
"plugin:menu|new",
|
||||
Args {
|
||||
kind: ItemKind::MenuItem.as_str().to_string(),
|
||||
options,
|
||||
handler: ChannelId::from(&channel),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
Self {
|
||||
rid,
|
||||
id: id.into(),
|
||||
channel,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MenuItem {
|
||||
pub fn rid(&self) -> Rid {
|
||||
self.rid
|
||||
}
|
||||
|
||||
pub fn kind() -> &'static str {
|
||||
ItemKind::MenuItem.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl MenuItem {
|
||||
// pub fn listen(&mut self) -> impl Stream<Item = Message<String>> {
|
||||
// self.channel.map(|message| message.message)
|
||||
// }
|
||||
|
||||
pub fn listen(&mut self) -> &mut core::Channel<Message<String>> {
|
||||
&mut self.channel
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct MenuItemOptions {
|
||||
/// Specify an id to use for the new menu item.
|
||||
id: Option<MenuId>,
|
||||
|
||||
/// The text of the new menu item.
|
||||
text: String,
|
||||
|
||||
/// Whether the new menu item is enabled or not.
|
||||
enabled: Option<bool>,
|
||||
|
||||
/// Specify an accelerator for the new menu item.
|
||||
accelerator: Option<String>,
|
||||
}
|
||||
|
||||
impl MenuItemOptions {
|
||||
pub fn new(text: impl Into<String>) -> Self {
|
||||
Self {
|
||||
id: None,
|
||||
text: text.into(),
|
||||
enabled: None,
|
||||
accelerator: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_id(&mut self, id: impl Into<MenuId>) -> &mut Self {
|
||||
let _ = self.id.insert(id.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_enabled(&mut self, enabled: bool) -> &mut Self {
|
||||
let _ = self.enabled.insert(enabled);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_accelerator(&mut self, accelerator: impl Into<String>) -> &mut Self {
|
||||
let _ = self.accelerator.insert(accelerator.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod inner {
|
||||
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
|
||||
|
||||
#[wasm_bindgen(module = "/src/menu.js")]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_name = "getCurrent")]
|
||||
pub fn get_current() -> JsValue;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
//! Provides APIs to create windows, communicate with other windows and manipulate the current window.
|
||||
//! Provides APIs to create windows, communicate with other windows, and manipulate the current window.
|
||||
//!
|
||||
//! ## Window events
|
||||
//! Events can be listened to using [`Window::listen`].
|
||||
|
@ -90,8 +90,8 @@ impl Stream for DragDropListen {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct WindowLabel {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub(crate) struct WindowLabel {
|
||||
label: String,
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue