mirror of
https://github.com/tauri-apps/tauri.git
synced 2024-08-16 19:30:27 +03:00
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:
parent
2ca9afb576
commit
77b9a508a4
@ -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
7
Cargo.lock
generated
@ -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",
|
||||
|
@ -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]
|
||||
|
@ -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",
|
||||
|
19
core/tauri/permissions/image/autogenerated/reference.md
Normal file
19
core/tauri/permissions/image/autogenerated/reference.md
Normal 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
@ -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"))]
|
||||
|
@ -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!(),
|
||||
}
|
||||
),
|
116
core/tauri/src/image/plugin.rs
Normal file
116
core/tauri/src/image/plugin.rs
Normal 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()
|
||||
}
|
@ -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)?;
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
7
examples/api/src-tauri/Cargo.lock
generated
7
examples/api/src-tauri/Cargo.lock
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
141
tooling/api/src/image.ts
Normal file
141
tooling/api/src/image.ts
Normal 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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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. */
|
||||
|
@ -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 })
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
"webview:default",
|
||||
"app:default",
|
||||
"resources:default",
|
||||
"image:default",
|
||||
"menu:default",
|
||||
"tray:default"
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user