feat(borders): initial impl of direct2d border drawing

Not sure what I'm doing wrong but this is super slow to update on window dragging and an absolute
resource hog.
This commit is contained in:
LGUG2Z
2024-11-03 10:10:43 -08:00
parent e707a14b8a
commit 238271a71e
2 changed files with 141 additions and 71 deletions
+4
View File
@@ -44,11 +44,15 @@ which = "7"
version = "0.58" version = "0.58"
features = [ features = [
"implement", "implement",
"Foundation_Numerics",
"Win32_System_Com", "Win32_System_Com",
"Win32_UI_Shell_Common", # for IObjectArray "Win32_UI_Shell_Common", # for IObjectArray
"Win32_Foundation", "Win32_Foundation",
"Win32_Graphics_Dwm", "Win32_Graphics_Dwm",
"Win32_Graphics_Gdi", "Win32_Graphics_Gdi",
"Win32_Graphics_Direct2D",
"Win32_Graphics_Direct2D_Common",
"Win32_Graphics_Dxgi_Common",
"Win32_System_LibraryLoader", "Win32_System_LibraryLoader",
"Win32_System_RemoteDesktop", "Win32_System_RemoteDesktop",
"Win32_System_Threading", "Win32_System_Threading",
+132 -66
View File
@@ -5,34 +5,38 @@ use crate::border_manager::BORDER_WIDTH;
use crate::border_manager::FOCUS_STATE; use crate::border_manager::FOCUS_STATE;
use crate::border_manager::STYLE; use crate::border_manager::STYLE;
use crate::border_manager::Z_ORDER; use crate::border_manager::Z_ORDER;
use crate::core::BorderStyle;
use crate::core::Rect;
use crate::windows_api; use crate::windows_api;
use crate::WindowsApi; use crate::WindowsApi;
use crate::WINDOWS_11; use crate::WINDOWS_11;
use crate::core::BorderStyle;
use crate::core::Rect;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::mpsc; use std::sync::mpsc;
use std::sync::LazyLock;
use std::time::Duration; use std::time::Duration;
use windows::core::PCWSTR; use windows::Foundation::Numerics::Matrix3x2;
use windows::Win32::Foundation::BOOL; use windows::Win32::Foundation::BOOL;
use windows::Win32::Foundation::COLORREF;
use windows::Win32::Foundation::HWND; use windows::Win32::Foundation::HWND;
use windows::Win32::Foundation::LPARAM; use windows::Win32::Foundation::LPARAM;
use windows::Win32::Foundation::LRESULT; use windows::Win32::Foundation::LRESULT;
use windows::Win32::Foundation::WPARAM; use windows::Win32::Foundation::WPARAM;
use windows::Win32::Graphics::Gdi::BeginPaint; use windows::Win32::Graphics::Direct2D::Common::D2D1_ALPHA_MODE_PREMULTIPLIED;
use windows::Win32::Graphics::Gdi::CreatePen; use windows::Win32::Graphics::Direct2D::Common::D2D1_COLOR_F;
use windows::Win32::Graphics::Gdi::DeleteObject; use windows::Win32::Graphics::Direct2D::Common::D2D1_PIXEL_FORMAT;
use windows::Win32::Graphics::Gdi::EndPaint; use windows::Win32::Graphics::Direct2D::Common::D2D_RECT_F;
use windows::Win32::Graphics::Direct2D::Common::D2D_SIZE_U;
use windows::Win32::Graphics::Direct2D::D2D1CreateFactory;
use windows::Win32::Graphics::Direct2D::ID2D1Factory;
use windows::Win32::Graphics::Direct2D::D2D1_ANTIALIAS_MODE_PER_PRIMITIVE;
use windows::Win32::Graphics::Direct2D::D2D1_BRUSH_PROPERTIES;
use windows::Win32::Graphics::Direct2D::D2D1_FACTORY_TYPE_MULTI_THREADED;
use windows::Win32::Graphics::Direct2D::D2D1_HWND_RENDER_TARGET_PROPERTIES;
use windows::Win32::Graphics::Direct2D::D2D1_PRESENT_OPTIONS_IMMEDIATELY;
use windows::Win32::Graphics::Direct2D::D2D1_RENDER_TARGET_PROPERTIES;
use windows::Win32::Graphics::Direct2D::D2D1_RENDER_TARGET_TYPE_DEFAULT;
use windows::Win32::Graphics::Direct2D::D2D1_ROUNDED_RECT;
use windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT_UNKNOWN;
use windows::Win32::Graphics::Gdi::InvalidateRect; use windows::Win32::Graphics::Gdi::InvalidateRect;
use windows::Win32::Graphics::Gdi::Rectangle;
use windows::Win32::Graphics::Gdi::RoundRect;
use windows::Win32::Graphics::Gdi::SelectObject;
use windows::Win32::Graphics::Gdi::PAINTSTRUCT;
use windows::Win32::Graphics::Gdi::PS_INSIDEFRAME;
use windows::Win32::Graphics::Gdi::PS_SOLID;
use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW; use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW;
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW; use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
use windows::Win32::UI::WindowsAndMessaging::GetMessageW; use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
@@ -44,6 +48,15 @@ use windows::Win32::UI::WindowsAndMessaging::MSG;
use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY; use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY;
use windows::Win32::UI::WindowsAndMessaging::WM_PAINT; use windows::Win32::UI::WindowsAndMessaging::WM_PAINT;
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW; use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
use windows_core::PCWSTR;
#[allow(clippy::expect_used)]
pub static RENDER_FACTORY: LazyLock<ID2D1Factory> = unsafe {
LazyLock::new(|| {
D2D1CreateFactory::<ID2D1Factory>(D2D1_FACTORY_TYPE_MULTI_THREADED, None)
.expect("creating RENDER_FACTORY failed")
})
};
pub extern "system" fn border_hwnds(hwnd: HWND, lparam: LPARAM) -> BOOL { pub extern "system" fn border_hwnds(hwnd: HWND, lparam: LPARAM) -> BOOL {
let hwnds = unsafe { &mut *(lparam.0 as *mut Vec<isize>) }; let hwnds = unsafe { &mut *(lparam.0 as *mut Vec<isize>) };
@@ -159,71 +172,124 @@ impl Border {
unsafe { unsafe {
match message { match message {
WM_PAINT => { WM_PAINT => {
let mut ps = PAINTSTRUCT::default(); if let Ok(rect) = WindowsApi::window_rect(window.0 as isize) {
let hdc = BeginPaint(window, &mut ps); let hwnd_render_target_properties = D2D1_HWND_RENDER_TARGET_PROPERTIES {
hwnd: window,
pixelSize: Default::default(),
presentOptions: D2D1_PRESENT_OPTIONS_IMMEDIATELY,
};
// With the rect that we set in Self::update let render_target_properties = D2D1_RENDER_TARGET_PROPERTIES {
match WindowsApi::window_rect(window.0 as isize) { r#type: D2D1_RENDER_TARGET_TYPE_DEFAULT,
Ok(rect) => { pixelFormat: D2D1_PIXEL_FORMAT {
// Grab the focus kind for this border format: DXGI_FORMAT_UNKNOWN,
let window_kind = { alphaMode: D2D1_ALPHA_MODE_PREMULTIPLIED,
FOCUS_STATE },
dpiX: 96.0,
dpiY: 96.0,
..Default::default()
};
if let Ok(render_target) = RENDER_FACTORY.CreateHwndRenderTarget(
&render_target_properties,
&hwnd_render_target_properties,
) {
render_target.SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
let brush_properties = D2D1_BRUSH_PROPERTIES {
opacity: 1.0,
transform: Matrix3x2::identity(),
};
let pixel_size = D2D_SIZE_U {
width: rect.right as u32,
height: rect.bottom as u32,
};
let border_width = BORDER_WIDTH.load(Ordering::SeqCst);
let border_offset = BORDER_OFFSET.load(Ordering::SeqCst);
let rect = D2D_RECT_F {
left: (border_width / 2 - border_offset) as f32,
top: (border_width / 2 - border_offset) as f32,
right: (rect.right - border_width / 2 + border_offset) as f32,
bottom: (rect.bottom - border_width / 2 + border_offset) as f32,
};
let _ = render_target.Resize(&pixel_size);
// Get window kind and color
let window_kind = FOCUS_STATE
.lock() .lock()
.get(&(window.0 as isize)) .get(&(window.0 as isize))
.copied() .copied()
.unwrap_or(WindowKind::Unfocused) .unwrap_or(WindowKind::Unfocused);
let color = window_kind_colour(window_kind);
let color = D2D1_COLOR_F {
r: ((color & 0xFF) as f32) / 255.0,
g: (((color >> 8) & 0xFF) as f32) / 255.0,
b: (((color >> 16) & 0xFF) as f32) / 255.0,
a: 1.0,
}; };
// Set up the brush to draw the border if let Ok(brush) =
let hpen = CreatePen( render_target.CreateSolidColorBrush(&color, Some(&brush_properties))
PS_SOLID | PS_INSIDEFRAME, {
BORDER_WIDTH.load(Ordering::SeqCst), // Calculate border radius based on style
COLORREF(window_kind_colour(window_kind)), let style = STYLE.load();
); let radius = match style {
let hbrush = WindowsApi::create_solid_brush(0);
// Draw the border
SelectObject(hdc, hpen);
SelectObject(hdc, hbrush);
// TODO(raggi): this is approximately the correct curvature for
// the top left of a Windows 11 window (DWMWCP_DEFAULT), but
// often the bottom right has a different shape. Furthermore if
// the window was made with DWMWCP_ROUNDSMALL then this is the
// wrong size. In the future we should read the DWM properties
// of windows and attempt to match appropriately.
match STYLE.load() {
BorderStyle::System => { BorderStyle::System => {
if *WINDOWS_11 { if *WINDOWS_11 {
// TODO: error handling 10.0
let _ =
RoundRect(hdc, 0, 0, rect.right, rect.bottom, 20, 20);
} else { } else {
// TODO: error handling 0.0
let _ = Rectangle(hdc, 0, 0, rect.right, rect.bottom);
} }
} }
BorderStyle::Rounded => { BorderStyle::Rounded => 10.0,
// TODO: error handling BorderStyle::Square => 0.0,
let _ = RoundRect(hdc, 0, 0, rect.right, rect.bottom, 20, 20); };
render_target.BeginDraw();
render_target.Clear(None);
match radius {
0.0 => {
let rect = D2D_RECT_F {
left: rect.left,
top: rect.top,
right: rect.right,
bottom: rect.bottom,
};
render_target.DrawRectangle(
&rect,
&brush,
border_width as f32,
None,
);
} }
BorderStyle::Square => { 10.0 => {
// TODO: error handling let rounded_rect = D2D1_ROUNDED_RECT {
let _ = Rectangle(hdc, 0, 0, rect.right, rect.bottom); rect,
} radiusX: radius,
} radiusY: radius,
// TODO: error handling };
let _ = DeleteObject(hpen);
// TODO: error handling render_target.DrawRoundedRectangle(
let _ = DeleteObject(hbrush); &rounded_rect,
} &brush,
Err(error) => { border_width as f32,
tracing::error!("could not get border rect: {}", error.to_string()) None,
);
} }
_ => unreachable!(),
} }
// TODO: error handling let _ = render_target.EndDraw(None, None);
let _ = EndPaint(window, &ps); }
}
}
LRESULT(0) LRESULT(0)
} }
WM_DESTROY => { WM_DESTROY => {