Add menu functionality (#59)

* Added core::Channel and menu functionality. core::Channel may leak memory.

* Updated examples to v2 using Leptos.
This commit is contained in:
bicarlsen 2024-08-06 18:20:50 +02:00 committed by GitHub
parent 115009d4bf
commit ae49310ee1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 5712 additions and 4216 deletions

View file

@ -1,11 +1,10 @@
//! Common functionality
use std::collections::HashMap;
use futures::{channel::mpsc, future::FusedFuture, Stream, StreamExt};
use serde::{de::DeserializeOwned, ser::SerializeStruct, Serialize};
use serde::{de::DeserializeOwned, Serialize};
use serde_wasm_bindgen as swb;
use wasm_bindgen::{prelude::Closure, JsValue};
pub use channel::{Channel, Message};
pub async fn invoke<T>(command: &str, args: impl Serialize) -> T
where
T: DeserializeOwned,
@ -40,54 +39,77 @@ 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>,
}
mod channel {
use super::inner;
use futures::{channel::mpsc, Stream, StreamExt};
use serde::{de::DeserializeOwned, ser::SerializeStruct, Deserialize, Serialize};
use wasm_bindgen::{prelude::Closure, JsValue};
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());
});
#[derive(derive_more::Deref, Deserialize, Debug)]
pub struct Message<T> {
id: usize,
let id = inner::transform_callback(&closure, false);
closure.forget();
Channel { id, rx }
#[deref]
message: T,
}
pub fn id(&self) -> usize {
self.id
impl<T> Message<T> {
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()
// TODO: Could cause memory leak because handler is never released.
#[derive(Debug)]
pub struct Channel<T> {
id: usize,
rx: mpsc::UnboundedReceiver<Message<T>>,
}
}
impl<T> Stream for Channel<T> {
type Item = T;
impl<T> Channel<T> {
pub fn new() -> Self
where
T: DeserializeOwned + 'static,
{
let (tx, rx) = mpsc::unbounded::<Message<T>>();
let closure = Closure::<dyn FnMut(JsValue)>::new(move |raw| {
let _ = tx.unbounded_send(serde_wasm_bindgen::from_value(raw).unwrap());
});
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)
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)
.map(|item| item.map(|value| value.message))
}
}
}

View file

