diff --git a/.changes/background_color.md b/.changes/background_color.md new file mode 100644 index 000000000..407751f48 --- /dev/null +++ b/.changes/background_color.md @@ -0,0 +1,6 @@ +--- +"tao": "patch" +--- + +Add `WindowAttributes::background_color`, `WindowBuilder::with_background_color`, and `Window::set_background_color` APIs to set and change window background color. + diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 069272311..a24155a0f 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -683,6 +683,8 @@ impl Window { )) } + pub fn set_background_color(&self, _color: Option) {} + pub fn set_ignore_cursor_events(&self, _ignore: bool) -> Result<(), error::ExternalError> { Err(error::ExternalError::NotSupported( error::NotSupportedError::new(), diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index d24f63d22..26db35e06 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -363,6 +363,8 @@ impl Inner { warn!("`Window::request_user_attention` is ignored on iOS") } + pub fn set_background_color(&self, _color: Option) {} + // Allow directly accessing the current monitor internally without unwrapping. fn current_monitor_inner(&self) -> RootMonitorHandle { unsafe { diff --git a/src/platform_impl/linux/event_loop.rs b/src/platform_impl/linux/event_loop.rs index e5229395d..a90f913ba 100644 --- a/src/platform_impl/linux/event_loop.rs +++ b/src/platform_impl/linux/event_loop.rs @@ -368,6 +368,28 @@ impl EventLoop { window.set_skip_taskbar_hint(skip); window.set_skip_pager_hint(skip) } + WindowRequest::BackgroundColor(css_provider, color) => { + unsafe { window.set_data("background_color", color) }; + + let style_context = window.style_context(); + style_context.remove_provider(&css_provider); + + if let Some(color) = color { + let theme = format!( + r#" + window {{ + background-color: rgba({},{},{},{}); + }} + "#, + color.0, + color.1, + color.2, + color.3 as f64 / 255.0 + ); + let _ = css_provider.load_from_data(theme.as_bytes()); + style_context.add_provider(&css_provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION); + }; + } WindowRequest::SetVisibleOnAllWorkspaces(visible) => { if visible { window.stick(); @@ -854,15 +876,36 @@ impl EventLoop { // Receive draw events of the window. let draw_clone = draw_tx.clone(); - window.connect_draw(move |_, cr| { + window.connect_draw(move |window, cr| { if let Err(e) = draw_clone.send(id) { log::warn!("Failed to send redraw event to event channel: {}", e); } if transparent { - cr.set_source_rgba(0., 0., 0., 0.); + let background_color = unsafe { + window + .data::>("background_color") + .and_then(|c| c.as_ref().clone()) + }; + + let rgba = background_color + .map(|(r, g, b, a)| (r as f64, g as f64, b as f64, a as f64 / 255.0)) + .unwrap_or((0., 0., 0., 0.)); + + let rect = window + .child() + .map(|c| c.allocation()) + .unwrap_or_else(|| window.allocation()); + + cr.rectangle( + rect.x() as _, + rect.y() as _, + rect.width() as _, + rect.height() as _, + ); + cr.set_source_rgba(rgba.0, rgba.1, rgba.2, rgba.3); cr.set_operator(cairo::Operator::Source); - let _ = cr.paint(); + let _ = cr.fill(); cr.set_operator(cairo::Operator::Over); } diff --git a/src/platform_impl/linux/window.rs b/src/platform_impl/linux/window.rs index bcf37e154..aeba25e18 100644 --- a/src/platform_impl/linux/window.rs +++ b/src/platform_impl/linux/window.rs @@ -16,7 +16,7 @@ use gtk::{ gdk::WindowState, glib::{self, translate::ToGlibPtr}, prelude::*, - Settings, + CssProvider, Settings, }; use crate::{ @@ -27,7 +27,7 @@ use crate::{ platform_impl::wayland::header::WlHeader, window::{ CursorIcon, Fullscreen, ProgressBarState, ResizeDirection, Theme, UserAttentionType, - WindowAttributes, WindowSizeConstraints, + WindowAttributes, WindowSizeConstraints, RGBA, }, }; @@ -69,6 +69,7 @@ pub struct Window { /// Draw event Sender draw_tx: crossbeam_channel::Sender, preferred_theme: RefCell>, + css_provider: CssProvider, } impl Window { @@ -324,9 +325,11 @@ impl Window { fullscreen: RefCell::new(attributes.fullscreen), inner_size_constraints: RefCell::new(attributes.inner_size_constraints), preferred_theme: RefCell::new(preferred_theme), + css_provider: CssProvider::new(), }; - win.set_skip_taskbar(pl_attribs.skip_taskbar); + let _ = win.set_skip_taskbar(pl_attribs.skip_taskbar); + win.set_background_color(attributes.background_color); Ok(win) } @@ -407,6 +410,7 @@ impl Window { fullscreen: RefCell::new(None), inner_size_constraints: RefCell::new(WindowSizeConstraints::default()), preferred_theme: RefCell::new(None), + css_provider: CssProvider::new(), }; Ok(win) @@ -456,6 +460,15 @@ impl Window { } } + pub fn set_background_color(&self, color: Option) { + if let Err(e) = self.window_requests_tx.send(( + self.window_id, + WindowRequest::BackgroundColor(self.css_provider.clone(), color), + )) { + log::warn!("Fail to send size request: {}", e); + } + } + pub fn inner_size(&self) -> PhysicalSize { let (width, height) = &*self.size; @@ -1029,6 +1042,7 @@ pub enum WindowRequest { SetVisibleOnAllWorkspaces(bool), ProgressBarState(ProgressBarState), SetTheme(Option), + BackgroundColor(CssProvider, Option), } impl Drop for Window { diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 6ac3ab4c0..929aeb4bc 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -459,6 +459,7 @@ pub struct UnownedWindow { pub shared_state: Arc>, decorations: AtomicBool, cursor_state: Weak>, + transparent: bool, pub inner_rect: Option>, } @@ -500,7 +501,22 @@ impl UnownedWindow { unsafe { if win_attribs.transparent { ns_window.setOpaque_(NO); - ns_window.setBackgroundColor_(NSColor::clearColor(nil)); + } + + if win_attribs.transparent || win_attribs.background_color.is_some() { + let color = win_attribs + .background_color + .map(|(r, g, b, a)| { + NSColor::colorWithRed_green_blue_alpha_( + nil, + r as f64, + g as f64, + b as f64, + a as f64 / 255.0, + ) + }) + .unwrap_or_else(|| NSColor::clearColor(nil)); + ns_window.setBackgroundColor_(color); } if win_attribs.inner_size_constraints.has_min() { @@ -530,6 +546,7 @@ impl UnownedWindow { // `WindowDelegate` to update the state. let fullscreen = win_attribs.fullscreen.take(); let maximized = win_attribs.maximized; + let transparent = win_attribs.transparent; let visible = win_attribs.visible; let focused = win_attribs.focused; let decorations = win_attribs.decorations; @@ -548,6 +565,7 @@ impl UnownedWindow { decorations: AtomicBool::new(decorations), cursor_state, inner_rect, + transparent, }); match cloned_preferred_theme { @@ -848,6 +866,30 @@ impl UnownedWindow { Ok(()) } + #[inline] + pub fn set_background_color(&self, color: Option) { + unsafe { + let color = color + .map(|(r, g, b, a)| { + NSColor::colorWithRed_green_blue_alpha_( + nil, + r as f64, + g as f64, + b as f64, + a as f64 / 255.0, + ) + }) + .unwrap_or_else(|| { + if self.transparent { + NSColor::clearColor(nil) + } else { + nil + } + }); + self.ns_window.setBackgroundColor_(color); + } + } + #[inline] pub fn drag_window(&self) -> Result<(), ExternalError> { unsafe { diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 5b9d4db59..ab5df3f79 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1113,6 +1113,25 @@ unsafe fn public_window_callback_inner( } } + win32wm::WM_ERASEBKGND => { + let w = subclass_input.window_state.lock(); + if let Some(color) = w.background_color { + let hdc = HDC(wparam.0 as *mut _); + let mut rc = RECT::default(); + if GetClientRect(window, &mut rc).is_ok() { + let brush = CreateSolidBrush(util::RGB(color.0, color.1, color.2)); + FillRect(hdc, &rc, brush); + let _ = DeleteObject(brush); + + result = ProcResult::Value(LRESULT(1)); + } else { + result = ProcResult::DefSubclassProc; + } + } else { + result = ProcResult::DefSubclassProc; + } + } + win32wm::WM_WINDOWPOSCHANGING => { let mut window_state = subclass_input.window_state.lock(); diff --git a/src/platform_impl/windows/util.rs b/src/platform_impl/windows/util.rs index a7568f27a..96879c0c7 100644 --- a/src/platform_impl/windows/util.rs +++ b/src/platform_impl/windows/util.rs @@ -20,7 +20,7 @@ use crate::{ use windows::{ core::{HRESULT, PCSTR, PCWSTR}, Win32::{ - Foundation::{BOOL, FARPROC, HWND, LPARAM, LRESULT, POINT, RECT, WPARAM}, + Foundation::{BOOL, COLORREF, FARPROC, HWND, LPARAM, LRESULT, POINT, RECT, WPARAM}, Globalization::lstrlenW, Graphics::Gdi::{ClientToScreen, InvalidateRgn, HMONITOR, HRGN}, System::LibraryLoader::*, @@ -393,6 +393,13 @@ pub fn PRIMARYLANGID(hkl: HKL) -> u32 { ((hkl.0 as usize) & 0x3FF) as u32 } +/// Implementation of the `RGB` macro. +#[allow(non_snake_case)] +#[inline] +pub fn RGB>(r: T, g: T, b: T) -> COLORREF { + COLORREF(r.into() | g.into() << 8 | b.into() << 16) +} + pub unsafe extern "system" fn call_default_window_proc( hwnd: HWND, msg: u32, diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 31ec275ea..d9143bd35 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -51,7 +51,7 @@ use crate::{ }, window::{ CursorIcon, Fullscreen, ProgressBarState, ProgressState, ResizeDirection, Theme, - UserAttentionType, WindowAttributes, WindowSizeConstraints, + UserAttentionType, WindowAttributes, WindowSizeConstraints, RGBA, }, }; @@ -982,6 +982,16 @@ impl Window { unsafe { set_skip_taskbar(self.hwnd(), skip) } } + #[inline] + pub fn set_background_color(&self, color: Option) { + self.window_state.lock().background_color = color; + + unsafe { + let _ = InvalidateRect(self.hwnd(), None, true); + let _ = UpdateWindow(self.hwnd()); + } + } + #[inline] pub fn set_progress_bar(&self, progress: ProgressBarState) { unsafe { @@ -1176,6 +1186,7 @@ unsafe fn init( scale_factor, current_theme, attributes.preferred_theme, + attributes.background_color, ); let window_state = Arc::new(Mutex::new(window_state)); WindowState::set_window_flags(window_state.lock(), real_window.0, |f| *f = window_flags); diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 12bafb884..00d558d1d 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -7,7 +7,7 @@ use crate::{ icon::Icon, keyboard::ModifiersState, platform_impl::platform::{event_loop, minimal_ime::MinimalIme, util}, - window::{CursorIcon, Fullscreen, Theme, WindowAttributes, WindowSizeConstraints}, + window::{CursorIcon, Fullscreen, Theme, WindowAttributes, WindowSizeConstraints, RGBA}, }; use parking_lot::MutexGuard; use std::io; @@ -46,6 +46,8 @@ pub struct WindowState { // Used by WM_NCACTIVATE, WM_SETFOCUS and WM_KILLFOCUS pub is_active: bool, pub is_focused: bool, + + pub background_color: Option, } unsafe impl Send for WindowState {} @@ -126,6 +128,7 @@ impl WindowState { scale_factor: f64, current_theme: Theme, preferred_theme: Option, + background_color: Option, ) -> WindowState { WindowState { mouse: MouseProperties { @@ -155,6 +158,8 @@ impl WindowState { window_flags: WindowFlags::empty(), is_active: false, is_focused: false, + + background_color, } } diff --git a/src/window.rs b/src/window.rs index abe11c25b..22f438db2 100644 --- a/src/window.rs +++ b/src/window.rs @@ -85,6 +85,11 @@ impl Drop for Window { } } +/// Type alias for a color in the RGBA format. +/// +/// Each value can be 0..255 inclusive. +pub type RGBA = (u8, u8, u8, u8); + /// Identifier of a window. Unique for each window. /// /// Can be obtained with `window.id()`. @@ -257,6 +262,14 @@ pub struct WindowAttributes { /// /// - **iOS / Android / Windows:** Unsupported. pub visible_on_all_workspaces: bool, + + /// Sets the window background color. + /// + /// ## Platform-specific: + /// + /// - **Windows:** alpha channel is ignored. Instead manually draw the window, for example using `softbuffer` crate, see + /// - **iOS / Android:** Unsupported. + pub background_color: Option, } impl Default for WindowAttributes { @@ -283,6 +296,7 @@ impl Default for WindowAttributes { focused: true, content_protection: false, visible_on_all_workspaces: false, + background_color: None, } } } @@ -546,6 +560,18 @@ impl WindowBuilder { self } + /// Sets the window background color. + /// + /// ## Platform-specific: + /// + /// - **Windows:** alpha channel is ignored. Instead manually draw the window, for example using `softbuffer` crate, see + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn with_background_color(mut self, color: RGBA) -> WindowBuilder { + self.window.background_color = Some(color); + self + } + /// Builds the window. /// /// Possible causes of error include denied permission, incompatible system, and lack of memory. @@ -1155,6 +1181,17 @@ impl Window { #[cfg(any(target_os = "macos", target_os = "linux"))] self.window.set_visible_on_all_workspaces(visible) } + + /// Sets the window background color. + /// + /// ## Platform-specific: + /// + /// - **Windows:** alpha channel is ignored. Instead manually draw the window, for example using `softbuffer` crate, see + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn set_background_color(&self, color: Option) { + self.window.set_background_color(color) + } } /// Cursor functions.