Skip to content

Commit

Permalink
feat: add a function to set theme dynamically (#937)
Browse files Browse the repository at this point in the history
  • Loading branch information
Legend-Master committed Sep 19, 2024
1 parent ad652e5 commit 1a085ad
Show file tree
Hide file tree
Showing 11 changed files with 203 additions and 88 deletions.
5 changes: 5 additions & 0 deletions .changes/set-theme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tao": "patch"
---

Add a function `Window::set_theme` and `EventLoopWindowTarget::set_them`to set theme after window or event loop creation.
35 changes: 22 additions & 13 deletions examples/theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
// Copyright 2021-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0

#[allow(clippy::single_match)]
use tao::{event::KeyEvent, keyboard::KeyCode};

fn main() {
use tao::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
window::{Theme, WindowBuilder},
};

env_logger::init();
Expand All @@ -20,22 +21,30 @@ fn main() {
.unwrap();

println!("Initial theme: {:?}", window.theme());
println!("Press D for Dark Mode");
println!("Press L for Light Mode");
println!("Press A for Auto Mode");

event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;

match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
Event::WindowEvent {
event: WindowEvent::ThemeChanged(theme),
window_id,
..
} if window_id == window.id() => {
println!("Theme is changed: {:?}", theme)
}
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::KeyboardInput {
event: KeyEvent { physical_key, .. },
..
} => match physical_key {
KeyCode::KeyD => window.set_theme(Some(Theme::Dark)),
KeyCode::KeyL => window.set_theme(Some(Theme::Light)),
KeyCode::KeyA => window.set_theme(None),
_ => {}
},
WindowEvent::ThemeChanged(theme) => {
println!("Theme is changed: {theme:?}")
}
_ => (),
},
_ => (),
}
});
Expand Down
27 changes: 25 additions & 2 deletions src/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@ use instant::Instant;
use std::{error, fmt, marker::PhantomData, ops::Deref};

use crate::{
dpi::PhysicalPosition, error::ExternalError, event::Event, monitor::MonitorHandle, platform_impl,
window::ProgressBarState,
dpi::PhysicalPosition,
error::ExternalError,
event::Event,
monitor::MonitorHandle,
platform_impl,
window::{ProgressBarState, Theme},
};

/// Provides a way to retrieve events from the system and from the windows that were registered to
Expand Down Expand Up @@ -296,6 +300,25 @@ impl<T> EventLoopWindowTarget<T> {
#[cfg(any(target_os = "linux", target_os = "macos"))]
self.p.set_progress_bar(_progress)
}

/// Sets the theme for the application.
///
/// ## Platform-specific
///
/// - **iOS / Android:** Unsupported.
#[inline]
pub fn set_theme(&self, theme: Option<Theme>) {
#[cfg(any(
windows,
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "macos",
))]
self.p.set_theme(theme)
}
}

#[cfg(feature = "rwh_05")]
Expand Down
26 changes: 24 additions & 2 deletions src/platform_impl/linux/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use gtk::{
cairo, gdk, gio,
glib::{self},
prelude::*,
Settings,
};

use crate::{
Expand All @@ -33,7 +34,9 @@ use crate::{
keyboard::ModifiersState,
monitor::MonitorHandle as RootMonitorHandle,
platform_impl::platform::{device, DEVICE_ID},
window::{CursorIcon, Fullscreen, ProgressBarState, ResizeDirection, WindowId as RootWindowId},
window::{
CursorIcon, Fullscreen, ProgressBarState, ResizeDirection, Theme, WindowId as RootWindowId,
},
};

use super::{
Expand Down Expand Up @@ -153,7 +156,17 @@ impl<T> EventLoopWindowTarget<T> {
.window_requests_tx
.send((WindowId::dummy(), WindowRequest::ProgressBarState(progress)))
{
log::warn!("Fail to send update progress bar request: {}", e);
log::warn!("Fail to send update progress bar request: {e}");
}
}

#[inline]
pub fn set_theme(&self, theme: Option<Theme>) {
if let Err(e) = self
.window_requests_tx
.send((WindowId::dummy(), WindowRequest::SetTheme(theme)))
{
log::warn!("Fail to send update theme request: {e}");
}
}
}
Expand Down Expand Up @@ -392,6 +405,7 @@ impl<T: 'static> EventLoop<T> {
};
}
WindowRequest::ProgressBarState(_) => unreachable!(),
WindowRequest::SetTheme(_) => unreachable!(),
WindowRequest::WireUpEvents {
transparent,
fullscreen,
Expand Down Expand Up @@ -857,6 +871,14 @@ impl<T: 'static> EventLoop<T> {
WindowRequest::ProgressBarState(state) => {
taskbar.update(state);
}
WindowRequest::SetTheme(theme) => {
if let Some(settings) = Settings::default() {
match theme {
Some(Theme::Dark) => settings.set_gtk_application_prefer_dark_theme(true),
Some(Theme::Light) | None => settings.set_gtk_application_prefer_dark_theme(false),
}
}
}
_ => unreachable!(),
}
}
Expand Down
19 changes: 15 additions & 4 deletions src/platform_impl/linux/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ pub struct Window {
inner_size_constraints: RefCell<WindowSizeConstraints>,
/// Draw event Sender
draw_tx: crossbeam_channel::Sender<WindowId>,
preferred_theme: Option<Theme>,
preferred_theme: RefCell<Option<Theme>>,
}

