mirror of
https://github.com/tauri-apps/tauri.git
synced 2024-08-17 11:50:26 +03:00
feat(core): add include_image
macro (#9959)
This commit is contained in:
parent
586a816e62
commit
5b769948a8
5
.changes/include-image-macro-codegen.md
Normal file
5
.changes/include-image-macro-codegen.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"tauri-codegen": "patch:feat"
|
||||||
|
---
|
||||||
|
|
||||||
|
Add `include_image_codegen` function to help embedding instances of `Image` struct at compile-time in rust to be used with window, menu or tray icons.
|
7
.changes/include-image-macro.md
Normal file
7
.changes/include-image-macro.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"tauri": "patch:feat"
|
||||||
|
"tauri-utils": "patch:feat"
|
||||||
|
"tauri-macros": "patch:feat"
|
||||||
|
---
|
||||||
|
|
||||||
|
Add `include_image` macro to help embedding instances of `Image` struct at compile-time in rust to be used with window, menu or tray icons.
|
@ -25,7 +25,10 @@ use tauri_utils::platform::Target;
|
|||||||
use tauri_utils::plugin::GLOBAL_API_SCRIPT_FILE_LIST_PATH;
|
use tauri_utils::plugin::GLOBAL_API_SCRIPT_FILE_LIST_PATH;
|
||||||
use tauri_utils::tokens::{map_lit, str_lit};
|
use tauri_utils::tokens::{map_lit, str_lit};
|
||||||
|
|
||||||
use crate::embedded_assets::{AssetOptions, CspHashes, EmbeddedAssets, EmbeddedAssetsError};
|
use crate::embedded_assets::{
|
||||||
|
ensure_out_dir, AssetOptions, CspHashes, EmbeddedAssets, EmbeddedAssetsResult,
|
||||||
|
};
|
||||||
|
use crate::image::{ico_icon, image_icon, png_icon, raw_icon};
|
||||||
|
|
||||||
const ACL_MANIFESTS_FILE_NAME: &str = "acl-manifests.json";
|
const ACL_MANIFESTS_FILE_NAME: &str = "acl-manifests.json";
|
||||||
const CAPABILITIES_FILE_NAME: &str = "capabilities.json";
|
const CAPABILITIES_FILE_NAME: &str = "capabilities.json";
|
||||||
@ -65,7 +68,7 @@ fn inject_script_hashes(document: &NodeRef, key: &AssetKey, csp_hashes: &mut Csp
|
|||||||
|
|
||||||
fn map_core_assets(
|
fn map_core_assets(
|
||||||
options: &AssetOptions,
|
options: &AssetOptions,
|
||||||
) -> impl Fn(&AssetKey, &Path, &mut Vec<u8>, &mut CspHashes) -> Result<(), EmbeddedAssetsError> {
|
) -> impl Fn(&AssetKey, &Path, &mut Vec<u8>, &mut CspHashes) -> EmbeddedAssetsResult<()> {
|
||||||
let csp = options.csp;
|
let csp = options.csp;
|
||||||
let dangerous_disable_asset_csp_modification =
|
let dangerous_disable_asset_csp_modification =
|
||||||
options.dangerous_disable_asset_csp_modification.clone();
|
options.dangerous_disable_asset_csp_modification.clone();
|
||||||
@ -92,7 +95,7 @@ fn map_core_assets(
|
|||||||
fn map_isolation(
|
fn map_isolation(
|
||||||
_options: &AssetOptions,
|
_options: &AssetOptions,
|
||||||
dir: PathBuf,
|
dir: PathBuf,
|
||||||
) -> impl Fn(&AssetKey, &Path, &mut Vec<u8>, &mut CspHashes) -> Result<(), EmbeddedAssetsError> {
|
) -> impl Fn(&AssetKey, &Path, &mut Vec<u8>, &mut CspHashes) -> EmbeddedAssetsResult<()> {
|
||||||
// create the csp for the isolation iframe styling now, to make the runtime less complex
|
// create the csp for the isolation iframe styling now, to make the runtime less complex
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
hasher.update(tauri_utils::pattern::isolation::IFRAME_STYLE);
|
hasher.update(tauri_utils::pattern::isolation::IFRAME_STYLE);
|
||||||
@ -129,7 +132,7 @@ fn map_isolation(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Build a `tauri::Context` for including in application code.
|
/// Build a `tauri::Context` for including in application code.
|
||||||
pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsError> {
|
pub fn context_codegen(data: ContextData) -> EmbeddedAssetsResult<TokenStream> {
|
||||||
let ContextData {
|
let ContextData {
|
||||||
dev,
|
dev,
|
||||||
config,
|
config,
|
||||||
@ -201,17 +204,7 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
|
|||||||
quote!(#assets)
|
quote!(#assets)
|
||||||
};
|
};
|
||||||
|
|
||||||
let out_dir = {
|
let out_dir = ensure_out_dir()?;
|
||||||
let out_dir = std::env::var("OUT_DIR")
|
|
||||||
.map_err(|_| EmbeddedAssetsError::OutDir)
|
|
||||||
.map(PathBuf::from)
|
|
||||||
.and_then(|p| p.canonicalize().map_err(|_| EmbeddedAssetsError::OutDir))?;
|
|
||||||
|
|
||||||
// make sure that our output directory is created
|
|
||||||
std::fs::create_dir_all(&out_dir).map_err(|_| EmbeddedAssetsError::OutDir)?;
|
|
||||||
|
|
||||||
out_dir
|
|
||||||
};
|
|
||||||
|
|
||||||
let default_window_icon = {
|
let default_window_icon = {
|
||||||
if target == Target::Windows {
|
if target == Target::Windows {
|
||||||
@ -223,7 +216,7 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
|
|||||||
"icons/icon.ico",
|
"icons/icon.ico",
|
||||||
);
|
);
|
||||||
if icon_path.exists() {
|
if icon_path.exists() {
|
||||||
ico_icon(&root, &out_dir, icon_path).map(|i| quote!(::std::option::Option::Some(#i)))?
|
ico_icon(&root, &out_dir, &icon_path).map(|i| quote!(::std::option::Option::Some(#i)))?
|
||||||
} else {
|
} else {
|
||||||
let icon_path = find_icon(
|
let icon_path = find_icon(
|
||||||
&config,
|
&config,
|
||||||
@ -231,7 +224,7 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
|
|||||||
|i| i.ends_with(".png"),
|
|i| i.ends_with(".png"),
|
||||||
"icons/icon.png",
|
"icons/icon.png",
|
||||||
);
|
);
|
||||||
png_icon(&root, &out_dir, icon_path).map(|i| quote!(::std::option::Option::Some(#i)))?
|
png_icon(&root, &out_dir, &icon_path).map(|i| quote!(::std::option::Option::Some(#i)))?
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// handle default window icons for Unix targets
|
// handle default window icons for Unix targets
|
||||||
@ -241,7 +234,7 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
|
|||||||
|i| i.ends_with(".png"),
|
|i| i.ends_with(".png"),
|
||||||
"icons/icon.png",
|
"icons/icon.png",
|
||||||
);
|
);
|
||||||
png_icon(&root, &out_dir, icon_path).map(|i| quote!(::std::option::Option::Some(#i)))?
|
png_icon(&root, &out_dir, &icon_path).map(|i| quote!(::std::option::Option::Some(#i)))?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -260,7 +253,7 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
|
|||||||
"icons/icon.png",
|
"icons/icon.png",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
raw_icon(&out_dir, icon_path)?
|
raw_icon(&out_dir, &icon_path)?
|
||||||
} else {
|
} else {
|
||||||
quote!(::std::option::Option::None)
|
quote!(::std::option::Option::None)
|
||||||
};
|
};
|
||||||
@ -289,18 +282,8 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
|
|||||||
let with_tray_icon_code = if target.is_desktop() {
|
let with_tray_icon_code = if target.is_desktop() {
|
||||||
if let Some(tray) = &config.app.tray_icon {
|
if let Some(tray) = &config.app.tray_icon {
|
||||||
let tray_icon_icon_path = config_parent.join(&tray.icon_path);
|
let tray_icon_icon_path = config_parent.join(&tray.icon_path);
|
||||||
let ext = tray_icon_icon_path.extension();
|
image_icon(&root, &out_dir, &tray_icon_icon_path)
|
||||||
if ext.map_or(false, |e| e == "ico") {
|
.map(|i| quote!(context.set_tray_icon(Some(#i));))?
|
||||||
ico_icon(&root, &out_dir, tray_icon_icon_path)
|
|
||||||
.map(|i| quote!(context.set_tray_icon(Some(#i));))?
|
|
||||||
} else if ext.map_or(false, |e| e == "png") {
|
|
||||||
png_icon(&root, &out_dir, tray_icon_icon_path)
|
|
||||||
.map(|i| quote!(context.set_tray_icon(Some(#i));))?
|
|
||||||
} else {
|
|
||||||
quote!(compile_error!(
|
|
||||||
"The tray icon extension must be either `.ico` or `.png`."
|
|
||||||
))
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
quote!()
|
quote!()
|
||||||
}
|
}
|
||||||
@ -504,122 +487,18 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ico_icon<P: AsRef<Path>>(
|
fn find_icon(
|
||||||
root: &TokenStream,
|
|
||||||
out_dir: &Path,
|
|
||||||
path: P,
|
|
||||||
) -> Result<TokenStream, EmbeddedAssetsError> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
let bytes = std::fs::read(path)
|
|
||||||
.unwrap_or_else(|e| panic!("failed to read icon {}: {}", path.display(), e))
|
|
||||||
.to_vec();
|
|
||||||
let icon_dir = ico::IconDir::read(std::io::Cursor::new(bytes))
|
|
||||||
.unwrap_or_else(|e| panic!("failed to parse icon {}: {}", path.display(), e));
|
|
||||||
let entry = &icon_dir.entries()[0];
|
|
||||||
let rgba = entry
|
|
||||||
.decode()
|
|
||||||
.unwrap_or_else(|e| panic!("failed to decode icon {}: {}", path.display(), e))
|
|
||||||
.rgba_data()
|
|
||||||
.to_vec();
|
|
||||||
let width = entry.width();
|
|
||||||
let height = entry.height();
|
|
||||||
|
|
||||||
let icon_file_name = path.file_name().unwrap();
|
|
||||||
let out_path = out_dir.join(icon_file_name);
|
|
||||||
write_if_changed(&out_path, &rgba).map_err(|error| EmbeddedAssetsError::AssetWrite {
|
|
||||||
path: path.to_owned(),
|
|
||||||
error,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let icon_file_name = icon_file_name.to_str().unwrap();
|
|
||||||
let icon = quote!(#root::image::Image::new(include_bytes!(concat!(std::env!("OUT_DIR"), "/", #icon_file_name)), #width, #height));
|
|
||||||
Ok(icon)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn raw_icon<P: AsRef<Path>>(out_dir: &Path, path: P) -> Result<TokenStream, EmbeddedAssetsError> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
let bytes = std::fs::read(path)
|
|
||||||
.unwrap_or_else(|e| panic!("failed to read icon {}: {}", path.display(), e))
|
|
||||||
.to_vec();
|
|
||||||
|
|
||||||
let out_path = out_dir.join(path.file_name().unwrap());
|
|
||||||
write_if_changed(&out_path, &bytes).map_err(|error| EmbeddedAssetsError::AssetWrite {
|
|
||||||
path: path.to_owned(),
|
|
||||||
error,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let icon_path = path.file_name().unwrap().to_str().unwrap().to_string();
|
|
||||||
let icon = quote!(::std::option::Option::Some(
|
|
||||||
include_bytes!(concat!(std::env!("OUT_DIR"), "/", #icon_path)).to_vec()
|
|
||||||
));
|
|
||||||
Ok(icon)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn png_icon<P: AsRef<Path>>(
|
|
||||||
root: &TokenStream,
|
|
||||||
out_dir: &Path,
|
|
||||||
path: P,
|
|
||||||
) -> Result<TokenStream, EmbeddedAssetsError> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
let bytes = std::fs::read(path)
|
|
||||||
.unwrap_or_else(|e| panic!("failed to read icon {}: {}", path.display(), e))
|
|
||||||
.to_vec();
|
|
||||||
let decoder = png::Decoder::new(std::io::Cursor::new(bytes));
|
|
||||||
let mut reader = decoder
|
|
||||||
.read_info()
|
|
||||||
.unwrap_or_else(|e| panic!("failed to read icon {}: {}", path.display(), e));
|
|
||||||
|
|
||||||
let (color_type, _) = reader.output_color_type();
|
|
||||||
|
|
||||||
if color_type != png::ColorType::Rgba {
|
|
||||||
panic!("icon {} is not RGBA", path.display());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut buffer: Vec<u8> = Vec::new();
|
|
||||||
while let Ok(Some(row)) = reader.next_row() {
|
|
||||||
buffer.extend(row.data());
|
|
||||||
}
|
|
||||||
let width = reader.info().width;
|
|
||||||
let height = reader.info().height;
|
|
||||||
|
|
||||||
let icon_file_name = path.file_name().unwrap();
|
|
||||||
let out_path = out_dir.join(icon_file_name);
|
|
||||||
write_if_changed(&out_path, &buffer).map_err(|error| EmbeddedAssetsError::AssetWrite {
|
|
||||||
path: path.to_owned(),
|
|
||||||
error,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let icon_file_name = icon_file_name.to_str().unwrap();
|
|
||||||
let icon = quote!(#root::image::Image::new(include_bytes!(concat!(std::env!("OUT_DIR"), "/", #icon_file_name)), #width, #height));
|
|
||||||
Ok(icon)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_if_changed(out_path: &Path, data: &[u8]) -> std::io::Result<()> {
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::Write;
|
|
||||||
|
|
||||||
if let Ok(curr) = std::fs::read(out_path) {
|
|
||||||
if curr == data {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut out_file = File::create(out_path)?;
|
|
||||||
out_file.write_all(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_icon<F: Fn(&&String) -> bool>(
|
|
||||||
config: &Config,
|
config: &Config,
|
||||||
config_parent: &Path,
|
config_parent: &Path,
|
||||||
predicate: F,
|
predicate: impl Fn(&&String) -> bool,
|
||||||
default: &str,
|
default: &str,
|
||||||
) -> PathBuf {
|
) -> PathBuf {
|
||||||
let icon_path = config
|
let icon_path = config
|
||||||
.bundle
|
.bundle
|
||||||
.icon
|
.icon
|
||||||
.iter()
|
.iter()
|
||||||
.find(|i| predicate(i))
|
.find(predicate)
|
||||||
.cloned()
|
.map(AsRef::as_ref)
|
||||||
.unwrap_or_else(|| default.to_string());
|
.unwrap_or(default);
|
||||||
config_parent.join(icon_path)
|
config_parent.join(icon_path)
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,9 @@ pub enum EmbeddedAssetsError {
|
|||||||
#[error("invalid prefix {prefix} used while including path {path}")]
|
#[error("invalid prefix {prefix} used while including path {path}")]
|
||||||
PrefixInvalid { prefix: PathBuf, path: PathBuf },
|
PrefixInvalid { prefix: PathBuf, path: PathBuf },
|
||||||
|
|
||||||
|
#[error("invalid extension {extension} used for image {path}, must be `ico` or `png`")]
|
||||||
|
InvalidImageExtension { extension: PathBuf, path: PathBuf },
|
||||||
|
|
||||||
#[error("failed to walk directory {path} because {error}")]
|
#[error("failed to walk directory {path} because {error}")]
|
||||||
Walkdir {
|
Walkdir {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
@ -61,6 +64,8 @@ pub enum EmbeddedAssetsError {
|
|||||||
Version(#[from] semver::Error),
|
Version(#[from] semver::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type EmbeddedAssetsResult<T> = Result<T, EmbeddedAssetsError>;
|
||||||
|
|
||||||
/// Represent a directory of assets that are compressed and embedded.
|
/// Represent a directory of assets that are compressed and embedded.
|
||||||
///
|
///
|
||||||
/// This is the compile time generation of [`tauri_utils::assets::Assets`] from a directory. Assets
|
/// This is the compile time generation of [`tauri_utils::assets::Assets`] from a directory. Assets
|
||||||
@ -439,3 +444,14 @@ impl ToTokens for EmbeddedAssets {
|
|||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn ensure_out_dir() -> EmbeddedAssetsResult<PathBuf> {
|
||||||
|
let out_dir = std::env::var("OUT_DIR")
|
||||||
|
.map_err(|_| EmbeddedAssetsError::OutDir)
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.and_then(|p| p.canonicalize().map_err(|_| EmbeddedAssetsError::OutDir))?;
|
||||||
|
|
||||||
|
// make sure that our output directory is created
|
||||||
|
std::fs::create_dir_all(&out_dir).map_err(|_| EmbeddedAssetsError::OutDir)?;
|
||||||
|
Ok(out_dir)
|
||||||
|
}
|
||||||
|
136
core/tauri-codegen/src/image.rs
Normal file
136
core/tauri-codegen/src/image.rs
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use crate::embedded_assets::{ensure_out_dir, EmbeddedAssetsError, EmbeddedAssetsResult};
|
||||||
|
use proc_macro2::{Span, TokenStream};
|
||||||
|
use quote::{quote, ToTokens};
|
||||||
|
use std::path::Path;
|
||||||
|
use syn::{punctuated::Punctuated, Ident, PathArguments, PathSegment, Token};
|
||||||
|
|
||||||
|
pub fn include_image_codegen(path: &Path) -> EmbeddedAssetsResult<TokenStream> {
|
||||||
|
let out_dir = ensure_out_dir()?;
|
||||||
|
|
||||||
|
let mut segments = Punctuated::new();
|
||||||
|
segments.push(PathSegment {
|
||||||
|
ident: Ident::new("tauri", Span::call_site()),
|
||||||
|
arguments: PathArguments::None,
|
||||||
|
});
|
||||||
|
let root = syn::Path {
|
||||||
|
leading_colon: Some(Token![::](Span::call_site())),
|
||||||
|
segments,
|
||||||
|
};
|
||||||
|
|
||||||
|
image_icon(&root.to_token_stream(), &out_dir, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn image_icon(
|
||||||
|
root: &TokenStream,
|
||||||
|
out_dir: &Path,
|
||||||
|
path: &Path,
|
||||||
|
) -> EmbeddedAssetsResult<TokenStream> {
|
||||||
|
let extension = path.extension().unwrap_or_default();
|
||||||
|
if extension == "ico" {
|
||||||
|
ico_icon(root, out_dir, path)
|
||||||
|
} else if extension == "png" {
|
||||||
|
png_icon(root, out_dir, path)
|
||||||
|
} else {
|
||||||
|
Err(EmbeddedAssetsError::InvalidImageExtension {
|
||||||
|
extension: extension.into(),
|
||||||
|
path: path.to_path_buf(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn raw_icon(out_dir: &Path, path: &Path) -> EmbeddedAssetsResult<TokenStream> {
|
||||||
|
let bytes =
|
||||||
|
std::fs::read(path).unwrap_or_else(|e| panic!("failed to read icon {}: {}", path.display(), e));
|
||||||
|
|
||||||
|
let out_path = out_dir.join(path.file_name().unwrap());
|
||||||
|
write_if_changed(&out_path, &bytes).map_err(|error| EmbeddedAssetsError::AssetWrite {
|
||||||
|
path: path.to_owned(),
|
||||||
|
error,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let icon_path = path.file_name().unwrap().to_str().unwrap().to_string();
|
||||||
|
let icon = quote!(::std::option::Option::Some(
|
||||||
|
include_bytes!(concat!(std::env!("OUT_DIR"), "/", #icon_path)).to_vec()
|
||||||
|
));
|
||||||
|
Ok(icon)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn ico_icon(
|
||||||
|
root: &TokenStream,
|
||||||
|
out_dir: &Path,
|
||||||
|
path: &Path,
|
||||||
|
) -> EmbeddedAssetsResult<TokenStream> {
|
||||||
|
let file = std::fs::File::open(path)
|
||||||
|
.unwrap_or_else(|e| panic!("failed to open icon {}: {}", path.display(), e));
|
||||||
|
let icon_dir = ico::IconDir::read(file)
|
||||||
|
.unwrap_or_else(|e| panic!("failed to parse icon {}: {}", path.display(), e));
|
||||||
|
let entry = &icon_dir.entries()[0];
|
||||||
|
let rgba = entry
|
||||||
|
.decode()
|
||||||
|
.unwrap_or_else(|e| panic!("failed to decode icon {}: {}", path.display(), e))
|
||||||
|
.rgba_data()
|
||||||
|
.to_vec();
|
||||||
|
let width = entry.width();
|
||||||
|
let height = entry.height();
|
||||||
|
|
||||||
|
let icon_file_name = path.file_name().unwrap();
|
||||||
|
let out_path = out_dir.join(icon_file_name);
|
||||||
|
write_if_changed(&out_path, &rgba).map_err(|error| EmbeddedAssetsError::AssetWrite {
|
||||||
|
path: path.to_owned(),
|
||||||
|
error,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let icon_file_name = icon_file_name.to_str().unwrap();
|
||||||
|
let icon = quote!(#root::image::Image::new(include_bytes!(concat!(std::env!("OUT_DIR"), "/", #icon_file_name)), #width, #height));
|
||||||
|
Ok(icon)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn png_icon(
|
||||||
|
root: &TokenStream,
|
||||||
|
out_dir: &Path,
|
||||||
|
path: &Path,
|
||||||
|
) -> EmbeddedAssetsResult<TokenStream> {
|
||||||
|
let file = std::fs::File::open(path)
|
||||||
|
.unwrap_or_else(|e| panic!("failed to open icon {}: {}", path.display(), e));
|
||||||
|
let decoder = png::Decoder::new(file);
|
||||||
|
let mut reader = decoder
|
||||||
|
.read_info()
|
||||||
|
.unwrap_or_else(|e| panic!("failed to read icon {}: {}", path.display(), e));
|
||||||
|
|
||||||
|
let (color_type, _) = reader.output_color_type();
|
||||||
|
|
||||||
|
if color_type != png::ColorType::Rgba {
|
||||||
|
panic!("icon {} is not RGBA", path.display());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buffer: Vec<u8> = Vec::new();
|
||||||
|
while let Ok(Some(row)) = reader.next_row() {
|
||||||
|
buffer.extend(row.data());
|
||||||
|
}
|
||||||
|
let width = reader.info().width;
|
||||||
|
let height = reader.info().height;
|
||||||
|
|
||||||
|
let icon_file_name = path.file_name().unwrap();
|
||||||
|
let out_path = out_dir.join(icon_file_name);
|
||||||
|
write_if_changed(&out_path, &buffer).map_err(|error| EmbeddedAssetsError::AssetWrite {
|
||||||
|
path: path.to_owned(),
|
||||||
|
error,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let icon_file_name = icon_file_name.to_str().unwrap();
|
||||||
|
let icon = quote!(#root::image::Image::new(include_bytes!(concat!(std::env!("OUT_DIR"), "/", #icon_file_name)), #width, #height));
|
||||||
|
Ok(icon)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn write_if_changed(out_path: &Path, data: &[u8]) -> std::io::Result<()> {
|
||||||
|
if let Ok(curr) = std::fs::read(out_path) {
|
||||||
|
if curr == data {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::fs::write(out_path, data)
|
||||||
|
}
|
@ -13,6 +13,7 @@
|
|||||||
)]
|
)]
|
||||||
|
|
||||||
pub use self::context::{context_codegen, ContextData};
|
pub use self::context::{context_codegen, ContextData};
|
||||||
|
pub use self::image::include_image_codegen;
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
@ -22,6 +23,7 @@ use tauri_utils::platform::Target;
|
|||||||
|
|
||||||
mod context;
|
mod context;
|
||||||
pub mod embedded_assets;
|
pub mod embedded_assets;
|
||||||
|
mod image;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub mod vendor;
|
pub mod vendor;
|
||||||
|
|
||||||
|
@ -1318,7 +1318,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"iconPath": {
|
"iconPath": {
|
||||||
"description": "Path to the default icon to use for the tray icon.",
|
"description": "Path to the default icon to use for the tray icon.\n\nNote: this stores the image in raw pixels to the final binary, so keep the icon size (width and height) small or else it's going to bloat your final executable",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"iconAsTemplate": {
|
"iconAsTemplate": {
|
||||||
|
@ -11,9 +11,12 @@
|
|||||||
html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png"
|
html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png"
|
||||||
)]
|
)]
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::context::ContextItems;
|
use crate::context::ContextItems;
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use syn::parse_macro_input;
|
use quote::quote;
|
||||||
|
use syn::{parse2, parse_macro_input, LitStr};
|
||||||
|
|
||||||
mod command;
|
mod command;
|
||||||
mod menu;
|
mod menu;
|
||||||
@ -151,3 +154,58 @@ pub fn do_menu_item(input: TokenStream) -> TokenStream {
|
|||||||
let tokens = parse_macro_input!(input as menu::DoMenuItemInput);
|
let tokens = parse_macro_input!(input as menu::DoMenuItemInput);
|
||||||
menu::do_menu_item(tokens).into()
|
menu::do_menu_item(tokens).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert a .png or .ico icon to an Image
|
||||||
|
/// for things like `tauri::tray::TrayIconBuilder` to consume,
|
||||||
|
/// relative paths are resolved from `CARGO_MANIFEST_DIR`, not current file
|
||||||
|
///
|
||||||
|
/// ### Examples
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// const APP_ICON: Image<'_> = include_image!("./icons/32x32.png");
|
||||||
|
///
|
||||||
|
/// // then use it with tray
|
||||||
|
/// TrayIconBuilder::new().icon(APP_ICON).build().unwrap();
|
||||||
|
///
|
||||||
|
/// // or with window
|
||||||
|
/// WebviewWindowBuilder::new(app, "main", WebviewUrl::default())
|
||||||
|
/// .icon(APP_ICON)
|
||||||
|
/// .unwrap()
|
||||||
|
/// .build()
|
||||||
|
/// .unwrap();
|
||||||
|
///
|
||||||
|
/// // or with any other functions that takes `Image` struct
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Note: this stores the image in raw pixels to the final binary,
|
||||||
|
/// so keep the icon size (width and height) small
|
||||||
|
/// or else it's going to bloat your final executable
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn include_image(tokens: TokenStream) -> TokenStream {
|
||||||
|
let path = match parse2::<LitStr>(tokens.into()) {
|
||||||
|
Ok(path) => path,
|
||||||
|
Err(err) => return err.into_compile_error().into(),
|
||||||
|
};
|
||||||
|
let path = PathBuf::from(path.value());
|
||||||
|
let resolved_path = if path.is_relative() {
|
||||||
|
if let Ok(base_dir) = std::env::var("CARGO_MANIFEST_DIR").map(PathBuf::from) {
|
||||||
|
base_dir.join(path)
|
||||||
|
} else {
|
||||||
|
return quote!(compile_error!("$CARGO_MANIFEST_DIR is not defined")).into();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
path
|
||||||
|
};
|
||||||
|
if !resolved_path.exists() {
|
||||||
|
let error_string = format!(
|
||||||
|
"Provided Image path \"{}\" doesn't exists",
|
||||||
|
resolved_path.display()
|
||||||
|
);
|
||||||
|
return quote!(compile_error!(#error_string)).into();
|
||||||
|
}
|
||||||
|
match tauri_codegen::include_image_codegen(&resolved_path).map_err(|error| error.to_string()) {
|
||||||
|
Ok(output) => output,
|
||||||
|
Err(error) => quote!(compile_error!(#error)),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
@ -1798,6 +1798,10 @@ pub struct TrayIconConfig {
|
|||||||
/// Set an id for this tray icon so you can reference it later, defaults to `main`.
|
/// Set an id for this tray icon so you can reference it later, defaults to `main`.
|
||||||
pub id: Option<String>,
|
pub id: Option<String>,
|
||||||
/// Path to the default icon to use for the tray icon.
|
/// Path to the default icon to use for the tray icon.
|
||||||
|
///
|
||||||
|
/// Note: this stores the image in raw pixels to the final binary,
|
||||||
|
/// so keep the icon size (width and height) small
|
||||||
|
/// or else it's going to bloat your final executable
|
||||||
#[serde(alias = "icon-path")]
|
#[serde(alias = "icon-path")]
|
||||||
pub icon_path: PathBuf,
|
pub icon_path: PathBuf,
|
||||||
/// A Boolean value that determines whether the image represents a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) image on macOS.
|
/// A Boolean value that determines whether the image represents a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) image on macOS.
|
||||||
|
@ -75,6 +75,7 @@ pub use resources::{Resource, ResourceId, ResourceTable};
|
|||||||
#[cfg(target_os = "ios")]
|
#[cfg(target_os = "ios")]
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use swift_rs;
|
pub use swift_rs;
|
||||||
|
pub use tauri_macros::include_image;
|
||||||
#[cfg(mobile)]
|
#[cfg(mobile)]
|
||||||
pub use tauri_macros::mobile_entry_point;
|
pub use tauri_macros::mobile_entry_point;
|
||||||
pub use tauri_macros::{command, generate_handler};
|
pub use tauri_macros::{command, generate_handler};
|
||||||
|
@ -1318,7 +1318,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"iconPath": {
|
"iconPath": {
|
||||||
"description": "Path to the default icon to use for the tray icon.",
|
"description": "Path to the default icon to use for the tray icon.\n\nNote: this stores the image in raw pixels to the final binary, so keep the icon size (width and height) small or else it's going to bloat your final executable",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"iconAsTemplate": {
|
"iconAsTemplate": {
|
||||||
|
Loading…
Reference in New Issue
Block a user