Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dark mode support #305

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion native-windows-gui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ keywords = ["gui", "ui", "windows"]
winapi = { version = "0.3", features = [
"winuser", "wingdi", "winbase", "libloaderapi", "processthreadsapi",
"errhandlingapi", "winerror", "commctrl", "sysinfoapi", "shobjidl", "combaseapi",
"commdlg", "d2d1", "objbase", "dwrite", "winnls", "shellapi", "wincodec", "stringapiset"] }
"commdlg", "d2d1", "objbase", "dwrite", "winnls", "shellapi", "wincodec", "stringapiset", "dwmapi", "impl-default"] }

lazy_static = "1.4.0"
bitflags = "1.1.0"
Expand Down
29 changes: 28 additions & 1 deletion native-windows-gui/src/win32/base_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ use std::ptr;
use winapi::shared::windef::HWND;
use winapi::shared::minwindef::DWORD;
use crate::ControlHandle;
use std::ffi::OsString;
use std::ffi::{CStr, OsString};
use std::os::windows::ffi::OsStringExt;
use winapi::ctypes::c_char;

pub const CUSTOM_ID_BEGIN: u32 = 10000;

Expand Down Expand Up @@ -112,3 +114,28 @@ pub unsafe fn get_system_error() -> (DWORD, String) {

(code, error_message)
}

/// Converts a wide C string pointer to a string.
///
/// # Arguments
///
/// * `ptr`: Pointer to a null-terminated Rust string.
///
/// returns: String
pub fn cwstr_to_str(ptr: isize) -> String {
if ptr == 0 {
return "".to_string()
}

let wide_ptr = ptr as *const u16;

let mut len = 0;
while unsafe { *wide_ptr.add(len) } != 0 {
len += 1;
}

let slice = unsafe { std::slice::from_raw_parts(wide_ptr, len) };

let os_string = OsString::from_wide(slice);
os_string.to_string_lossy().into_owned()
}
87 changes: 80 additions & 7 deletions native-windows-gui/src/win32/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@ Native Windows GUI windowing base. Includes events dispatching and window creati

Warning. Not for the faint of heart.
*/
use winapi::shared::minwindef::{BOOL, UINT, DWORD, HMODULE, WPARAM, LPARAM, LRESULT};
use winapi::shared::minwindef::{BOOL, UINT, DWORD, HMODULE, WPARAM, LPARAM, LRESULT, TRUE, LPVOID};
use winapi::shared::windef::{HWND, HMENU, HBRUSH};
use winapi::shared::basetsd::{DWORD_PTR, UINT_PTR};
use winapi::um::winuser::{WNDPROC, NMHDR, IDCANCEL, IDOK};
use winapi::um::commctrl::{NMTTDISPINFOW, SUBCLASSPROC};
use super::base_helper::{CUSTOM_ID_BEGIN, to_utf16};
use super::window_helper::{NOTICE_MESSAGE, NWG_INIT, NWG_TRAY, NWG_TIMER_TICK, NWG_TIMER_STOP};
use winapi::um::winuser::{WNDPROC, NMHDR, IDCANCEL, IDOK, MAKEINTRESOURCEA, SetPropW, SystemParametersInfoW, SPI_GETHIGHCONTRAST, HIGHCONTRASTW, HCF_HIGHCONTRASTON, WM_THEMECHANGED, WM_SETTINGCHANGE};
use winapi::um::commctrl::{InitCommonControls, NMTTDISPINFOW, SUBCLASSPROC};
use super::base_helper::{CUSTOM_ID_BEGIN, cwstr_to_str, to_utf16};
use super::window_helper::{NOTICE_MESSAGE, NWG_INIT, NWG_TRAY, NWG_TIMER_TICK, NWG_TIMER_STOP, send_message};
use super::high_dpi;
use crate::controls::ControlHandle;
use crate::{Event, EventData, NwgError};
use std::{ptr, mem};
use std::rc::Rc;
use std::ffi::OsString;
use std::ffi::{c_char, c_void, CStr, CString, OsString};
use std::os::windows::prelude::OsStringExt;
use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering};

Expand Down Expand Up @@ -353,6 +353,58 @@ pub fn unbind_raw_event_handler(handler: &RawEventHandler) -> Result<(), NwgErro
}
}