impl Window {
Expand Down Expand Up @@ -306,7 +306,7 @@ impl Window {
minimized,
fullscreen: RefCell::new(attributes.fullscreen),
inner_size_constraints: RefCell::new(attributes.inner_size_constraints),
preferred_theme,
preferred_theme: RefCell::new(preferred_theme),
};

win.set_skip_taskbar(pl_attribs.skip_taskbar);
Expand Down Expand Up @@ -385,7 +385,7 @@ impl Window {
minimized,
fullscreen: RefCell::new(None),
inner_size_constraints: RefCell::new(WindowSizeConstraints::default()),
preferred_theme: None,
preferred_theme: RefCell::new(None),
};

Ok(win)
Expand Down Expand Up @@ -941,7 +941,7 @@ impl Window {
}

pub fn theme(&self) -> Theme {
if let Some(theme) = self.preferred_theme {
if let Some(theme) = *self.preferred_theme.borrow() {
return theme;
}

Expand All @@ -954,6 +954,16 @@ impl Window {

Theme::Light
}

pub fn set_theme(&self, theme: Option<Theme>) {
*self.preferred_theme.borrow_mut() = theme;
if let Err(e) = self
.window_requests_tx
.send((WindowId::dummy(), WindowRequest::SetTheme(theme)))
{
log::warn!("Fail to send set theme request: {e}");
}
}
}

// We need GtkWindow to initialize WebView, so we have to keep it in the field.
Expand Down Expand Up @@ -992,6 +1002,7 @@ pub enum WindowRequest {
},
SetVisibleOnAllWorkspaces(bool),
ProgressBarState(ProgressBarState),
SetTheme(Option<Theme>),
}

impl Drop for Window {
Expand Down
9 changes: 8 additions & 1 deletion src/platform_impl/macos/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ use crate::{
util::{self, IdRef},
},
platform_impl::set_progress_indicator,
window::ProgressBarState,
window::{ProgressBarState, Theme},
};

use super::window::set_ns_theme;

#[derive(Default)]
pub struct PanicInfo {
inner: Cell<Option<Box<dyn Any + Send + 'static>>>,
Expand Down Expand Up @@ -120,6 +122,11 @@ impl<T: 'static> EventLoopWindowTarget<T> {
pub fn set_progress_bar(&self, progress: ProgressBarState) {
set_progress_indicator(progress);
}

#[inline]
pub fn set_theme(&self, theme: Option<Theme>) {
set_ns_theme(theme)
}
}

pub struct EventLoop<T: 'static> {
Expand Down
23 changes: 16 additions & 7 deletions src/platform_impl/macos/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,17 +337,20 @@ pub(super) fn get_ns_theme() -> Theme {
}
}