@ -10,39 +10,68 @@ type Rid = usize;
pub struct Menu {
rid: Rid,
id: MenuId,
channel: Option<core::Channel<Message<String>>>,
channel: Option<core::Channel<String>>,
}
impl Menu {
pub async fn with_id(id: impl Into<MenuId>) -> Self {
let (this, _) = Self::new(Some(id.into()), vec![]).await;
this
}
pub async fn with_items(items: Vec<NewMenuItem>) -> (Self, Vec<Option<core::Channel<String>>>) {
Self::new(None, items).await
}
pub async fn with_id_and_items(
id: impl Into<MenuId>,
items: Vec<NewMenuItem>,
) -> (Self, Vec<Option<core::Channel<String>>>) {
Self::new(Some(id.into()), items).await
}
async fn new(
id: Option<MenuId>,
items: Vec<NewMenuItem>,
) -> (Self, Vec<Option<core::Channel<String>>>) {
#[derive(Serialize)]
struct Args {
kind: String,
options: MenuOptions,
options: NewMenuOptions,
handler: ChannelId,
}
let options = MenuOptions {
id: Some(id.into()),
};
let channel = core::Channel::new();
let (items, item_channels) = items
.into_iter()
.map(|mut item| match item {
NewMenuItem::MenuItemsOptions(ref mut value) => {
let channel = core::Channel::new();
value.set_handler_channel_id(channel.id());
(item, Some(channel))
}
})
.unzip();
let options = NewMenuOptions { id, items };
let (rid, id) = core::invoke::<(Rid, String)>(
"plugin:menu|new",
Args {
kind: ItemKind::Menu.as_str().to_string(),
kind: ItemId::Menu.as_str().to_string(),
options,
handler: ChannelId::from(&channel),
},
)
.await;
Self {
rid,
id: id.into(),
channel: Some(channel),
}
(
Self {
rid,
id: id.into(),
channel: Some(channel),
},
item_channels,
)
}
pub async fn default() -> Self {
@ -61,7 +90,7 @@ impl Menu {
}
pub fn kind() -> &'static str {
ItemKind::Menu.as_str()
ItemId::Menu.as_str()
}
}
@ -109,7 +138,7 @@ impl Menu {
}
impl Menu {
pub fn listen(&mut self) -> Option<&mut core::Channel<Message<String>>> {
pub fn listen(&mut self) -> Option<&mut core::Channel<String>> {
self.channel.as_mut()
}
}
@ -131,26 +160,24 @@ impl From<&'static str> for MenuId {
}
}
#[derive(derive_more::Deref, Deserialize, Debug)]
pub struct Message<T> {
id: usize,
#[deref]
message: T,
#[derive(Serialize)]
struct NewMenuOptions {
id: Option<MenuId>,
items: Vec<NewMenuItem>,
}
impl<T> Message<T> {
pub fn id(&self) -> usize {
self.id
}
#[derive(Serialize, derive_more::From)]
#[serde(untagged)]
pub enum NewMenuItem {
MenuItemsOptions(item::MenuItemOptions),
}
#[derive(Serialize)]
struct MenuOptions {
id: Option<MenuId>,
enum OptionsKind {
MenuItem(item::MenuItemOptions),
}
enum ItemKind {
enum ItemId {
MenuItem,
Predefined,
Check,
@ -159,7 +186,7 @@ enum ItemKind {
Menu,
}
impl ItemKind {
impl ItemId {
pub fn as_str(&self) -> &'static str {
match self {
Self::MenuItem => "MenuItem",
@ -195,15 +222,14 @@ impl Serialize for ChannelId {
}
pub mod item {
use super::{ChannelId, ItemKind, MenuId, Message, Rid};
use super::{ChannelId, ItemId, MenuId, Rid};
use crate::core;
use futures::{Stream, StreamExt};
use serde::Serialize;
use serde::{ser::SerializeStruct, Serialize};
pub struct MenuItem {
rid: Rid,
id: MenuId,
channel: core::Channel<Message<String>>,
channel: core::Channel<String>,
}
impl MenuItem {
@ -227,7 +253,7 @@ pub mod item {
let (rid, id) = core::invoke::<(Rid, String)>(
"plugin:menu|new",
Args {
kind: ItemKind::MenuItem.as_str().to_string(),
kind: ItemId::MenuItem.as_str().to_string(),
options,
handler: ChannelId::from(&channel),
},
@ -248,16 +274,12 @@ pub mod item {
}
pub fn kind() -> &'static str {
ItemKind::MenuItem.as_str()
ItemId::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>> {
pub fn listen(&mut self) -> &mut core::Channel<String> {
&mut self.channel
}
}
@ -275,6 +297,10 @@ pub mod item {
/// Specify an accelerator for the new menu item.
accelerator: Option<String>,
/// Id to the channel handler.
#[serde(rename = "handler")]
handler_channel_id: Option<HandlerChannelId>,
}
impl MenuItemOptions {
@ -284,6 +310,7 @@ pub mod item {
text: text.into(),
enabled: None,
accelerator: None,
handler_channel_id: None,
}
}
@ -301,6 +328,26 @@ pub mod item {
let _ = self.accelerator.insert(accelerator.into());
self
}
/// Set the handler channel id directly.
pub(crate) fn set_handler_channel_id(&mut self, id: usize) -> &mut Self {
let _ = self.handler_channel_id.insert(id.into());
self
}
}
#[derive(derive_more::From)]
struct HandlerChannelId(usize);
impl Serialize for HandlerChannelId {
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.0)?;
map.end()
}
}
}