feat: windows app icon (#3753)
* default icon * cli * fix typos, fmt + icon fix I don't think it's a good idea to do [default.extend-words] fo = "fo" as it's a very common mispelling for 'for' * fix clippy * return error on non implemented platforms instead of default dx icon * fix * fix fix the amazing merge renamed winres to windows moved windows winres build request to windows * fix typos * fix schema * Update schema.json * fix schema again * use default icon if no fallback * wire up the rest of windows * fmt --------- Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
This commit is contained in:
1 parent
6d1f19067a
commit
3f82c45520
17 files changed
+1125
-25
No files matched your search
@@ -21,6 +21,8 @@ dioxus-asset-resolver = { workspace = true, features = ["native"] }
|
||||
generational-box = { workspace = true }
|
||||
dioxus-devtools = { workspace = true, optional = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
image = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
use anyhow::Result;
|
||||
use image::load_from_memory;
|
||||
use image::GenericImageView;
|
||||
use image::ImageReader;
|
||||
use std::path::Path;
|
||||
|
||||
/// Pre-decoded RGBA bytes of the bundled fallback icon.
|
||||
const FALLBACK_ICON_RGBA: &[u8] = include_bytes!("./assets/default_icon.bin");
|
||||
const FALLBACK_ICON_WIDTH: u32 = 460;
|
||||
const FALLBACK_ICON_HEIGHT: u32 = 460;
|
||||
|
||||
/// Trait that creates icons for various types
|
||||
pub trait DioxusIconTrait {
|
||||
fn get_icon() -> Result<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
fn from_memory(value: &[u8]) -> Result<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
fn path<P: AsRef<Path>>(path: P, size: Option<(u32, u32)>) -> Result<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
|
||||
use crate::trayicon::DioxusTrayIcon;
|
||||
|
||||
fn load_image_from_memory(value: &[u8]) -> Result<(Vec<u8>, u32, u32)> {
|
||||
let img = load_from_memory(value)?;
|
||||
let rgba = img.to_rgba8();
|
||||
let (width, height) = img.dimensions();
|
||||
Ok((rgba.to_vec(), width, height))
|
||||
}
|
||||
|
||||
fn load_image_from_path<P: AsRef<Path>>(path: P) -> Result<(Vec<u8>, u32, u32)> {
|
||||
let img = ImageReader::open(path)?.decode()?;
|
||||
let rgba = img.to_rgba8();
|
||||
let (width, height) = img.dimensions();
|
||||
Ok((rgba.to_vec(), width, height))
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
|
||||
impl DioxusIconTrait for DioxusTrayIcon {
|
||||
fn get_icon() -> Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
#[cfg(target_os = "windows")]
|
||||
if let Ok(icon) = DioxusTrayIcon::from_resource(32512, None) {
|
||||
return Ok(icon);
|
||||
}
|
||||
DioxusTrayIcon::from_rgba(
|
||||
FALLBACK_ICON_RGBA.to_vec(),
|
||||
FALLBACK_ICON_WIDTH,
|
||||
FALLBACK_ICON_HEIGHT,
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn from_memory(value: &[u8]) -> Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let (icon, width, height) = load_image_from_memory(value)?;
|
||||
DioxusTrayIcon::from_rgba(icon, width, height).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn path<P: AsRef<Path>>(path: P, size: Option<(u32, u32)>) -> Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let (img, width, height) = load_image_from_path(path)?;
|
||||
if let Some((width, height)) = size {
|
||||
Ok(DioxusTrayIcon::from_rgba(img, width, height)?)
|
||||
} else {
|
||||
Ok(DioxusTrayIcon::from_rgba(img, width, height)?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "ios", target_os = "android")))]
|
||||
use crate::menubar::DioxusMenuIcon;
|
||||
|
||||
#[cfg(not(any(target_os = "ios", target_os = "android")))]
|
||||
impl DioxusIconTrait for DioxusMenuIcon {
|
||||
fn get_icon() -> Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
#[cfg(target_os = "windows")]
|
||||
if let Ok(icon) = DioxusMenuIcon::from_resource(32512, None) {
|
||||
return Ok(icon);
|
||||
}
|
||||
DioxusMenuIcon::from_rgba(
|
||||
FALLBACK_ICON_RGBA.to_vec(),
|
||||
FALLBACK_ICON_WIDTH,
|
||||
FALLBACK_ICON_HEIGHT,
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn from_memory(value: &[u8]) -> Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let (icon, width, height) = load_image_from_memory(value)?;
|
||||
DioxusMenuIcon::from_rgba(icon, width, height).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn path<P: AsRef<Path>>(path: P, size: Option<(u32, u32)>) -> Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let (img, width, height) = load_image_from_path(path)?;
|
||||
if let Some((width, height)) = size {
|
||||
Ok(DioxusMenuIcon::from_rgba(img, width, height)?)
|
||||
} else {
|
||||
Ok(DioxusMenuIcon::from_rgba(img, width, height)?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use tao::window::Icon;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use tao::platform::windows::IconExtWindows;
|
||||
|
||||
impl DioxusIconTrait for Icon {
|
||||
fn get_icon() -> Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
#[cfg(target_os = "windows")]
|
||||
if let Ok(icon) = Icon::from_resource(32512, None) {
|
||||
return Ok(icon);
|
||||
}
|
||||
Icon::from_rgba(
|
||||
FALLBACK_ICON_RGBA.to_vec(),
|
||||
FALLBACK_ICON_WIDTH,
|
||||
FALLBACK_ICON_HEIGHT,
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn from_memory(value: &[u8]) -> Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let (icon, width, height) = load_image_from_memory(value)?;
|
||||
Icon::from_rgba(icon, width, height).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn path<P: AsRef<Path>>(path: P, size: Option<(u32, u32)>) -> Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let (img, width, height) = load_image_from_path(path)?;
|
||||
if let Some((width, height)) = size {
|
||||
Ok(Icon::from_rgba(img, width, height)?)
|
||||
} else {
|
||||
Ok(Icon::from_rgba(img, width, height)?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides the default icon of the app.
|
||||
///
|
||||
/// On Windows this prefers the icon embedded as resource id `IDI::APPLICATION`
|
||||
/// (32512) by `dx`'s bundler, falling back to a generic Dioxus icon when the
|
||||
/// resource is missing (e.g. when running with plain `cargo run`). On all
|
||||
/// other platforms the bundled fallback icon is returned directly.
|
||||
pub fn default_icon<T: DioxusIconTrait>() -> Result<T> {
|
||||
T::get_icon()
|
||||
}
|
||||
|
||||
/// Helper function to load image from include_bytes!("image.png")
|
||||
pub fn icon_from_memory<T: DioxusIconTrait>(value: &[u8]) -> Result<T> {
|
||||
T::from_memory(value)
|
||||
}
|
||||
|
||||
/// Helper function to load image from path
|
||||
pub fn icon_from_path<T: DioxusIconTrait, P: AsRef<Path>>(
|
||||
path: P,
|
||||
size: Option<(u32, u32)>,
|
||||
) -> Result<T> {
|
||||
T::path(path, size)
|
||||
}
|
||||
@@ -9,6 +9,7 @@ mod android_sync_lock;
|
||||
mod app;
|
||||
mod assets;
|
||||
mod config;
|
||||
mod default_icon;
|
||||
mod desktop_context;
|
||||
mod document;
|
||||
mod edits;
|
||||
@@ -50,6 +51,7 @@ pub mod trayicon;
|
||||
// Public exports
|
||||
pub use assets::AssetRequest;
|
||||
pub use config::{Config, WindowCloseBehaviour};
|
||||
pub use default_icon::{default_icon, icon_from_memory, icon_from_path};
|
||||
pub use desktop_context::{
|
||||
window, DesktopContext, DesktopService, PendingDesktopContext, WeakDesktopContext,
|
||||
};
|
||||
|
||||
@@ -5,6 +5,11 @@ pub type DioxusMenu = muda::Menu;
|
||||
#[cfg(any(target_os = "ios", target_os = "android"))]
|
||||
pub type DioxusMenu = ();
|
||||
|
||||
#[cfg(not(any(target_os = "ios", target_os = "android")))]
|
||||
pub type DioxusMenuIcon = muda::Icon;
|
||||
#[cfg(any(target_os = "ios", target_os = "android"))]
|
||||
pub type DioxusMenuIcon = ();
|
||||
|
||||
/// Initializes the menu bar for the window.
|
||||
#[allow(unused)]
|
||||
pub fn init_menu_bar(menu: &DioxusMenu, window: &Window) {
|
||||
|
||||
@@ -28,20 +28,21 @@ pub type DioxusTray = ();
|
||||
pub fn init_tray_icon(menu: DioxusTrayMenu, icon: Option<DioxusTrayIcon>) -> DioxusTray {
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
{
|
||||
let builder = tray_icon::TrayIconBuilder::new()
|
||||
.with_menu(Box::new(menu))
|
||||
.with_menu_on_left_click(false)
|
||||
.with_icon(match icon {
|
||||
Some(value) => value,
|
||||
None => tray_icon::Icon::from_rgba(
|
||||
include_bytes!("./assets/default_icon.bin").to_vec(),
|
||||
460,
|
||||
460,
|
||||
)
|
||||
.expect("image parse failed"),
|
||||
});
|
||||
let icon = icon.map(Ok).unwrap_or_else(crate::default_icon);
|
||||
|
||||
provide_context(builder.build().expect("tray icon builder failed"))
|
||||
let tray = tray_icon::TrayIconBuilder::new()
|
||||
.with_menu(Box::new(menu))
|
||||
.with_menu_on_left_click(false);
|
||||
|
||||
let tray = match icon {
|
||||
Ok(icon) => tray.with_icon(icon),
|
||||
Err(err) => {
|
||||
tracing::trace!("No tray icon: {:?}", err);
|
||||
tray
|
||||
}
|
||||
};
|
||||
|
||||
provide_context(tray.build().expect("tray icon builder failed"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -234,14 +234,7 @@ impl WebviewInstance {
|
||||
|
||||
// We assume that if the icon is None in cfg, then the user just didnt set it
|
||||
if cfg.window.window.window_icon.is_none() {
|
||||
window = window.with_window_icon(Some(
|
||||
tao::window::Icon::from_rgba(
|
||||
include_bytes!("./assets/default_icon.bin").to_vec(),
|
||||
460,
|
||||
460,
|
||||
)
|
||||
.expect("image parse failed"),
|
||||
));
|
||||
window = window.with_window_icon(crate::default_icon().ok());
|
||||
}
|
||||
|
||||
let window = Arc::new(window.build(&shared.target).unwrap());
|
||||
|
||||
Reference in new issue
Block a user