use egui::Color32;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum VibrancyEffect {
#[default]
Auto,
Blur,
Acrylic,
Mica,
MicaTabbed,
#[cfg(target_os = "macos")]
MacVibrancy(MacVibrancyMaterial),
}
#[cfg(target_os = "macos")]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum MacVibrancyMaterial {
Titlebar,
Selection,
Menu,
Popover,
Sidebar,
HeaderView,
Sheet,
WindowBackground,
#[default]
HudWindow,
FullScreenUI,
ToolTip,
ContentBackground,
UnderWindowBackground,
UnderPageBackground,
}
#[derive(Debug, Clone)]
pub struct VibrancyConfig {
pub effect: VibrancyEffect,
pub tint: Option<(u8, u8, u8, u8)>,
pub dark_mode: Option<bool>,
}
impl Default for VibrancyConfig {
fn default() -> Self {
Self {
effect: VibrancyEffect::Auto,
tint: None,
dark_mode: None,
}
}
}
impl VibrancyConfig {
pub fn new(effect: VibrancyEffect) -> Self {
Self {
effect,
..Default::default()
}
}
pub fn with_tint(mut self, color: Color32) -> Self {
let [r, g, b, a] = color.to_array();
self.tint = Some((r, g, b, a));
self
}
pub fn with_dark_mode(mut self, dark: bool) -> Self {
self.dark_mode = Some(dark);
self
}
pub fn blur() -> Self {
Self::new(VibrancyEffect::Blur)
}
pub fn acrylic() -> Self {
Self::new(VibrancyEffect::Acrylic)
}
pub fn mica() -> Self {
Self::new(VibrancyEffect::Mica)
}
pub fn mica_dark() -> Self {
Self::new(VibrancyEffect::Mica).with_dark_mode(true)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VibrancyResult {
Applied(VibrancyEffect),
Unsupported,
EffectUnavailable,
Failed,
}
impl VibrancyResult {
pub fn is_applied(&self) -> bool {
matches!(self, Self::Applied(_))
}
}
#[cfg(target_os = "windows")]
pub fn apply_vibrancy<W: raw_window_handle::HasWindowHandle>(
window: &W,
config: VibrancyConfig,
) -> VibrancyResult {
use window_vibrancy::{apply_acrylic, apply_blur, apply_mica, apply_tabbed};
let Ok(handle) = window.window_handle() else {
return VibrancyResult::Failed;
};
let effect = match config.effect {
VibrancyEffect::Auto => {
if apply_mica(&handle, config.dark_mode).is_ok() {
return VibrancyResult::Applied(VibrancyEffect::Mica);
}
if apply_acrylic(&handle, config.tint).is_ok() {
return VibrancyResult::Applied(VibrancyEffect::Acrylic);
}
if apply_blur(&handle, config.tint).is_ok() {
return VibrancyResult::Applied(VibrancyEffect::Blur);
}
return VibrancyResult::EffectUnavailable;
}
VibrancyEffect::Blur => {
if apply_blur(&handle, config.tint).is_ok() {
VibrancyEffect::Blur
} else {
return VibrancyResult::EffectUnavailable;
}
}
VibrancyEffect::Acrylic => {
if apply_acrylic(&handle, config.tint).is_ok() {
VibrancyEffect::Acrylic
} else {
return VibrancyResult::EffectUnavailable;
}
}
VibrancyEffect::Mica => {
if apply_mica(&handle, config.dark_mode).is_ok() {
VibrancyEffect::Mica
} else {
return VibrancyResult::EffectUnavailable;
}
}
VibrancyEffect::MicaTabbed => {
if apply_tabbed(&handle, config.dark_mode).is_ok() {
VibrancyEffect::MicaTabbed
} else {
return VibrancyResult::EffectUnavailable;
}
}
#[cfg(target_os = "macos")]
VibrancyEffect::MacVibrancy(_) => return VibrancyResult::EffectUnavailable,
};
VibrancyResult::Applied(effect)
}
#[cfg(target_os = "macos")]
pub fn apply_vibrancy<W: raw_window_handle::HasWindowHandle>(
window: &W,
config: VibrancyConfig,
) -> VibrancyResult {
use window_vibrancy::{apply_vibrancy as wv_apply, NSVisualEffectMaterial};
let Ok(handle) = window.window_handle() else {
return VibrancyResult::Failed;
};
let material = match config.effect {
VibrancyEffect::Auto | VibrancyEffect::Blur => NSVisualEffectMaterial::HudWindow,
VibrancyEffect::Acrylic => NSVisualEffectMaterial::FullScreenUI,
VibrancyEffect::Mica | VibrancyEffect::MicaTabbed => NSVisualEffectMaterial::Sidebar,
VibrancyEffect::MacVibrancy(mat) => match mat {
MacVibrancyMaterial::Titlebar => NSVisualEffectMaterial::Titlebar,
MacVibrancyMaterial::Selection => NSVisualEffectMaterial::Selection,
MacVibrancyMaterial::Menu => NSVisualEffectMaterial::Menu,
MacVibrancyMaterial::Popover => NSVisualEffectMaterial::Popover,
MacVibrancyMaterial::Sidebar => NSVisualEffectMaterial::Sidebar,
MacVibrancyMaterial::HeaderView => NSVisualEffectMaterial::HeaderView,
MacVibrancyMaterial::Sheet => NSVisualEffectMaterial::Sheet,
MacVibrancyMaterial::WindowBackground => NSVisualEffectMaterial::WindowBackground,
MacVibrancyMaterial::HudWindow => NSVisualEffectMaterial::HudWindow,
MacVibrancyMaterial::FullScreenUI => NSVisualEffectMaterial::FullScreenUI,
MacVibrancyMaterial::ToolTip => NSVisualEffectMaterial::Tooltip,
MacVibrancyMaterial::ContentBackground => NSVisualEffectMaterial::ContentBackground,
MacVibrancyMaterial::UnderWindowBackground => {
NSVisualEffectMaterial::UnderWindowBackground
}
MacVibrancyMaterial::UnderPageBackground => NSVisualEffectMaterial::UnderPageBackground,
},
};
if wv_apply(&handle, material, None, None).is_ok() {
VibrancyResult::Applied(config.effect)
} else {
VibrancyResult::EffectUnavailable
}
}
#[cfg(target_os = "linux")]
pub fn apply_vibrancy<W: raw_window_handle::HasWindowHandle>(
_window: &W,
_config: VibrancyConfig,
) -> VibrancyResult {
VibrancyResult::Unsupported
}
#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
pub fn apply_vibrancy<W: raw_window_handle::HasWindowHandle>(
_window: &W,
_config: VibrancyConfig,
) -> VibrancyResult {
VibrancyResult::Unsupported
}
#[cfg(target_os = "windows")]
pub fn clear_vibrancy<W: raw_window_handle::HasWindowHandle>(window: &W) -> bool {
use window_vibrancy::{clear_acrylic, clear_blur, clear_mica, clear_tabbed};
let Ok(handle) = window.window_handle() else {
return false;
};
let _ = clear_blur(&handle);
let _ = clear_acrylic(&handle);
let _ = clear_mica(&handle);
let _ = clear_tabbed(&handle);
true
}
#[cfg(target_os = "macos")]
pub fn clear_vibrancy<W: raw_window_handle::HasWindowHandle>(window: &W) -> bool {
use window_vibrancy::clear_vibrancy as wv_clear;
let Ok(handle) = window.window_handle() else {
return false;
};
wv_clear(&handle).is_ok()
}
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
pub fn clear_vibrancy<W: raw_window_handle::HasWindowHandle>(_window: &W) -> bool {
true }
pub fn is_vibrancy_supported() -> bool {
cfg!(any(target_os = "windows", target_os = "macos"))
}
pub fn available_effects() -> &'static [VibrancyEffect] {
#[cfg(target_os = "windows")]
{
&[
VibrancyEffect::Auto,
VibrancyEffect::Blur,
VibrancyEffect::Acrylic,
VibrancyEffect::Mica,
VibrancyEffect::MicaTabbed,
]
}
#[cfg(target_os = "macos")]
{
&[VibrancyEffect::Auto, VibrancyEffect::Blur]
}
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
{
&[]
}
}
#[derive(Clone)]
pub struct GlassFrame {
opacity: f32,
blur_radius: f32,
tint: Option<Color32>,
border: bool,
corner_radius: f32,
margin: f32,
}
impl Default for GlassFrame {
fn default() -> Self {
Self {
opacity: 0.6,
blur_radius: 8.0,
tint: None,
border: true,
corner_radius: 8.0,
margin: 12.0,
}
}
}
impl GlassFrame {
pub fn new() -> Self {
Self::default()
}
pub fn from_theme(theme: &crate::Theme) -> Self {
Self {
opacity: theme.glass_opacity,
blur_radius: theme.glass_blur_radius,
tint: theme.glass_tint,
border: theme.glass_border,
corner_radius: theme.radius_md,
margin: theme.spacing_sm,
}
}
pub fn opacity(mut self, opacity: f32) -> Self {
self.opacity = opacity.clamp(0.0, 1.0);
self
}
pub fn blur_radius(mut self, radius: f32) -> Self {
self.blur_radius = radius;
self
}
pub fn tint(mut self, color: Color32) -> Self {
self.tint = Some(color);
self
}
pub fn border(mut self, show: bool) -> Self {
self.border = show;
self
}
pub fn corner_radius(mut self, radius: f32) -> Self {
self.corner_radius = radius;
self
}
pub fn margin(mut self, margin: f32) -> Self {
self.margin = margin;
self
}
pub fn light() -> Self {
Self::new()
.tint(Color32::from_rgba_unmultiplied(255, 255, 255, 180))
.opacity(0.7)
}
pub fn dark() -> Self {
Self::new()
.tint(Color32::from_rgba_unmultiplied(30, 30, 40, 200))
.opacity(0.8)
}
pub fn frosted() -> Self {
Self::new().opacity(0.85).blur_radius(12.0)
}
pub fn show<R>(
self,
ui: &mut egui::Ui,
add_contents: impl FnOnce(&mut egui::Ui) -> R,
) -> egui::InnerResponse<R> {
let theme = crate::Theme::current(ui.ctx());
let fill = if let Some(tint) = self.tint {
let [r, g, b, _] = tint.to_array();
Color32::from_rgba_unmultiplied(r, g, b, (self.opacity * 255.0) as u8)
} else {
let [r, g, b, _] = theme.bg_primary.to_array();
Color32::from_rgba_unmultiplied(r, g, b, (self.opacity * 255.0) as u8)
};
let stroke = if self.border {
let border_alpha = (self.opacity * 0.3 * 255.0) as u8;
egui::Stroke::new(
1.0,
Color32::from_rgba_unmultiplied(255, 255, 255, border_alpha),
)
} else {
egui::Stroke::NONE
};
egui::Frame::new()
.fill(fill)
.stroke(stroke)
.corner_radius(self.corner_radius)
.inner_margin(self.margin)
.show(ui, add_contents)
}
}
pub fn transparent_viewport_builder() -> egui::ViewportBuilder {
egui::ViewportBuilder::default()
.with_transparent(true)
.with_decorations(false) }
pub trait TransparentApp {
fn transparent_clear_color() -> [f32; 4] {
[0.0, 0.0, 0.0, 0.0]
}
}
impl<T> TransparentApp for T {}