unsafe fn is_dark_mode_active() -> bool {
type FnIsDarkModeAllowedForWindow = extern "stdcall" fn(hwnd: HWND) -> bool;
type FnShouldAppsUseDarkMode = extern "stdcall" fn() -> bool;
type FnIsHighContrast = extern "stdcall" fn() -> bool;

let h_uxtheme = LoadLibraryExW(to_utf16("uxtheme.dll").as_ptr(), 0 as HANDLE, LOAD_LIBRARY_SEARCH_SYSTEM32);

if h_uxtheme.is_null() {
println!("Failed to load uxtheme.dll");
return false;
}

let should_apps_use_dark_mode: FnShouldAppsUseDarkMode = mem::transmute(GetProcAddress(h_uxtheme, MAKEINTRESOURCEA(132)));

let mut is_high_contrast = false;
let mut high_contrast = HIGHCONTRASTW::default();
high_contrast.cbSize = size_of::<HIGHCONTRASTW>() as UINT;

if SystemParametersInfoW(SPI_GETHIGHCONTRAST, high_contrast.cbSize, &mut high_contrast as *mut _ as *mut winapi::ctypes::c_void, 0) != 0 {
is_high_contrast = (high_contrast.dwFlags & HCF_HIGHCONTRASTON) != 0;
}

let result = should_apps_use_dark_mode() && !is_high_contrast;
FreeLibrary(h_uxtheme);
result
}

/// Updates dark mode status for a top-level window.
/// This function only affects the titlebar's color.
///
/// # Arguments
///
/// * `hwnd`: Handle to a top-level window.
///
/// returns: Result<(), String>
unsafe fn update_dark_mode_for_window(hwnd: HWND) -> Result<(), String> {
let value: BOOL = is_dark_mode_active() as BOOL;
let result: HRESULT = winapi::um::dwmapi::DwmSetWindowAttribute(
hwnd,
20,
&value as *const BOOL as *const _,
size_of::<BOOL>() as _,
);

if result != 0 {
return Err("DwmSetWindowAttribute failed".to_string());
}

return Ok(());
}

/**
High level function that handle the creation of custom window control or built in window control
*/
Expand Down Expand Up @@ -714,10 +766,26 @@ unsafe extern "system" fn process_events(hwnd: HWND, msg: UINT, w: WPARAM, l: LP
WM_LBUTTONDOWN => callback(Event::OnMousePress(MousePressEvent::MousePressLeftDown), NO_DATA, base_handle),
WM_RBUTTONUP => callback(Event::OnMousePress(MousePressEvent::MousePressRightUp), NO_DATA, base_handle),
WM_RBUTTONDOWN => callback(Event::OnMousePress(MousePressEvent::MousePressRightDown), NO_DATA, base_handle),
WM_SETTINGCHANGE => {
let str = cwstr_to_str(l);
match str.as_str() {
"ImmersiveColorSet" => {
if update_dark_mode_for_window(hwnd).is_err() {
println!("enable_dark_mode_for_window failed, is the Windows version too old?");
}
},
_ => {}
}
},
NOTICE_MESSAGE => callback(Event::OnNotice, NO_DATA, ControlHandle::Notice(hwnd, w as u32)),
NWG_TIMER_STOP => callback(Event::OnTimerStop, NO_DATA, ControlHandle::Timer(hwnd, w as u32)),
NWG_TIMER_TICK => callback(Event::OnTimerTick, NO_DATA, ControlHandle::Timer(hwnd, w as u32)),
NWG_INIT => callback(Event::OnInit, NO_DATA, base_handle),
NWG_INIT => {
if update_dark_mode_for_window(hwnd).is_err() {
println!("enable_dark_mode_for_window failed, is the Windows version too old?");
}
callback(Event::OnInit, NO_DATA, base_handle)
},
WM_CLOSE => {
let mut should_exit = true;
let data = EventData::OnWindowClose(WindowCloseData { data: &mut should_exit as *mut bool });
Expand Down Expand Up @@ -1059,6 +1127,11 @@ unsafe fn is_textbox_control(hwnd: HWND) -> bool {
//

#[cfg(target_env="gnu")] use std::{sync::Mutex, collections::HashMap};
use std::ptr::null;
use winapi::ctypes::wchar_t;
use winapi::shared::winerror::HRESULT;
use winapi::um::libloaderapi::{FreeLibrary, GetProcAddress, LOAD_LIBRARY_SEARCH_SYSTEM32, LoadLibraryExW};
use winapi::um::winnt::{CHAR, HANDLE, LPWSTR, VOID};

#[cfg(target_env="gnu")]
type SubclassId = (usize, usize, UINT_PTR);
Expand Down