From a002940231d8eb9ab16c9632158a87f9193f1ab2 Mon Sep 17 00:00:00 2001 From: Jari Pennanen Date: Sun, 14 Feb 2021 21:50:38 +0200 Subject: [PATCH 1/5] Windows with transparent background --- druid-shell/src/platform/windows/paint.rs | 7 +- druid-shell/src/platform/windows/window.rs | 157 ++++++++++++++++++--- druid-shell/src/platform/x11/window.rs | 4 + druid-shell/src/window.rs | 5 + druid/examples/transparency.rs | 88 ++++++++++++ druid/examples/web/src/lib.rs | 1 + druid/src/app.rs | 28 ++++ druid/src/window.rs | 12 +- 8 files changed, 282 insertions(+), 20 deletions(-) create mode 100644 druid/examples/transparency.rs diff --git a/druid-shell/src/platform/windows/paint.rs b/druid-shell/src/platform/windows/paint.rs index 2253d13ac5..80ab9035ab 100644 --- a/druid-shell/src/platform/windows/paint.rs +++ b/druid-shell/src/platform/windows/paint.rs @@ -46,6 +46,7 @@ pub(crate) unsafe fn create_render_target_dxgi( d2d_factory: &D2DFactory, swap_chain: *mut IDXGISwapChain1, scale: Scale, + transparent: bool, ) -> Result { let mut buffer: *mut IDXGISurface = null_mut(); as_result((*swap_chain).GetBuffer( @@ -57,7 +58,11 @@ pub(crate) unsafe fn create_render_target_dxgi( _type: D2D1_RENDER_TARGET_TYPE_DEFAULT, pixelFormat: D2D1_PIXEL_FORMAT { format: DXGI_FORMAT_B8G8R8A8_UNORM, - alphaMode: D2D1_ALPHA_MODE_IGNORE, + alphaMode: if transparent { + D2D1_ALPHA_MODE_PREMULTIPLIED + } else { + D2D1_ALPHA_MODE_IGNORE + }, }, dpiX: (scale.x() * SCALE_TARGET_DPI) as f32, dpiY: (scale.y() * SCALE_TARGET_DPI) as f32, diff --git a/druid-shell/src/platform/windows/window.rs b/druid-shell/src/platform/windows/window.rs index 6174539345..21315dfdf2 100644 --- a/druid-shell/src/platform/windows/window.rs +++ b/druid-shell/src/platform/windows/window.rs @@ -35,6 +35,11 @@ use winapi::shared::dxgitype::*; use winapi::shared::minwindef::*; use winapi::shared::windef::*; use winapi::shared::winerror::*; +use winapi::um::d2d1_1::ID2D1DeviceContext; +use winapi::um::d2d1_1::D2D1_PRIMITIVE_BLEND_COPY; +use winapi::um::dcomp::{ + DCompositionCreateDevice, IDCompositionDevice, IDCompositionTarget, IDCompositionVisual, +}; use winapi::um::dwmapi::DwmExtendFrameIntoClientArea; use winapi::um::errhandlingapi::GetLastError; use winapi::um::shellscalingapi::MDT_EFFECTIVE_DPI; @@ -43,12 +48,14 @@ use winapi::um::uxtheme::*; use winapi::um::wingdi::*; use winapi::um::winnt::*; use winapi::um::winuser::*; +use winapi::Interface; +use wio::com::ComPtr; use piet_common::d2d::{D2DFactory, DeviceContext}; use piet_common::dwrite::DwriteFactory; use crate::kurbo::{Insets, Point, Rect, Size, Vec2}; -use crate::piet::{Piet, PietText, RenderContext}; +use crate::piet::{Color, Piet, PietText, RenderContext}; use super::accels::register_accel; use super::application::Application; @@ -86,6 +93,7 @@ pub(crate) struct WindowBuilder { resizable: bool, show_titlebar: bool, size: Option, + transparent: bool, min_size: Option, position: Option, state: window::WindowState, @@ -188,6 +196,7 @@ struct WindowState { timers: Arc>, deferred_queue: RefCell>, has_titlebar: Cell, + is_transparent: Cell, // For resizable borders, window can still be resized with code. is_resizable: Cell, handle_titlebar: Cell, @@ -225,6 +234,7 @@ struct WndState { // capture. When the first mouse button is down on our window we enter // capture, and we hold it until the last mouse button is up. captured_mouse_buttons: MouseButtons, + transparent: bool, // Is this window the topmost window under the mouse cursor has_mouse_focus: bool, //TODO: track surrogate orphan @@ -236,6 +246,14 @@ struct WndState { /// State for DXGI swapchains. struct DxgiState { swap_chain: *mut IDXGISwapChain1, + + // These ComPtrs must live as long as the window + #[allow(dead_code)] + composition_device: Option>, + #[allow(dead_code)] + composition_target: Option>, + #[allow(dead_code)] + composition_visual: Option>, } #[derive(Clone, PartialEq)] @@ -255,6 +273,9 @@ impl Drop for HCursor { /// Message indicating there are idle tasks to run. const DS_RUN_IDLE: UINT = WM_USER; +/// Transparent bg clearing color +const TRANSPARENT: Color = Color::rgba8(0, 0, 0, 0); + /// Message relaying a request to destroy the window. /// /// Calling `DestroyWindow` from inside the handler is problematic @@ -359,7 +380,7 @@ impl WndState { fn rebuild_render_target(&mut self, d2d: &D2DFactory, scale: Scale) -> Result<(), Error> { unsafe { let swap_chain = self.dxgi_state.as_ref().unwrap().swap_chain; - match paint::create_render_target_dxgi(d2d, swap_chain, scale) { + match paint::create_render_target_dxgi(d2d, swap_chain, scale, self.transparent) { Ok(rt) => { self.render_target = Some(rt.as_device_context().expect("TODO remove this expect")); @@ -373,9 +394,38 @@ impl WndState { // Renders but does not present. fn render(&mut self, d2d: &D2DFactory, dw: &DwriteFactory, invalid: &Region) { let rt = self.render_target.as_mut().unwrap(); + rt.begin_draw(); { + // Piet is missing alpha blending setting, so we have to call + // ID2D1DeviceContext::SetPrimitiveBlend() manually to clear just + // the required pixels + let dc_for_transparency: Option<&ComPtr> = if self.transparent { + Some(unsafe { + (rt as *mut _ as *mut ComPtr) + .as_ref() + .unwrap() + }) + } else { + None + }; + let mut piet_ctx = Piet::new(d2d, dw.clone(), rt); + + // Clear the background if transparency DC is found + if let Some(dc) = dc_for_transparency { + let current_blend = unsafe { dc.GetPrimitiveBlend() }; + unsafe { + dc.SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_COPY); + } + for r in invalid.rects().iter() { + piet_ctx.fill(r, &TRANSPARENT); + } + unsafe { + dc.SetPrimitiveBlend(current_blend); + } + } + // The documentation on DXGI_PRESENT_PARAMETERS says we "must not update any // pixel outside of the dirty rectangles." piet_ctx.clip(invalid.to_bez_path()); @@ -477,6 +527,10 @@ impl MyWndProc { self.with_window_state(|state| state.is_resizable.get()) } + fn is_transparent(&self) -> bool { + self.with_window_state(|state| state.is_transparent.get()) + } + fn handle_deferred_queue(&self) { let q = self.with_window_state(move |state| state.deferred_queue.replace(Vec::new())); for op in q { @@ -655,10 +709,11 @@ impl WndProc for MyWndProc { } if let Some(state) = self.state.borrow_mut().as_mut() { let dxgi_state = unsafe { - create_dxgi_state(self.present_strategy, hwnd).unwrap_or_else(|e| { - error!("Creating swapchain failed: {:?}", e); - None - }) + create_dxgi_state(self.present_strategy, hwnd, self.is_transparent()) + .unwrap_or_else(|e| { + error!("Creating swapchain failed: {:?}", e); + None + }) }; state.dxgi_state = dxgi_state; @@ -670,7 +725,7 @@ impl WndProc for MyWndProc { WM_ACTIVATE => { if LOWORD(wparam as u32) as u32 != 0 { unsafe { - if !self.has_titlebar() { + if !self.has_titlebar() && !self.is_transparent() { // This makes windows paint the dropshadow around the window // since we give it a "1 pixel frame" that we paint over anyway. // From my testing top seems to be the best option when it comes to avoiding resize artifacts. @@ -1172,6 +1227,7 @@ impl WindowBuilder { menu: None, resizable: true, show_titlebar: true, + transparent: false, present_strategy: Default::default(), size: None, min_size: None, @@ -1201,6 +1257,11 @@ impl WindowBuilder { self.show_titlebar = show_titlebar; } + pub fn set_transparent(&mut self, transparent: bool) { + self.present_strategy = PresentStrategy::Flip; + self.transparent = transparent; + } + pub fn set_title>(&mut self, title: S) { self.title = title.into(); } @@ -1271,6 +1332,7 @@ impl WindowBuilder { deferred_queue: RefCell::new(Vec::new()), has_titlebar: Cell::new(self.show_titlebar), is_resizable: Cell::new(self.resizable), + is_transparent: Cell::new(self.transparent), handle_titlebar: Cell::new(false), }; let win = Rc::new(window); @@ -1287,6 +1349,7 @@ impl WindowBuilder { keyboard_state: KeyboardState::new(), captured_mouse_buttons: MouseButtons::new(), has_mouse_focus: false, + transparent: self.transparent, last_click_time: Instant::now(), last_click_pos: (0, 0), click_count: 0, @@ -1398,6 +1461,7 @@ unsafe fn choose_adapter(factory: *mut IDXGIFactory2) -> *mut IDXGIAdapter { unsafe fn create_dxgi_state( present_strategy: PresentStrategy, hwnd: HWND, + transparent: bool, ) -> Result, Error> { let mut factory: *mut IDXGIFactory2 = null_mut(); as_result(CreateDXGIFactory1( @@ -1416,6 +1480,7 @@ unsafe fn create_dxgi_state( (DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, 2) } }; + let desc = DXGI_SWAP_CHAIN_DESC1 { Width: 1024, Height: 768, @@ -1429,21 +1494,79 @@ unsafe fn create_dxgi_state( BufferCount: bufs, Scaling: DXGI_SCALING_STRETCH, SwapEffect: swap_effect, - AlphaMode: DXGI_ALPHA_MODE_IGNORE, + AlphaMode: if transparent { + DXGI_ALPHA_MODE_PREMULTIPLIED + } else { + DXGI_ALPHA_MODE_IGNORE + }, Flags: 0, }; let mut swap_chain: *mut IDXGISwapChain1 = null_mut(); - let res = (*factory).CreateSwapChainForHwnd( - d3d11_device.raw_ptr() as *mut IUnknown, - hwnd, - &desc, - null_mut(), - null_mut(), - &mut swap_chain, + let swap_chain_res = if transparent { + (*factory).CreateSwapChainForComposition( + d3d11_device.raw_ptr() as *mut IUnknown, + &desc, + null_mut(), + &mut swap_chain, + ) + } else { + (*factory).CreateSwapChainForHwnd( + d3d11_device.raw_ptr() as *mut IUnknown, + hwnd, + &desc, + null_mut(), + null_mut(), + &mut swap_chain, + ) + }; + debug!( + "swap chain res = 0x{:x}, pointer = {:?}", + swap_chain_res, swap_chain ); - debug!("swap chain res = 0x{:x}, pointer = {:?}", res, swap_chain); - Ok(Some(DxgiState { swap_chain })) + let (composition_device, composition_target, composition_visual) = if transparent { + // Following resources are created according to this tutorial: + // https://docs.microsoft.com/en-us/archive/msdn-magazine/2014/june/windows-with-c-high-performance-window-layering-using-the-windows-composition-engine + + // Create IDCompositionDevice + let mut ptr: *mut c_void = null_mut(); + DCompositionCreateDevice( + d3d11_device.raw_ptr() as *mut IDXGIDevice, + &IDCompositionDevice::uuidof(), + &mut ptr, + ); + let composition_device = ComPtr::::from_raw(ptr as _); + + // Create IDCompositionTarget for the window + let mut ptr: *mut IDCompositionTarget = null_mut(); + composition_device.CreateTargetForHwnd(hwnd as _, 1, &mut ptr); + let composition_target = ComPtr::from_raw(ptr); + + // Create IDCompositionVisual and assign to swap chain + let mut ptr: *mut IDCompositionVisual = null_mut(); + composition_device.CreateVisual(&mut ptr); + let composition_visual = ComPtr::from_raw(ptr); + composition_visual.SetContent(swap_chain as *mut IUnknown); + + // Set the root as composition target and commit + composition_target.SetRoot(composition_visual.as_raw()); + composition_device.Commit(); + + ( + Some(composition_device), + Some(composition_target), + Some(composition_visual), + ) + } else { + (None, None, None) + }; + + Ok(Some(DxgiState { + swap_chain, + composition_device, + composition_target, + composition_visual, + })) } #[cfg(target_arch = "x86_64")] diff --git a/druid-shell/src/platform/x11/window.rs b/druid-shell/src/platform/x11/window.rs index 9c19d72d75..73685979dd 100644 --- a/druid-shell/src/platform/x11/window.rs +++ b/druid-shell/src/platform/x11/window.rs @@ -133,6 +133,10 @@ impl WindowBuilder { warn!("WindowBuilder::show_titlebar is currently unimplemented for X11 platforms."); } + pub fn set_transparent(&mut self, _transparent: bool) { + // Ignored + } + pub fn set_position(&mut self, _position: Point) { warn!("WindowBuilder::set_position is currently unimplemented for X11 platforms."); } diff --git a/druid-shell/src/window.rs b/druid-shell/src/window.rs index eb779d6e2a..78e004dc8d 100644 --- a/druid-shell/src/window.rs +++ b/druid-shell/src/window.rs @@ -388,6 +388,11 @@ impl WindowBuilder { self.0.show_titlebar(show_titlebar) } + /// Set whether the window background should be transparent + pub fn set_transparent(&mut self, transparent: bool) { + self.0.set_transparent(transparent) + } + /// Sets the initial window position in [pixels](crate::Scale), relative to the origin of the /// virtual screen. pub fn set_position(&mut self, position: Point) { diff --git a/druid/examples/transparency.rs b/druid/examples/transparency.rs new file mode 100644 index 0000000000..3013a2464a --- /dev/null +++ b/druid/examples/transparency.rs @@ -0,0 +1,88 @@ +// Copyright 2019 The Druid Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! An example of a transparent window background. +//! Useful for dropdowns, tooltips and other overlay windows. + +use druid::kurbo::Circle; +use druid::widget::prelude::*; +use druid::widget::{Button, Flex}; +use druid::{AppLauncher, Color, Rect, WindowDesc}; + +struct CustomWidget; + +impl Widget for CustomWidget { + fn event(&mut self, _ctx: &mut EventCtx, _event: &Event, _data: &mut String, _env: &Env) {} + + fn lifecycle( + &mut self, + _ctx: &mut LifeCycleCtx, + _event: &LifeCycle, + _data: &String, + _env: &Env, + ) { + } + + fn update(&mut self, _ctx: &mut UpdateCtx, _old_data: &String, _data: &String, _env: &Env) {} + + fn layout( + &mut self, + _layout_ctx: &mut LayoutCtx, + bc: &BoxConstraints, + _data: &String, + _env: &Env, + ) -> Size { + bc.max() + } + + // The paint method gets called last, after an event flow. + // It goes event -> update -> layout -> paint, and each method can influence the next. + // Basically, anything that changes the appearance of a widget causes a paint. + fn paint(&mut self, ctx: &mut PaintCtx, _data: &String, _env: &Env) { + let boundaries = ctx.size().to_rect(); + let center = (boundaries.width() / 2., boundaries.height() / 2.); + let circle = Circle::new(center, center.0.min(center.1)); + ctx.fill(circle, &Color::RED); + + let rect1 = Rect::new(0., 0., boundaries.width() / 2., boundaries.height() / 2.); + ctx.fill(rect1, &Color::rgba8(0x0, 0xff, 0, 125)); + + let rect2 = Rect::new( + boundaries.width() / 2., + boundaries.height() / 2., + boundaries.width(), + boundaries.height(), + ); + ctx.fill(rect2, &Color::rgba8(0x0, 0x0, 0xff, 125)); + } +} + +pub fn main() { + let btn = Button::new("Example button on transparent bg"); + let example = Flex::column() + .with_flex_child(CustomWidget {}, 10.0) + .with_flex_child(btn, 1.0); + let window = WindowDesc::new(example) + .show_titlebar(false) + .set_position((50., 50.)) + .window_size((823., 823.)) + .transparent(true) + .resizable(true) + .title("Transparent background"); + + AppLauncher::with_window(window) + .use_simple_logger() + .launch("Druid + Piet".to_string()) + .expect("launch failed"); +} diff --git a/druid/examples/web/src/lib.rs b/druid/examples/web/src/lib.rs index f096c19ef0..f83c467ec9 100644 --- a/druid/examples/web/src/lib.rs +++ b/druid/examples/web/src/lib.rs @@ -79,6 +79,7 @@ impl_example!(styled_text.unwrap()); impl_example!(switches); impl_example!(timer); impl_example!(tabs); +impl_example!(transparency); impl_example!(view_switcher); impl_example!(widget_gallery); impl_example!(text); diff --git a/druid/src/app.rs b/druid/src/app.rs index 3043d5ce85..46eea2655e 100644 --- a/druid/src/app.rs +++ b/druid/src/app.rs @@ -57,6 +57,7 @@ pub struct WindowConfig { pub(crate) min_size: Option, pub(crate) position: Option, pub(crate) resizable: Option, + pub(crate) transparent: Option, pub(crate) show_titlebar: Option, pub(crate) level: Option, pub(crate) state: Option, @@ -79,6 +80,7 @@ pub struct WindowDesc { pub struct PendingWindow { pub(crate) root: Box>, pub(crate) title: LabelText, + pub(crate) transparent: bool, pub(crate) menu: Option>, pub(crate) size_policy: WindowSizePolicy, // This is copied over from the WindowConfig // when the native window is constructed. @@ -95,6 +97,7 @@ impl PendingWindow { root: Box::new(root), title: LocalizedString::new("app-name").into(), menu: MenuDesc::platform_default(), + transparent: false, size_policy: WindowSizePolicy::User, } } @@ -110,6 +113,12 @@ impl PendingWindow { self } + /// Set wether the background should be transparent + pub fn transparent(mut self, transparent: bool) -> Self { + self.transparent = transparent; + self + } + /// Set the menu for this window. pub fn menu(mut self, menu: MenuDesc) -> Self { self.menu = Some(menu); @@ -265,6 +274,7 @@ impl Default for WindowConfig { position: None, resizable: None, show_titlebar: None, + transparent: None, level: None, state: None, } @@ -352,6 +362,12 @@ impl WindowConfig { self } + /// Set whether the window background should be transparent + pub fn transparent(mut self, transparent: bool) -> Self { + self.transparent = Some(transparent); + self + } + /// Apply this window configuration to the passed in WindowBuilder pub fn apply_to_builder(&self, builder: &mut WindowBuilder) { if let Some(resizable) = self.resizable { @@ -372,6 +388,10 @@ impl WindowConfig { builder.set_position(position); } + if let Some(transparent) = self.transparent { + builder.set_transparent(transparent); + } + if let Some(level) = self.level { builder.set_level(level) } @@ -503,6 +523,14 @@ impl WindowDesc { self } + /// Builder-style method to set whether this window's background should be + /// transparent. + pub fn transparent(mut self, transparent: bool) -> Self { + self.config = self.config.transparent(transparent); + self.pending = self.pending.transparent(transparent); + self + } + /// Sets the initial window position in virtual screen coordinates. /// [`position`] Position in pixels. /// diff --git a/druid/src/window.rs b/druid/src/window.rs index fc8bf134cf..995faf31b1 100644 --- a/druid/src/window.rs +++ b/druid/src/window.rs @@ -20,7 +20,7 @@ use std::mem; // Automatically defaults to std::time::Instant on non Wasm platforms use instant::Instant; -use crate::piet::{Piet, RenderContext}; +use crate::piet::{Color, Piet, RenderContext}; use crate::shell::{Counter, Cursor, Region, WindowHandle}; use crate::app::{PendingWindow, WindowSizePolicy}; @@ -35,6 +35,8 @@ use crate::{ TimerToken, UpdateCtx, Widget, WidgetId, WidgetPod, }; +const TRANSPARENT: Color = Color::rgba8(0, 0, 0, 0); + /// A unique identifier for a window. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct WindowId(u64); @@ -55,6 +57,7 @@ pub struct Window { pub(crate) focus: Option, pub(crate) handle: WindowHandle, pub(crate) timers: HashMap, + pub(crate) transparent: bool, ext_handle: ExtEventSink, // delegate? } @@ -73,6 +76,7 @@ impl Window { size: Size::ZERO, invalid: Region::EMPTY, title: pending.title, + transparent: pending.transparent, menu: pending.menu, context_menu: None, last_anim: None, @@ -364,7 +368,11 @@ impl Window { piet.fill( invalid.bounding_box(), - &env.get(crate::theme::WINDOW_BACKGROUND_COLOR), + &(if self.transparent { + TRANSPARENT + } else { + env.get(crate::theme::WINDOW_BACKGROUND_COLOR) + }), ); self.paint(piet, invalid, queue, data, env); } From 1d31b2564b6cc7feeaf71fae060c96ff0ee22bc2 Mon Sep 17 00:00:00 2001 From: Robert Wittams Date: Sun, 14 Feb 2021 22:01:05 +0200 Subject: [PATCH 2/5] Mac support for transparency Web stub implementation to allow compilation Signed-off-by: Jari Pennanen --- druid-shell/src/platform/mac/window.rs | 15 +++++++++++++-- druid-shell/src/platform/web/window.rs | 4 ++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/druid-shell/src/platform/mac/window.rs b/druid-shell/src/platform/mac/window.rs index 6e64628e54..45c254e216 100644 --- a/druid-shell/src/platform/mac/window.rs +++ b/druid-shell/src/platform/mac/window.rs @@ -24,8 +24,8 @@ use std::time::Instant; use block::ConcreteBlock; use cocoa::appkit::{ - CGFloat, NSApp, NSApplication, NSAutoresizingMaskOptions, NSBackingStoreBuffered, NSEvent, - NSView, NSViewHeightSizable, NSViewWidthSizable, NSWindow, NSWindowStyleMask, + CGFloat, NSApp, NSApplication, NSAutoresizingMaskOptions, NSBackingStoreBuffered, NSColor, + NSEvent, NSView, NSViewHeightSizable, NSViewWidthSizable, NSWindow, NSWindowStyleMask, }; use cocoa::base::{id, nil, BOOL, NO, YES}; use cocoa::foundation::{ @@ -119,6 +119,7 @@ pub(crate) struct WindowBuilder { window_state: Option, resizable: bool, show_titlebar: bool, + transparent: bool, } #[derive(Clone)] @@ -170,6 +171,7 @@ impl WindowBuilder { window_state: None, resizable: true, show_titlebar: true, + transparent: false, } } @@ -193,6 +195,10 @@ impl WindowBuilder { self.show_titlebar = show_titlebar; } + pub fn set_transparent(&mut self, transparent: bool) { + self.transparent = transparent; + } + pub fn set_level(&mut self, level: WindowLevel) { self.level = Some(level); } @@ -246,6 +252,11 @@ impl WindowBuilder { window.setContentMinSize_(size); } + if self.transparent { + window.setOpaque_(NO); + window.setBackgroundColor_(NSColor::clearColor(nil)); + } + window.setTitle_(make_nsstring(&self.title)); let (view, idle_queue) = make_view(self.handler.expect("view")); diff --git a/druid-shell/src/platform/web/window.rs b/druid-shell/src/platform/web/window.rs index 09ec7c316f..80aea1c222 100644 --- a/druid-shell/src/platform/web/window.rs +++ b/druid-shell/src/platform/web/window.rs @@ -357,6 +357,10 @@ impl WindowBuilder { // Ignored } + pub fn set_transparent(&mut self, _transparent: bool) { + // Ignored + } + pub fn set_position(&mut self, _position: Point) { // Ignored } From 85757fa61aa47023a3a22d7cf57793daf0230b7e Mon Sep 17 00:00:00 2001 From: jaap aarts Date: Sun, 14 Feb 2021 22:04:06 +0200 Subject: [PATCH 3/5] add transparance on linux!!! Signed-off-by: Jari Pennanen --- druid-shell/src/platform/gtk/window.rs | 36 +++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/druid-shell/src/platform/gtk/window.rs b/druid-shell/src/platform/gtk/window.rs index 95517ac63f..b1a5a44479 100644 --- a/druid-shell/src/platform/gtk/window.rs +++ b/druid-shell/src/platform/gtk/window.rs @@ -116,6 +116,7 @@ pub(crate) struct WindowBuilder { min_size: Option, resizable: bool, show_titlebar: bool, + transparent: bool, } #[derive(Clone)] @@ -137,6 +138,7 @@ pub(crate) struct WindowState { window: ApplicationWindow, scale: Cell, area: Cell, + is_transparent: Cell, /// Used to determine whether to honor close requests from the system: we inhibit them unless /// this is true, and this gets set to true when our client requests a close. closing: Cell, @@ -180,6 +182,7 @@ impl WindowBuilder { min_size: None, resizable: true, show_titlebar: true, + transparent: false, } } @@ -203,6 +206,10 @@ impl WindowBuilder { self.show_titlebar = show_titlebar; } + pub fn set_transparent(&mut self, transparent: bool) { + self.transparent = transparent; + } + pub fn set_position(&mut self, position: Point) { self.position = Some(position); } @@ -232,7 +239,16 @@ impl WindowBuilder { window.set_title(&self.title); window.set_resizable(self.resizable); + window.set_app_paintable(true); window.set_decorated(self.show_titlebar); + let mut can_transparent = false; + if self.transparent { + if let Some(screen) = window.get_screen() { + let visual = screen.get_rgba_visual(); + can_transparent = visual.is_some(); + window.set_visual(visual.as_ref()); + } + } // Get the scale factor based on the GTK reported DPI let scale_factor = @@ -254,6 +270,7 @@ impl WindowBuilder { window, scale: Cell::new(scale), area: Cell::new(area), + is_transparent: Cell::new(self.transparent & can_transparent), closing: Cell::new(false), drawing_area, surface: RefCell::new(None), @@ -409,6 +426,19 @@ impl WindowBuilder { Inhibit(false) })); + win_state.drawing_area.connect_screen_changed( + clone!(handle => move |widget, _prev_screen| { + if let Some(state) = handle.state.upgrade() { + + if let Some(screen) = widget.get_screen(){ + let visual = screen.get_rgba_visual(); + state.is_transparent.set(visual.is_some()); + widget.set_visual(visual.as_ref()); + } + } + }), + ); + win_state.drawing_area.connect_button_press_event(clone!(handle => move |_widget, event| { if let Some(state) = handle.state.upgrade() { state.with_handler(|handler| { @@ -712,7 +742,11 @@ impl WindowState { *surface = None; if let Some(w) = self.drawing_area.get_window() { - *surface = w.create_similar_surface(cairo::Content::Color, width, height); + if self.is_transparent.get() { + *surface = w.create_similar_surface(cairo::Content::ColorAlpha, width, height); + } else { + *surface = w.create_similar_surface(cairo::Content::Color, width, height); + } if surface.is_none() { return Err(anyhow!("create_similar_surface failed")); } From eb80ed2af1a794508c175d24675c0775782b33d2 Mon Sep 17 00:00:00 2001 From: Jari Pennanen Date: Tue, 16 Feb 2021 22:46:30 +0200 Subject: [PATCH 4/5] Win 7 transparency fix: optionalize DCompositionCreateDevice --- druid-shell/src/platform/windows/util.rs | 18 ++++++++++++++++++ druid-shell/src/platform/windows/window.rs | 20 +++++++++++++++----- druid/examples/transparency.rs | 2 +- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/druid-shell/src/platform/windows/util.rs b/druid-shell/src/platform/windows/util.rs index a5d72b7a7a..34ac3853d9 100644 --- a/druid-shell/src/platform/windows/util.rs +++ b/druid-shell/src/platform/windows/util.rs @@ -24,6 +24,9 @@ use std::ptr; use std::slice; use lazy_static::lazy_static; +use winapi::ctypes::c_void; +use winapi::shared::dxgi::IDXGIDevice; +use winapi::shared::guiddef::REFIID; use winapi::shared::minwindef::{BOOL, HMODULE, UINT}; use winapi::shared::ntdef::{HRESULT, LPWSTR}; use winapi::shared::windef::{HMONITOR, HWND, RECT}; @@ -147,6 +150,11 @@ type GetSystemMetricsForDpi = // from shcore.dll type GetDpiForMonitor = unsafe extern "system" fn(HMONITOR, MONITOR_DPI_TYPE, *mut UINT, *mut UINT); type SetProcessDpiAwareness = unsafe extern "system" fn(PROCESS_DPI_AWARENESS) -> HRESULT; +type DCompositionCreateDevice = unsafe extern "system" fn( + dxgiDevice: *const IDXGIDevice, + iid: REFIID, + dcompositionDevice: *mut *mut c_void, +) -> HRESULT; #[allow(non_snake_case)] // For member fields pub struct OptionalFunctions { @@ -156,6 +164,7 @@ pub struct OptionalFunctions { pub GetDpiForMonitor: Option, pub SetProcessDpiAwareness: Option, pub GetSystemMetricsForDpi: Option, + pub DCompositionCreateDevice: Option, } #[allow(non_snake_case)] // For local variables @@ -201,6 +210,7 @@ fn load_optional_functions() -> OptionalFunctions { let shcore = load_library("shcore.dll"); let user32 = load_library("user32.dll"); + let dcomp = load_library("dcomp.dll"); let mut GetDpiForSystem = None; let mut GetDpiForMonitor = None; @@ -208,6 +218,7 @@ fn load_optional_functions() -> OptionalFunctions { let mut SetProcessDpiAwarenessContext = None; let mut SetProcessDpiAwareness = None; let mut GetSystemMetricsForDpi = None; + let mut DCompositionCreateDevice = None; if shcore.is_null() { tracing::info!("No shcore.dll"); @@ -225,6 +236,12 @@ fn load_optional_functions() -> OptionalFunctions { load_function!(user32, GetSystemMetricsForDpi, "10"); } + if dcomp.is_null() { + tracing::info!("No dcomp.dll"); + } else { + load_function!(dcomp, DCompositionCreateDevice, "8.1"); + } + OptionalFunctions { GetDpiForSystem, GetDpiForWindow, @@ -232,6 +249,7 @@ fn load_optional_functions() -> OptionalFunctions { GetDpiForMonitor, SetProcessDpiAwareness, GetSystemMetricsForDpi, + DCompositionCreateDevice, } } diff --git a/druid-shell/src/platform/windows/window.rs b/druid-shell/src/platform/windows/window.rs index 21315dfdf2..ca7b462199 100644 --- a/druid-shell/src/platform/windows/window.rs +++ b/druid-shell/src/platform/windows/window.rs @@ -37,9 +37,7 @@ use winapi::shared::windef::*; use winapi::shared::winerror::*; use winapi::um::d2d1_1::ID2D1DeviceContext; use winapi::um::d2d1_1::D2D1_PRIMITIVE_BLEND_COPY; -use winapi::um::dcomp::{ - DCompositionCreateDevice, IDCompositionDevice, IDCompositionTarget, IDCompositionVisual, -}; +use winapi::um::dcomp::{IDCompositionDevice, IDCompositionTarget, IDCompositionVisual}; use winapi::um::dwmapi::DwmExtendFrameIntoClientArea; use winapi::um::errhandlingapi::GetLastError; use winapi::um::shellscalingapi::MDT_EFFECTIVE_DPI; @@ -1258,8 +1256,16 @@ impl WindowBuilder { } pub fn set_transparent(&mut self, transparent: bool) { - self.present_strategy = PresentStrategy::Flip; - self.transparent = transparent; + // Transparency and Flip is only supported on Windows 8 and newer and + // require DComposition + if transparent { + if OPTIONAL_FUNCTIONS.DCompositionCreateDevice.is_some() { + self.present_strategy = PresentStrategy::Flip; + self.transparent = true; + } else { + tracing::warn!("Transparency requires Windows 8 or newer"); + } + } } pub fn set_title>(&mut self, title: S) { @@ -1525,8 +1531,12 @@ unsafe fn create_dxgi_state( ); let (composition_device, composition_target, composition_visual) = if transparent { + // This behavior is only supported on windows 8 and newer where + // composition is available + // Following resources are created according to this tutorial: // https://docs.microsoft.com/en-us/archive/msdn-magazine/2014/june/windows-with-c-high-performance-window-layering-using-the-windows-composition-engine + let DCompositionCreateDevice = OPTIONAL_FUNCTIONS.DCompositionCreateDevice.unwrap(); // Create IDCompositionDevice let mut ptr: *mut c_void = null_mut(); diff --git a/druid/examples/transparency.rs b/druid/examples/transparency.rs index 3013a2464a..acc45f0577 100644 --- a/druid/examples/transparency.rs +++ b/druid/examples/transparency.rs @@ -82,7 +82,7 @@ pub fn main() { .title("Transparent background"); AppLauncher::with_window(window) - .use_simple_logger() + .use_env_tracing() .launch("Druid + Piet".to_string()) .expect("launch failed"); } From 05aef1931fc4abaccfbba386a549f50e0fd0c14b Mon Sep 17 00:00:00 2001 From: Jari Pennanen Date: Thu, 18 Feb 2021 00:07:05 +0200 Subject: [PATCH 5/5] Transparency touch-ups --- druid-shell/src/platform/windows/window.rs | 11 +++-- druid/examples/transparency.rs | 47 +++++----------------- druid/src/window.rs | 1 + 3 files changed, 16 insertions(+), 43 deletions(-) diff --git a/druid-shell/src/platform/windows/window.rs b/druid-shell/src/platform/windows/window.rs index ca7b462199..0ba2a1b0f3 100644 --- a/druid-shell/src/platform/windows/window.rs +++ b/druid-shell/src/platform/windows/window.rs @@ -272,6 +272,8 @@ impl Drop for HCursor { const DS_RUN_IDLE: UINT = WM_USER; /// Transparent bg clearing color +/// +/// FIXME: Replace usage with Color::TRANSPARENT on next Piet release const TRANSPARENT: Color = Color::rgba8(0, 0, 0, 0); /// Message relaying a request to destroy the window. @@ -398,15 +400,12 @@ impl WndState { // Piet is missing alpha blending setting, so we have to call // ID2D1DeviceContext::SetPrimitiveBlend() manually to clear just // the required pixels - let dc_for_transparency: Option<&ComPtr> = if self.transparent { - Some(unsafe { + let dc_for_transparency: Option<&ComPtr> = + self.transparent.then(|| unsafe { (rt as *mut _ as *mut ComPtr) .as_ref() .unwrap() - }) - } else { - None - }; + }); let mut piet_ctx = Piet::new(d2d, dw.clone(), rt); diff --git a/druid/examples/transparency.rs b/druid/examples/transparency.rs index acc45f0577..7e265b431a 100644 --- a/druid/examples/transparency.rs +++ b/druid/examples/transparency.rs @@ -17,39 +17,12 @@ use druid::kurbo::Circle; use druid::widget::prelude::*; -use druid::widget::{Button, Flex}; +use druid::widget::{Button, Flex, Painter, WidgetExt}; use druid::{AppLauncher, Color, Rect, WindowDesc}; -struct CustomWidget; - -impl Widget for CustomWidget { - fn event(&mut self, _ctx: &mut EventCtx, _event: &Event, _data: &mut String, _env: &Env) {} - - fn lifecycle( - &mut self, - _ctx: &mut LifeCycleCtx, - _event: &LifeCycle, - _data: &String, - _env: &Env, - ) { - } - - fn update(&mut self, _ctx: &mut UpdateCtx, _old_data: &String, _data: &String, _env: &Env) {} - - fn layout( - &mut self, - _layout_ctx: &mut LayoutCtx, - bc: &BoxConstraints, - _data: &String, - _env: &Env, - ) -> Size { - bc.max() - } - - // The paint method gets called last, after an event flow. - // It goes event -> update -> layout -> paint, and each method can influence the next. - // Basically, anything that changes the appearance of a widget causes a paint. - fn paint(&mut self, ctx: &mut PaintCtx, _data: &String, _env: &Env) { +pub fn main() { + // Draw red circle, and two semi-transparent rectangles + let circle_and_rects = Painter::new(|ctx, _data, _env| { let boundaries = ctx.size().to_rect(); let center = (boundaries.width() / 2., boundaries.height() / 2.); let circle = Circle::new(center, center.0.min(center.1)); @@ -65,15 +38,15 @@ impl Widget for CustomWidget { boundaries.height(), ); ctx.fill(rect2, &Color::rgba8(0x0, 0x0, 0xff, 125)); - } -} + }); -pub fn main() { let btn = Button::new("Example button on transparent bg"); - let example = Flex::column() - .with_flex_child(CustomWidget {}, 10.0) + + let root_widget = Flex::column() + .with_flex_child(circle_and_rects.expand(), 10.0) .with_flex_child(btn, 1.0); - let window = WindowDesc::new(example) + + let window = WindowDesc::new(root_widget) .show_titlebar(false) .set_position((50., 50.)) .window_size((823., 823.)) diff --git a/druid/src/window.rs b/druid/src/window.rs index 995faf31b1..9328a16ec2 100644 --- a/druid/src/window.rs +++ b/druid/src/window.rs @@ -35,6 +35,7 @@ use crate::{ TimerToken, UpdateCtx, Widget, WidgetId, WidgetPod, }; +/// FIXME: Replace usage with Color::TRANSPARENT on next Piet release const TRANSPARENT: Color = Color::rgba8(0, 0, 0, 0); /// A unique identifier for a window.