pub(super) fn set_ns_theme(theme: Theme) {
let name = match theme {
Theme::Dark => "NSAppearanceNameDarkAqua",
Theme::Light => "NSAppearanceNameAqua",
};
pub(super) fn set_ns_theme(theme: Option<Theme>) {
unsafe {
let app_class = class!(NSApplication);
let app: id = msg_send![app_class, sharedApplication];
let has_theme: BOOL = msg_send![app, respondsToSelector: sel!(effectiveAppearance)];
if has_theme == YES {
let name = NSString::alloc(nil).init_str(name);
let name = if let Some(theme) = theme {
NSString::alloc(nil).init_str(match theme {
Theme::Dark => "NSAppearanceNameDarkAqua",
Theme::Light => "NSAppearanceNameAqua",
})
} else {
nil
};
let appearance: id = msg_send![class!(NSAppearance), appearanceNamed: name];
let _: () = msg_send![app, setAppearance: appearance];
}
Expand Down Expand Up @@ -547,7 +550,7 @@ impl UnownedWindow {

match cloned_preferred_theme {
Some(theme) => {
set_ns_theme(theme);
set_ns_theme(Some(theme));
let mut state = window.shared_state.lock().unwrap();
state.current_theme = theme.clone();
}
Expand Down Expand Up @@ -1417,6 +1420,12 @@ impl UnownedWindow {
state.current_theme
}

pub fn set_theme(&self, theme: Option<Theme>) {
set_ns_theme(theme);
let mut state = self.shared_state.lock().unwrap();
state.current_theme = theme.unwrap_or_else(get_ns_theme);
}

pub fn set_content_protection(&self, enabled: bool) {
unsafe {
let _: () = msg_send![*self.ns_window, setSharingType: !enabled as i32];
Expand Down
46 changes: 21 additions & 25 deletions src/platform_impl/windows/dark_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ use once_cell::sync::Lazy;
use windows::{
core::{s, w, PCSTR, PSTR},
Win32::{
Foundation::{BOOL, HANDLE, HMODULE, HWND},
Foundation::{BOOL, HANDLE, HMODULE, HWND, WPARAM},
Graphics::Dwm::{DwmSetWindowAttribute, DWMWINDOWATTRIBUTE},
System::LibraryLoader::*,
UI::{Accessibility::*, WindowsAndMessaging::*},
},
Expand Down Expand Up @@ -160,40 +161,35 @@ pub fn allow_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) {
}
}

type SetWindowCompositionAttribute =
unsafe extern "system" fn(HWND, *mut WINDOWCOMPOSITIONATTRIBDATA) -> BOOL;
static SET_WINDOW_COMPOSITION_ATTRIBUTE: Lazy<Option<SetWindowCompositionAttribute>> =
Lazy::new(|| get_function!("user32.dll", SetWindowCompositionAttribute));

type WINDOWCOMPOSITIONATTRIB = u32;
const WCA_USEDARKMODECOLORS: WINDOWCOMPOSITIONATTRIB = 26;
#[repr(C)]
struct WINDOWCOMPOSITIONATTRIBDATA {
Attrib: WINDOWCOMPOSITIONATTRIB,
pvData: *mut c_void,
cbData: usize,
}

fn refresh_titlebar_theme_color(hwnd: HWND, is_dark_mode: bool) {
// SetWindowCompositionAttribute needs a bigbool (i32), not bool.
let mut is_dark_mode_bigbool: i32 = is_dark_mode.into();

if let Some(ver) = *WIN10_BUILD_VERSION {
if ver < 18362 {
if ver < 17763 {
let mut is_dark_mode_bigbool: i32 = is_dark_mode.into();
unsafe {
let _ = SetPropW(
hwnd,
w!("UseImmersiveDarkModeColors"),
HANDLE(&mut is_dark_mode_bigbool as *mut _ as _),
);
}
} else if let Some(set_window_composition_attribute) = *SET_WINDOW_COMPOSITION_ATTRIBUTE {
let mut data = WINDOWCOMPOSITIONATTRIBDATA {
Attrib: WCA_USEDARKMODECOLORS,
pvData: &mut is_dark_mode_bigbool as *mut _ as _,
cbData: std::mem::size_of_val(&is_dark_mode_bigbool) as _,
} else {
// https://github.com/MicrosoftDocs/sdk-api/pull/966/files
let dwmwa_use_immersive_dark_mode = if ver > 18985 {
DWMWINDOWATTRIBUTE(20)
} else {
DWMWINDOWATTRIBUTE(19)
};
let _ = unsafe { set_window_composition_attribute(hwnd, &mut data as *mut _) };
let dark_mode = BOOL::from(is_dark_mode);
unsafe {
let _ = DwmSetWindowAttribute(
hwnd,
dwmwa_use_immersive_dark_mode,
&dark_mode as *const BOOL as *const c_void,
std::mem::size_of::<BOOL>() as u32,
);
}
unsafe { DefWindowProcW(hwnd, WM_NCACTIVATE, None, None) };
unsafe { DefWindowProcW(hwnd, WM_NCACTIVATE, WPARAM(true.into()), None) };
}
}
}
Expand Down
Loading

0 comments on commit 1a085ad

Please sign in to comment.