feat(api): add Image class (#9042)

* feat(api): add `Image` class

* clippy

* license headers

* small cleanup

* fixes

* code review

* readd from_png_bytes and from_ico_bytes

---------

Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
This commit is contained in:
Amr Bashir 2024-03-03 04:31:08 +02:00 committed by GitHub
parent 2ca9afb576
commit 77b9a508a4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 432 additions and 83 deletions

View File

@ -1,5 +1,6 @@
---
'tauri': 'minor:feat'
'@tauri-apps/api': 'minor:feat'
---
Add `Image` type.
Add a new `Image` type in Rust and JS.

7
Cargo.lock generated
View File

@ -3418,12 +3418,6 @@ dependencies = [
"loom",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "string_cache"
version = "0.8.7"
@ -3630,7 +3624,6 @@ dependencies = [
"serde_repr",
"serialize-to-javascript",
"state",
"static_assertions",
"swift-rs",
"tauri",
"tauri-build",

View File

@ -72,7 +72,6 @@ png = { version = "0.17", optional = true }
ico = { version = "0.3.0", optional = true }
http-range = { version = "0.1.5", optional = true }
tracing = { version = "0.1", optional = true }
static_assertions = "1"
heck = "0.4"
[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\", target_os = \"windows\", target_os = \"macos\"))".dependencies]

View File

@ -137,6 +137,19 @@ const PLUGINS: &[(&str, &[(&str, bool)])] = &[
("app_hide", false),
],
),
(
"image",
&[
("new", true),
("from_bytes", true),
("from_png_bytes", true),
("from_ico_bytes", true),
("from_path", true),
("rgba", true),
("width", true),
("height", true),
],
),
("resources", &[("close", true)]),
(
"menu",

View File

@ -0,0 +1,19 @@
| Permission | Description |
|------|-----|
|`allow-from-bytes`|Enables the from_bytes command without any pre-configured scope.|
|`deny-from-bytes`|Denies the from_bytes command without any pre-configured scope.|
|`allow-from-ico-bytes`|Enables the from_ico_bytes command without any pre-configured scope.|
|`deny-from-ico-bytes`|Denies the from_ico_bytes command without any pre-configured scope.|
|`allow-from-path`|Enables the from_path command without any pre-configured scope.|
|`deny-from-path`|Denies the from_path command without any pre-configured scope.|
|`allow-from-png-bytes`|Enables the from_png_bytes command without any pre-configured scope.|
|`deny-from-png-bytes`|Denies the from_png_bytes command without any pre-configured scope.|
|`allow-height`|Enables the height command without any pre-configured scope.|
|`deny-height`|Denies the height command without any pre-configured scope.|
|`allow-new`|Enables the new command without any pre-configured scope.|
|`deny-new`|Denies the new command without any pre-configured scope.|
|`allow-rgba`|Enables the rgba command without any pre-configured scope.|
|`deny-rgba`|Denies the rgba command without any pre-configured scope.|
|`allow-width`|Enables the width command without any pre-configured scope.|
|`deny-width`|Denies the width command without any pre-configured scope.|
|`default`|Default permissions for the plugin.|

File diff suppressed because one or more lines are too long

View File

@ -870,6 +870,7 @@ impl<R: Runtime> App<R> {
self.handle.plugin(crate::webview::plugin::init())?;
self.handle.plugin(crate::app::plugin::init())?;
self.handle.plugin(crate::resources::plugin::init())?;
self.handle.plugin(crate::image::plugin::init())?;
#[cfg(desktop)]
self.handle.plugin(crate::menu::plugin::init())?;
#[cfg(all(desktop, feature = "tray-icon"))]

View File

@ -2,8 +2,13 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
pub mod plugin;
use std::borrow::Cow;
use std::io::{Error, ErrorKind};
use std::sync::Arc;
use crate::{Manager, Resource, ResourceId, Runtime};
/// An RGBA Image in row-major order from top to bottom.
#[derive(Debug, Clone)]
@ -13,6 +18,21 @@ pub struct Image<'a> {
height: u32,
}
impl Resource for Image<'static> {}
impl Image<'static> {
/// Creates a new Image using RGBA data, in row-major order from top to bottom, and with specified width and height.
///
/// Similar to [`Self::new`] but avoids cloning the rgba data to get an owned Image.
pub const fn new_owned(rgba: Vec<u8>, width: u32, height: u32) -> Self {
Self {
rgba: Cow::Owned(rgba),
width,
height,
}
}
}
impl<'a> Image<'a> {
/// Creates a new Image using RGBA data, in row-major order from top to bottom, and with specified width and height.
pub const fn new(rgba: &'a [u8], width: u32, height: u32) -> Self {
@ -123,6 +143,19 @@ impl<'a> Image<'a> {
pub fn height(&self) -> u32 {
self.height
}
/// Convert into a 'static owned [`Image`].
/// This will allocate.
pub fn to_owned(self) -> Image<'static> {
Image {
rgba: match self.rgba {
Cow::Owned(v) => Cow::Owned(v),
Cow::Borrowed(v) => Cow::Owned(v.to_vec()),
},
height: self.height,
width: self.width,
}
}
}
impl<'a> From<Image<'a>> for crate::runtime::Icon<'a> {
@ -153,12 +186,12 @@ impl TryFrom<Image<'_>> for tray_icon::Icon {
}
}
#[cfg(desktop)]
#[derive(serde::Deserialize)]
#[serde(untagged)]
pub enum JsIcon<'a> {
pub enum JsImage<'a> {
Path(std::path::PathBuf),
Bytes(&'a [u8]),
Resource(ResourceId),
Rgba {
rgba: &'a [u8],
width: u32,
@ -166,23 +199,24 @@ pub enum JsIcon<'a> {
},
}
#[cfg(desktop)]
impl<'a> TryFrom<JsIcon<'a>> for Image<'a> {
type Error = crate::Error;
fn try_from(img: JsIcon<'a>) -> Result<Self, Self::Error> {
match img {
impl<'a> JsImage<'a> {
pub fn into_img<R: Runtime, M: Manager<R>>(self, app: &M) -> crate::Result<Arc<Image<'a>>> {
match self {
Self::Resource(rid) => {
let resources_table = app.resources_table();
resources_table.get::<Image<'static>>(rid)
}
#[cfg(any(feature = "image-ico", feature = "image-png"))]
JsIcon::Path(path) => Self::from_path(path).map_err(Into::into),
Self::Path(path) => Image::from_path(path).map(Arc::new).map_err(Into::into),
#[cfg(any(feature = "image-ico", feature = "image-png"))]
JsIcon::Bytes(bytes) => Self::from_bytes(bytes).map_err(Into::into),
Self::Bytes(bytes) => Image::from_bytes(bytes).map(Arc::new).map_err(Into::into),
JsIcon::Rgba {
Self::Rgba {
rgba,
width,
height,
} => Ok(Self::new(rgba, width, height)),
} => Ok(Arc::new(Image::new(rgba, width, height))),
#[cfg(not(any(feature = "image-ico", feature = "image-png")))]
_ => Err(
@ -190,9 +224,9 @@ impl<'a> TryFrom<JsIcon<'a>> for Image<'a> {
ErrorKind::InvalidInput,
format!(
"expected RGBA image data, found {}",
match img {
JsIcon::Path(_) => "a file path",
JsIcon::Bytes(_) => "raw bytes",
match self {
JsImage::Path(_) => "a file path",
JsImage::Bytes(_) => "raw bytes",
_ => unreachable!(),
}
),

View File

@ -0,0 +1,116 @@
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::plugin::{Builder, TauriPlugin};
use crate::{command, AppHandle, Image, Manager, ResourceId, Runtime};
#[command(root = "crate")]
fn new<R: Runtime>(
app: AppHandle<R>,
rgba: Vec<u8>,
width: u32,
height: u32,
) -> crate::Result<ResourceId> {
let image = Image::new_owned(rgba, width, height);
let mut resources_table = app.resources_table();
let rid = resources_table.add(image);
Ok(rid)
}
#[cfg(any(feature = "image-ico", feature = "image-png"))]
#[command(root = "crate")]
fn from_bytes<R: Runtime>(app: AppHandle<R>, bytes: Vec<u8>) -> crate::Result<ResourceId> {
let image = Image::from_bytes(&bytes)?.to_owned();
let mut resources_table = app.resources_table();
let rid = resources_table.add(image);
Ok(rid)
}
#[cfg(not(any(feature = "image-ico", feature = "image-png")))]
#[command(root = "crate")]
fn from_bytes() -> std::result::Result<(), &'static str> {
Err("from_bytes is only supported if the `image-ico` or `image-png` Cargo features are enabled")
}
#[cfg(feature = "image-ico")]
#[command(root = "crate")]
fn from_ico_bytes<R: Runtime>(app: AppHandle<R>, bytes: Vec<u8>) -> crate::Result<ResourceId> {
let image = Image::from_ico_bytes(&bytes)?.to_owned();
let mut resources_table = app.resources_table();
let rid = resources_table.add(image);
Ok(rid)
}
#[cfg(not(feature = "image-ico"))]
#[command(root = "crate")]
fn from_ico_bytes() -> std::result::Result<(), &'static str> {
Err("from_ico_bytes is only supported if the `image-ico` Cargo feature is enabled")
}
#[cfg(feature = "image-png")]
#[command(root = "crate")]
fn from_png_bytes<R: Runtime>(app: AppHandle<R>, bytes: Vec<u8>) -> crate::Result<ResourceId> {
let image = Image::from_png_bytes(&bytes)?.to_owned();
let mut resources_table = app.resources_table();
let rid = resources_table.add(image);
Ok(rid)
}
#[cfg(not(feature = "image-png"))]
#[command(root = "crate")]
fn from_png_bytes() -> std::result::Result<(), &'static str> {
Err("from_png_bytes is only supported if the `image-ico` Cargo feature is enabled")
}
#[cfg(any(feature = "image-ico", feature = "image-png"))]
#[command(root = "crate")]
fn from_path<R: Runtime>(app: AppHandle<R>, path: std::path::PathBuf) -> crate::Result<ResourceId> {
let image = Image::from_path(path)?.to_owned();
let mut resources_table = app.resources_table();
let rid = resources_table.add(image);
Ok(rid)
}
#[cfg(not(any(feature = "image-ico", feature = "image-png")))]
#[command(root = "crate")]
fn from_path() -> std::result::Result<(), &'static str> {
Err("from_path is only supported if the `image-ico` or `image-png` Cargo features are enabled")
}
#[command(root = "crate")]
fn rgba<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> crate::Result<Vec<u8>> {
let resources_table = app.resources_table();
let image = resources_table.get::<Image<'_>>(rid)?;
Ok(image.rgba().to_vec())
}
#[command(root = "crate")]
fn width<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> crate::Result<u32> {
let resources_table = app.resources_table();
let image = resources_table.get::<Image<'_>>(rid)?;
Ok(image.width())
}
#[command(root = "crate")]
fn height<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> crate::Result<u32> {
let resources_table = app.resources_table();
let image = resources_table.get::<Image<'_>>(rid)?;
Ok(image.height())
}
/// Initializes the plugin.
pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("image")
.invoke_handler(crate::generate_handler![
new,
from_bytes,
from_ico_bytes,
from_png_bytes,
from_path,
rgba,
width,
height
])
.build()
}

View File

@ -13,7 +13,7 @@ use tauri_runtime::window::dpi::Position;
use super::{sealed::ContextMenuBase, *};
use crate::{
command,
image::JsIcon,
image::JsImage,
ipc::{channel::JavaScriptChannelId, Channel},
plugin::{Builder, TauriPlugin},
resources::{ResourceId, ResourceTable},
@ -46,29 +46,30 @@ pub(crate) struct AboutMetadata<'a> {
pub website_label: Option<String>,
pub credits: Option<String>,
#[serde(borrow)]
pub icon: Option<JsIcon<'a>>,
pub icon: Option<JsImage<'a>>,
}
impl<'a> TryFrom<AboutMetadata<'a>> for super::AboutMetadata<'a> {
type Error = crate::Error;
fn try_from(value: AboutMetadata<'a>) -> Result<Self, Self::Error> {
let icon = match value.icon {
Some(i) => Some(i.try_into()?),
impl<'a> AboutMetadata<'a> {
pub fn into_metdata<R: Runtime, M: Manager<R>>(
self,
app: &M,
) -> crate::Result<super::AboutMetadata<'a>> {
let icon = match self.icon {
Some(i) => Some(i.into_img(app)?.as_ref().clone()),
None => None,
};
Ok(Self {
name: value.name,
version: value.version,
short_version: value.short_version,
authors: value.authors,
comments: value.comments,
copyright: value.copyright,
license: value.license,
website: value.website,
website_label: value.website_label,
credits: value.credits,
Ok(super::AboutMetadata {
name: self.name,
version: self.version,
short_version: self.short_version,
authors: self.authors,
comments: self.comments,
copyright: self.copyright,
license: self.license,
website: self.website,
website_label: self.website_label,
credits: self.credits,
icon,
})
}
@ -173,7 +174,7 @@ impl CheckMenuItemPayload {
enum Icon<'a> {
Native(NativeIcon),
#[serde(borrow)]
Icon(JsIcon<'a>),
Icon(JsImage<'a>),
}
#[derive(Deserialize)]
@ -203,7 +204,7 @@ impl<'a> IconMenuItemPayload<'a> {
}
builder = match self.icon {
Icon::Native(native_icon) => builder.native_icon(native_icon),
Icon::Icon(icon) => builder.icon(icon.try_into()?),
Icon::Icon(icon) => builder.icon(icon.into_img(webview)?.as_ref().clone()),
};
let item = builder.build(webview)?;
@ -291,7 +292,7 @@ impl<'a> PredefinedMenuItemPayload<'a> {
Predefined::Quit => PredefinedMenuItem::quit(webview, self.text.as_deref()),
Predefined::About(metadata) => {
let metadata = match metadata {
Some(m) => Some(m.try_into()?),
Some(m) => Some(m.into_metdata(webview)?),
None => None,
};
PredefinedMenuItem::about(webview, self.text.as_deref(), metadata)
@ -852,7 +853,7 @@ fn set_icon<R: Runtime>(
match icon {
Some(Icon::Native(icon)) => icon_item.set_native_icon(Some(icon)),
Some(Icon::Icon(icon)) => icon_item.set_icon(Some(icon.try_into()?)),
Some(Icon::Icon(icon)) => icon_item.set_icon(Some(icon.into_img(&app)?.as_ref().clone())),
None => {
icon_item.set_icon(None)?;
icon_item.set_native_icon(None)?;

View File

@ -8,7 +8,7 @@ use serde::Deserialize;
use crate::{
command,
image::JsIcon,
image::JsImage,
ipc::Channel,
menu::{plugin::ItemKind, Menu, Submenu},
plugin::{Builder, TauriPlugin},
@ -25,7 +25,7 @@ struct TrayIconOptions<'a> {
id: Option<String>,
menu: Option<(ResourceId, ItemKind)>,
#[serde(borrow)]
icon: Option<JsIcon<'a>>,
icon: Option<JsImage<'a>>,
tooltip: Option<String>,
title: Option<String>,
temp_dir_path: Option<PathBuf>,
@ -65,7 +65,7 @@ fn new<R: Runtime>(
};
}
if let Some(icon) = options.icon {
builder = builder.icon(icon.try_into()?);
builder = builder.icon(icon.into_img(&app)?.as_ref().clone());
}
if let Some(tooltip) = options.tooltip {
builder = builder.tooltip(tooltip);
@ -94,12 +94,12 @@ fn new<R: Runtime>(
fn set_icon<R: Runtime>(
app: AppHandle<R>,
rid: ResourceId,
icon: Option<JsIcon<'_>>,
icon: Option<JsImage<'_>>,
) -> crate::Result<()> {
let resources_table = app.resources_table();
let tray = resources_table.get::<TrayIcon<R>>(rid)?;
let icon = match icon {
Some(i) => Some(i.try_into()?),
Some(i) => Some(i.into_img(&app)?.as_ref().clone()),
None => None,
};
tray.set_icon(icon)

View File

@ -134,10 +134,11 @@ mod desktop_commands {
pub async fn set_icon<R: Runtime>(
window: Window<R>,
label: Option<String>,
value: crate::image::JsIcon<'_>,
value: crate::image::JsImage<'_>,
) -> crate::Result<()> {
get_window(window, label)?
.set_icon(value.try_into()?)
let window = get_window(window, label)?;
window
.set_icon(value.into_img(&window)?.as_ref().clone())
.map_err(Into::into)
}

View File

@ -2975,12 +2975,6 @@ dependencies = [
"loom",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "string_cache"
version = "0.8.7"
@ -3182,7 +3176,6 @@ dependencies = [
"serde_repr",
"serialize-to-javascript",
"state",
"static_assertions",
"swift-rs",
"tauri-build",
"tauri-macros",

View File

@ -2,10 +2,7 @@
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "run-app",
"description": "permissions to run the app",
"windows": [
"main",
"main-*"
],
"windows": ["main", "main-*"],
"permissions": [
{
"identifier": "allow-log-operation",
@ -24,6 +21,7 @@
"window:default",
"app:default",
"resources:default",
"image:default",
"menu:default",
"tray:default",
"app:allow-app-hide",
@ -98,4 +96,4 @@
"tray:allow-set-icon-as-template",
"tray:allow-set-show-menu-on-left-click"
]
}
}

141
tooling/api/src/image.ts Normal file
View File

@ -0,0 +1,141 @@
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
import { Resource, invoke } from './core'
/** An RGBA Image in row-major order from top to bottom. */
export class Image extends Resource {
private constructor(rid: number) {
super(rid)
}
/** Creates a new Image using RGBA data, in row-major order from top to bottom, and with specified width and height. */
static async new(
rgba: number[] | Uint8Array | ArrayBuffer,
width: number,
height: number
): Promise<Image> {
return invoke<number>('plugin:image|new', {
rgba: transformImage(rgba),
width,
height
}).then((rid) => new Image(rid))
}
/**
* Creates a new image using the provided bytes by inferring the file format.
* If the format is known, prefer [@link Image.fromPngBytes] or [@link Image.fromIcoBytes].
*
* Only `ico` and `png` are supported (based on activated feature flag).
*
* Note that you need the `image-ico` or `image-png` Cargo features to use this API.
* To enable it, change your Cargo.toml file:
* ```toml
* [dependencies]
* tauri = { version = "...", features = ["...", "image-png"] }
* ```
*/
static async fromBytes(
bytes: number[] | Uint8Array | ArrayBuffer
): Promise<Image> {
return invoke<number>('plugin:image|from_bytes', {
bytes: transformImage(bytes)
}).then((rid) => new Image(rid))
}
/**
* Creates a new image using the provided png bytes.
*
* Note that you need the `image-png` Cargo features to use this API.
* To enable it, change your Cargo.toml file:
* ```toml
* [dependencies]
* tauri = { version = "...", features = ["...", "image-png"] }
* ```
*/
static async fromPngBytes(
bytes: number[] | Uint8Array | ArrayBuffer
): Promise<Image> {
return invoke<number>('plugin:image|from_png_bytes', {
bytes: transformImage(bytes)
}).then((rid) => new Image(rid))
}
/**
* Creates a new image using the provided ico bytes.
*
* Note that you need the `image-ico` Cargo features to use this API.
* To enable it, change your Cargo.toml file:
* ```toml
* [dependencies]
* tauri = { version = "...", features = ["...", "image-ico"] }
* ```
*/
static async fromIcoBytes(
bytes: number[] | Uint8Array | ArrayBuffer
): Promise<Image> {
return invoke<number>('plugin:image|from_ico_bytes', {
bytes: transformImage(bytes)
}).then((rid) => new Image(rid))
}
/**
* Creates a new image using the provided path.
*
* Only `ico` and `png` are supported (based on activated feature flag).
*
* Note that you need the `image-ico` or `image-png` Cargo features to use this API.
* To enable it, change your Cargo.toml file:
* ```toml
* [dependencies]
* tauri = { version = "...", features = ["...", "image-png"] }
* ```
*/
static async fromPath(path: string): Promise<Image> {
return invoke<number>('plugin:image|from_path', { path }).then(
(rid) => new Image(rid)
)
}
/** Returns the RGBA data for this image, in row-major order from top to bottom. */
async rgba(): Promise<ArrayBuffer | number[]> {
return invoke<ArrayBuffer | number[]>('plugin:image|rgba', {
rid: this.rid
})
}
/** Returns the width of this image. */
async width() {
return invoke<number>('plugin:image|width', { rid: this.rid })
}
/** Returns the height of this image. */
async height() {
return invoke<number>('plugin:image|height', { rid: this.rid })
}
}
/**
* Transforms image from various types into a type acceptable by Rust. Intended for internal use only.
*
* @ignore
*/
export function transformImage<T>(
image: string | Image | Uint8Array | ArrayBuffer | number[] | null
): T {
const ret =
image == null
? null
: typeof image === 'string'
? image
: image instanceof Uint8Array
? Array.from(image)
: image instanceof ArrayBuffer
? Array.from(new Uint8Array(image))
: image instanceof Image
? image.rid
: image
return ret as T
}

View File

@ -23,6 +23,7 @@ import * as path from './path'
import * as dpi from './dpi'
import * as tray from './tray'
import * as menu from './menu'
import * as image from './image'
export {
app,
@ -34,5 +35,6 @@ export {
webview,
webviewWindow,
tray,
menu
menu,
image
}

View File

@ -3,6 +3,7 @@
// SPDX-License-Identifier: MIT
import { Channel, invoke, Resource } from '../core'
import { transformImage } from '../image'
import { CheckMenuItemOptions } from './checkMenuItem'
import { IconMenuItemOptions } from './iconMenuItem'
import { MenuItemOptions } from './menuItem'
@ -76,6 +77,15 @@ export async function newMenu(
if ('rid' in i) {
return [i.rid, i.kind]
}
if ('item' in i && typeof i.item === 'object' && i.item.About?.icon) {
i.item.About.icon = transformImage(i.item.About.icon)
}
if ('icon' in i && i.icon) {
i.icon = transformImage(i.icon)
}
return injectChannel(i)
})
}

View File

@ -5,6 +5,7 @@
import { MenuItemBase, newMenu } from './base'
import { type MenuItemOptions } from '../menu'
import { invoke } from '../core'
import { Image, transformImage } from '../image'
/**
* A native Icon to be used for the menu item
@ -133,7 +134,7 @@ export interface IconMenuItemOptions extends MenuItemOptions {
/**
* Icon to be used for the new icon menu item.
*/
icon?: NativeIcon | string | Uint8Array
icon?: NativeIcon | string | Image | Uint8Array | ArrayBuffer | number[]
}
/**
@ -189,7 +190,19 @@ export class IconMenuItem extends MenuItemBase {
}
/** Sets an icon for this icon menu item */
async setIcon(icon: NativeIcon | string | Uint8Array | null): Promise<void> {
return invoke('plugin:menu|set_icon', { rid: this.rid, icon })
async setIcon(
icon:
| NativeIcon
| string
| Image
| Uint8Array
| ArrayBuffer
| number[]
| null
): Promise<void> {
return invoke('plugin:menu|set_icon', {
rid: this.rid,
icon: transformImage(icon)
})
}
}

View File

@ -4,6 +4,7 @@
import { MenuItemBase, newMenu } from './base'
import { invoke } from '../core'
import { Image } from '../image'
/** A metadata for the about predefined menu item. */
export interface AboutMetadata {
@ -76,7 +77,7 @@ export interface AboutMetadata {
*
* - **Windows:** Unsupported.
*/
icon?: string | Uint8Array
icon?: string | Uint8Array | ArrayBuffer | number[] | Image
}
/** Options for creating a new predefined menu item. */

View File

@ -4,6 +4,7 @@
import type { Menu, Submenu } from './menu'
import { Channel, invoke, Resource } from './core'
import { Image, transformImage } from './image'
/**
* Describes a tray event emitted when a tray icon is clicked
@ -58,7 +59,7 @@ export interface TrayIconOptions {
* tauri = { version = "...", features = ["...", "image-png"] }
* ```
*/
icon?: string | Uint8Array | number[]
icon?: string | Uint8Array | ArrayBuffer | number[] | Image
/** The tray icon tooltip */
tooltip?: string
/**
@ -132,10 +133,7 @@ export class TrayIcon extends Resource {
options.menu = [options.menu.rid, options.menu.kind]
}
if (options?.icon) {
options.icon =
typeof options.icon === 'string'
? options.icon
: Array.from(options.icon)
options.icon = transformImage(options.icon)
}
const handler = new Channel<TrayIconEvent>()
@ -150,11 +148,22 @@ export class TrayIcon extends Resource {
}).then(([rid, id]) => new TrayIcon(rid, id))
}
/** Sets a new tray icon. If `null` is provided, it will remove the icon. */
async setIcon(icon: string | Uint8Array | null): Promise<void> {
/**
* Sets a new tray icon. If `null` is provided, it will remove the icon.
*
* Note that you need the `image-ico` or `image-png` Cargo features to use this API.
* To enable it, change your Cargo.toml file:
* ```toml
* [dependencies]
* tauri = { version = "...", features = ["...", "image-png"] }
* ```
*/
async setIcon(
icon: string | Image | Uint8Array | ArrayBuffer | number[] | null
): Promise<void> {
let trayIcon = null
if (icon) {
trayIcon = typeof icon === 'string' ? icon : Array.from(icon)
trayIcon = transformImage(icon)
}
return invoke('plugin:tray|set_icon', { rid: this.rid, icon: trayIcon })
}

View File

@ -36,6 +36,7 @@ import {
import { invoke } from './core'
import { WebviewWindow } from './webviewWindow'
import type { FileDropEvent, FileDropPayload } from './webview'
import { Image, transformImage } from './image'
/**
* Allows you to retrieve information about a given monitor.
@ -1393,10 +1394,12 @@ class Window {
* @param icon Icon bytes or path to the icon file.
* @returns A promise indicating the success or failure of the operation.
*/
async setIcon(icon: string | Uint8Array): Promise<void> {
async setIcon(
icon: string | Image | Uint8Array | ArrayBuffer | number[]
): Promise<void> {
return invoke('plugin:window|set_icon', {
label: this.label,
value: typeof icon === 'string' ? icon : Array.from(icon)
value: transformImage(icon)
})
}

View File

@ -10,6 +10,7 @@
"webview:default",
"app:default",
"resources:default",
"image:default",
"menu:default",
"tray:default"
]