From 387f15bde9673adebe1fa30a2111c54640f9630d Mon Sep 17 00:00:00 2001 From: sigoden Date: Tue, 22 Oct 2024 23:00:56 +0800 Subject: [PATCH] replace old painter --- src/app.rs | 2 +- src/lib.rs | 2 +- src/painter.rs | 598 ++++++++++++++++++++++++++++++----------------- src/painter2.rs | 433 ---------------------------------- src/utils/mod.rs | 4 +- 5 files changed, 384 insertions(+), 655 deletions(-) delete mode 100644 src/painter2.rs diff --git a/src/app.rs b/src/app.rs index c6ebc18..0a7f677 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,7 +1,7 @@ use crate::config::{edit_config_file, Config}; use crate::foreground::ForegroundWatcher; use crate::keyboard::KeyboardListener; -use crate::painter2::{find_clicked_app_index, GdiAAPainter}; +use crate::painter::{find_clicked_app_index, GdiAAPainter}; use crate::startup::Startup; use crate::trayicon::TrayIcon; use crate::utils::{ diff --git a/src/lib.rs b/src/lib.rs index 63f77cd..4ee9c5d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ mod app; mod config; mod foreground; mod keyboard; -mod painter2; +mod painter; mod startup; mod trayicon; diff --git a/src/painter.rs b/src/painter.rs index fb59bd0..bfef34b 100644 --- a/src/painter.rs +++ b/src/painter.rs @@ -1,271 +1,433 @@ use crate::app::SwitchAppsState; -use crate::utils::RegKey; -use anyhow::Result; -use windows::core::w; -use windows::Win32::Foundation::{COLORREF, RECT}; +use crate::utils::{check_error, get_moinitor_rect, is_light_theme, is_win11}; + +use anyhow::{Context, Result}; +use windows::Win32::Foundation::{COLORREF, POINT, RECT, SIZE}; use windows::Win32::Graphics::Gdi::{ - BeginPaint, BitBlt, CreateCompatibleBitmap, CreateCompatibleDC, CreateRoundRectRgn, - CreateSolidBrush, DeleteDC, DeleteObject, EndPaint, FillRgn, SelectObject, SetStretchBltMode, - SetWindowRgn, StretchBlt, HALFTONE, HBITMAP, HBRUSH, HDC, HRGN, PAINTSTRUCT, SRCCOPY, + CreateCompatibleBitmap, CreateCompatibleDC, CreateRoundRectRgn, CreateSolidBrush, DeleteDC, + DeleteObject, FillRect, FillRgn, ReleaseDC, SelectObject, SetStretchBltMode, StretchBlt, + AC_SRC_ALPHA, AC_SRC_OVER, BLENDFUNCTION, HALFTONE, HBITMAP, HBRUSH, HDC, HPALETTE, SRCCOPY, +}; +use windows::Win32::Graphics::GdiPlus::{ + FillModeAlternate, GdipAddPathArc, GdipClosePathFigure, GdipCreateBitmapFromHBITMAP, + GdipCreateFromHDC, GdipCreatePath, GdipCreatePen1, GdipDeleteBrush, GdipDeleteGraphics, + GdipDeletePath, GdipDeletePen, GdipDisposeImage, GdipDrawImageRect, GdipFillPath, + GdipFillRectangle, GdipGetPenBrushFill, GdipSetInterpolationMode, GdipSetSmoothingMode, + GdiplusShutdown, GdiplusStartup, GdiplusStartupInput, GpBitmap, GpBrush, GpGraphics, GpImage, + GpPath, GpPen, InterpolationModeHighQualityBicubic, SmoothingModeAntiAlias, Unit, +}; +use windows::Win32::UI::Input::KeyboardAndMouse::SetFocus; +use windows::Win32::UI::WindowsAndMessaging::{ + DestroyIcon, DrawIconEx, GetCursorPos, ShowWindow, UpdateLayeredWindow, DI_NORMAL, SW_HIDE, + SW_SHOW, ULW_ALPHA, }; -use windows::Win32::UI::WindowsAndMessaging::{DrawIconEx, DI_NORMAL}; use windows::Win32::{Foundation::HWND, Graphics::Gdi::GetDC}; -// window background color in dark theme -pub const BG_DARK_COLOR: COLORREF = COLORREF(0x3b3b3b); -// selected icon box color in dark theme -pub const FG_DARK_COLOR: COLORREF = COLORREF(0x4c4c4c); -// window background color in light theme -pub const BG_LIGHT_COLOR: COLORREF = COLORREF(0xf2f2f2); -// selected icon box color in light theme -pub const FG_LIGHT_COLOR: COLORREF = COLORREF(0xe0e0e0); -// maximum icon size +pub const BG_DARK_COLOR: u32 = 0x4c4c4c; +pub const FG_DARK_COLOR: u32 = 0x3b3b3b; +pub const BG_LIGHT_COLOR: u32 = 0xe0e0e0; +pub const FG_LIGHT_COLOR: u32 = 0xf2f2f2; +pub const ALPHA_MASK: u32 = 0xff000000; pub const ICON_SIZE: i32 = 64; -// window padding pub const WINDOW_BORDER_SIZE: i32 = 10; -// icon border pub const ICON_BORDER_SIZE: i32 = 4; +pub const SCALE_FACTOR: i32 = 6; // GDI Antialiasing Painter pub struct GdiAAPainter { - // memory - mem_hdc: HDC, - mem_map: HBITMAP, - // scaled - scaled_hdc: HDC, - scaled_map: HBITMAP, - // brush - fg_brush: HBRUSH, - bg_brush: HBRUSH, - // window region - window_rgn: HRGN, - // windows handle + token: usize, hwnd: HWND, - // content size - width: i32, - height: i32, - size: i32, - corner_radius: i32, - // scale - scale: i32, - // theme - light_theme: bool, + hdc_screen: HDC, + rounded_corner: bool, + light: bool, + show: bool, } impl GdiAAPainter { - /// Creates a new [GdiAAPainter] instance. - /// - /// The `scale` must be a multiple of 2, for example 2, 4, 6, 8, 12 ... - pub fn new(hwnd: HWND, scale: i32) -> Self { - let light_theme = match is_light_theme() { - Ok(v) => v, - Err(_) => { - warn!("Fail to get system theme"); - false - } + pub fn new(hwnd: HWND) -> Result { + let startup_input = GdiplusStartupInput { + GdiplusVersion: 1, + ..Default::default() }; - GdiAAPainter { - mem_hdc: Default::default(), - mem_map: Default::default(), - scaled_hdc: Default::default(), - scaled_map: Default::default(), - fg_brush: Default::default(), - bg_brush: Default::default(), - window_rgn: Default::default(), + let mut token: usize = 0; + check_error(|| unsafe { GdiplusStartup(&mut token, &startup_input, std::ptr::null_mut()) }) + .context("Failed to initialize GDI+")?; + + let hdc_screen = unsafe { GetDC(hwnd) }; + let rounded_corner = is_win11(); + let light = is_light_theme(); + + Ok(Self { + token, hwnd, - width: 0, - height: 0, - size: 0, - corner_radius: 0, - scale, - light_theme, - } + hdc_screen, + rounded_corner, + light, + show: false, + }) } - /// Initial this painter. - /// - /// Returns (icon_size, width, height) - pub fn init(&mut self, monitor_width: i32, num_apps: i32) -> (i32, i32, i32) { - let icon_size = ((monitor_width - 2 * WINDOW_BORDER_SIZE) / num_apps - - ICON_BORDER_SIZE * 2) - .min(ICON_SIZE); + pub fn paint(&mut self, state: &SwitchAppsState) { + let Coordinate { + x, + y, + width, + height, + icon_size, + item_size, + } = Coordinate::new(state.apps.len() as i32); - let item_size = icon_size + ICON_BORDER_SIZE * 2; - let width = item_size * num_apps + WINDOW_BORDER_SIZE * 2; - let height = item_size + WINDOW_BORDER_SIZE * 2; - let size = width * height; - if size == self.size { - return (icon_size, width, height); - } + let corner_radius = if self.rounded_corner { + item_size / 4 + } else { + 0 + }; + + let hwnd = self.hwnd; + let hdc_screen = self.hdc_screen; + + let (fg_color, bg_color) = theme_color(self.light); unsafe { - self.width = width; - self.height = height; - self.size = size; - - let _ = DeleteDC(self.mem_hdc); - let _ = DeleteObject(self.mem_map); - let _ = DeleteDC(self.scaled_hdc); - let _ = DeleteObject(self.scaled_map); - let _ = DeleteObject(self.fg_brush); - let _ = DeleteObject(self.bg_brush); - let _ = DeleteObject(self.window_rgn); - - let hdc = GetDC(self.hwnd); - let mem_dc = CreateCompatibleDC(hdc); - let mem_map = CreateCompatibleBitmap(hdc, width, height); - SelectObject(mem_dc, mem_map); - - let (fg_color, bg_color) = theme_color(self.light_theme); - self.fg_brush = CreateSolidBrush(fg_color); - self.bg_brush = CreateSolidBrush(bg_color); - - let corner_radius = item_size / 4; - let window_rgn = CreateRoundRectRgn(0, 0, width, height, corner_radius, corner_radius); - SetWindowRgn(self.hwnd, window_rgn, false); - let _ = FillRgn(hdc, window_rgn, self.fg_brush); - - let scaled_dc = CreateCompatibleDC(hdc); - let scaled_map = CreateCompatibleBitmap(hdc, width * self.scale, height * self.scale); - SelectObject(scaled_dc, scaled_map); - let rect = RECT { - left: 0, - top: 0, - right: width * self.scale, - bottom: height * self.scale, + let hdc_mem = CreateCompatibleDC(hdc_screen); + let bitmap_mem = CreateCompatibleBitmap(hdc_screen, width, height); + SelectObject(hdc_mem, bitmap_mem); + + let mut graphics = GpGraphics::default(); + let mut graphics_ptr: *mut GpGraphics = &mut graphics; + GdipCreateFromHDC(hdc_mem, &mut graphics_ptr as _); + GdipSetSmoothingMode(graphics_ptr, SmoothingModeAntiAlias); + GdipSetInterpolationMode(graphics_ptr, InterpolationModeHighQualityBicubic); + + let mut bg_pen = GpPen::default(); + let mut bg_pen_ptr: *mut GpPen = &mut bg_pen; + GdipCreatePen1(ALPHA_MASK | bg_color, 0.0, Unit(0), &mut bg_pen_ptr as _); + + let mut bg_brush = GpBrush::default(); + let mut bg_brush_ptr: *mut GpBrush = &mut bg_brush; + GdipGetPenBrushFill(bg_pen_ptr, &mut bg_brush_ptr as _); + + if self.rounded_corner { + draw_round_rect( + graphics_ptr, + bg_brush_ptr, + 0.0, + 0.0, + width as f32, + height as f32, + corner_radius as f32, + ); + } else { + GdipFillRectangle( + graphics_ptr, + bg_brush_ptr, + 0.0, + 0.0, + width as f32, + height as f32, + ); + } + + let icons_width = item_size * state.apps.len() as i32; + let icons_height = item_size; + let bitmap_icons = draw_icons( + state, + hdc_screen, + icon_size, + icons_width, + icons_height, + corner_radius, + fg_color, + bg_color, + ); + + let mut bitmap = GpBitmap::default(); + let mut bitmap_ptr: *mut GpBitmap = &mut bitmap as _; + GdipCreateBitmapFromHBITMAP(bitmap_icons, HPALETTE::default(), &mut bitmap_ptr as _); + + let image_ptr: *mut GpImage = bitmap_ptr as *mut GpImage; + GdipDrawImageRect( + graphics_ptr, + image_ptr, + WINDOW_BORDER_SIZE as f32, + WINDOW_BORDER_SIZE as f32, + icons_width as f32, + icons_height as f32, + ); + + let blend = BLENDFUNCTION { + BlendOp: AC_SRC_OVER as _, + SourceConstantAlpha: 255, + AlphaFormat: AC_SRC_ALPHA as _, + ..Default::default() }; - draw_round_rect(scaled_dc, &rect, self.fg_brush, corner_radius * self.scale); + let _ = UpdateLayeredWindow( + hwnd, + hdc_screen, + Some(&POINT { x, y }), + Some(&SIZE { + cx: width, + cy: height, + }), + hdc_mem, + Some(&POINT::default()), + COLORREF(0), + Some(&blend), + ULW_ALPHA, + ); - self.mem_hdc = mem_dc; - self.mem_map = mem_map; - self.scaled_hdc = scaled_dc; - self.scaled_map = scaled_map; + GdipDisposeImage(image_ptr); + GdipDeleteBrush(bg_brush_ptr); + GdipDeletePen(bg_pen_ptr); + GdipDeleteGraphics(graphics_ptr); - self.window_rgn = window_rgn; - self.corner_radius = corner_radius; + let _ = DeleteObject(bitmap_icons); + let _ = DeleteObject(bitmap_mem); + let _ = DeleteDC(hdc_mem); } - (icon_size, width, height) + if self.show { + return; + } + unsafe { + let _ = ShowWindow(self.hwnd, SW_SHOW); + let _ = SetFocus(self.hwnd); + } + self.show = true; } - /// Draw state onto hdc in memory - pub fn paint(&mut self, state: &SwitchAppsState) { - self.paint0(state); + pub fn unpaint(&mut self, state: SwitchAppsState) { unsafe { - SetStretchBltMode(self.mem_hdc, HALFTONE); - let _ = StretchBlt( - self.mem_hdc, - 0, - 0, - self.width, - self.height, - self.scaled_hdc, - 0, - 0, - self.width * self.scale, - self.height * self.scale, - SRCCOPY, - ); + let _ = ShowWindow(self.hwnd, SW_HIDE); } + for (hicon, _) in state.apps { + let _ = unsafe { DestroyIcon(hicon) }; + } + self.show = false; } +} - pub fn display(&mut self) { +impl Drop for GdiAAPainter { + fn drop(&mut self) { unsafe { - let mut ps = PAINTSTRUCT::default(); - let hdc = BeginPaint(self.hwnd, &mut ps); - let _ = BitBlt( - hdc, - 0, - 0, - self.width, - self.height, - self.mem_hdc, - 0, - 0, - SRCCOPY, - ); - let _ = EndPaint(self.hwnd, &ps); + ReleaseDC(self.hwnd, self.hdc_screen); + GdiplusShutdown(self.token); } } +} - fn paint0(&mut self, state: &SwitchAppsState) { - unsafe { - let corner_radius = self.corner_radius * self.scale; - - // draw background - let rect = RECT { - left: 0, - top: 0, - right: self.width * self.scale, - bottom: self.width * self.scale, - }; - draw_round_rect(self.scaled_hdc, &rect, self.fg_brush, corner_radius); - - let cy = (WINDOW_BORDER_SIZE + ICON_BORDER_SIZE) * self.scale; - let brush_icon = HBRUSH::default(); - let item_size = (state.icon_size + ICON_BORDER_SIZE * 2) * self.scale; - - for (i, (icon, _)) in state.apps.iter().enumerate() { - // draw the box for selected icon - if i == state.index { - let left = item_size * (i as i32) + WINDOW_BORDER_SIZE * self.scale; - let top = WINDOW_BORDER_SIZE * self.scale; - let right = left + item_size; - let bottom = top + item_size; - let rect = RECT { - left, - top, - right, - bottom, - }; - draw_round_rect(self.scaled_hdc, &rect as _, self.bg_brush, corner_radius); - } - - let cx = cy + item_size * (i as i32); - let _ = DrawIconEx( - self.scaled_hdc, - cx, - cy, - *icon, - state.icon_size * self.scale, - state.icon_size * self.scale, - 0, - brush_icon, - DI_NORMAL, - ); - } +pub fn find_clicked_app_index(state: &SwitchAppsState) -> Option { + let Coordinate { + x, y, item_size, .. + } = Coordinate::new(state.apps.len() as i32); + + let mut cursor_pos = POINT::default(); + let _ = unsafe { GetCursorPos(&mut cursor_pos) }; + + let xpos = cursor_pos.x - x; + let ypos = cursor_pos.y - y; + + let cy = WINDOW_BORDER_SIZE; + for (i, _) in state.apps.iter().enumerate() { + let cx = WINDOW_BORDER_SIZE + item_size * (i as i32); + if xpos >= cx && xpos < cx + item_size && ypos >= cy && ypos < cy + item_size { + return Some(i); } } + None +} + +const fn theme_color(light_theme: bool) -> (u32, u32) { + match light_theme { + true => (FG_LIGHT_COLOR, BG_LIGHT_COLOR), + false => (FG_DARK_COLOR, BG_DARK_COLOR), + } } -fn draw_round_rect(hdc: HDC, rect: &RECT, brush: HBRUSH, corner_radius: i32) { +unsafe fn draw_round_rect( + graphic_ptr: *mut GpGraphics, + brush_ptr: *mut GpBrush, + left: f32, + top: f32, + right: f32, + bottom: f32, + corner_radius: f32, +) { unsafe { - let rgn = CreateRoundRectRgn( - rect.left, - rect.top, - rect.right, - rect.bottom, + let mut path = GpPath::default(); + let mut path_ptr: *mut GpPath = &mut path; + GdipCreatePath(FillModeAlternate, &mut path_ptr as _); + GdipAddPathArc( + path_ptr, + left, + top, + corner_radius, + corner_radius, + 180.0, + 90.0, + ); + GdipAddPathArc( + path_ptr, + right - corner_radius, + top, + corner_radius, + corner_radius, + 270.0, + 90.0, + ); + GdipAddPathArc( + path_ptr, + right - corner_radius, + bottom - corner_radius, + corner_radius, + corner_radius, + 0.0, + 90.0, + ); + GdipAddPathArc( + path_ptr, + left, + bottom - corner_radius, corner_radius, corner_radius, + 90.0, + 90.0, + ); + GdipClosePathFigure(path_ptr); + GdipFillPath(graphic_ptr, brush_ptr, path_ptr); + GdipDeletePath(path_ptr); + } +} + +#[allow(clippy::too_many_arguments)] +fn draw_icons( + state: &SwitchAppsState, + hdc_screen: HDC, + icon_size: i32, + width: i32, + height: i32, + corner_radius: i32, + fg_color: u32, + bg_color: u32, +) -> HBITMAP { + let scaled_width = width * SCALE_FACTOR; + let scaled_height = height * SCALE_FACTOR; + let scaled_corner_radius = corner_radius * SCALE_FACTOR; + let scaled_border_size = ICON_BORDER_SIZE * SCALE_FACTOR; + let scaled_icon_inner_size = icon_size * SCALE_FACTOR; + let scaled_icon_outer_size = scaled_icon_inner_size + scaled_border_size * 2; + + unsafe { + let hdc_tmp = CreateCompatibleDC(hdc_screen); + let bitmap_tmp = CreateCompatibleBitmap(hdc_screen, width, height); + SelectObject(hdc_tmp, bitmap_tmp); + + let hdc_scaled = CreateCompatibleDC(hdc_screen); + let bitmap_scaled = CreateCompatibleBitmap(hdc_screen, scaled_width, scaled_height); + SelectObject(hdc_scaled, bitmap_scaled); + + let fg_brush = CreateSolidBrush(COLORREF(fg_color)); + let bg_brush = CreateSolidBrush(COLORREF(bg_color)); + + let rect = RECT { + left: 0, + top: 0, + right: scaled_width, + bottom: scaled_height, + }; + + FillRect(hdc_scaled, &rect, bg_brush); + + for (i, (icon, _)) in state.apps.iter().enumerate() { + // draw the box for selected icon + if i == state.index { + let left = scaled_icon_outer_size * (i as i32); + let top = 0; + let right = left + scaled_icon_outer_size; + let bottom = top + scaled_icon_outer_size; + let rgn = CreateRoundRectRgn( + left, + top, + right, + bottom, + scaled_corner_radius, + scaled_corner_radius, + ); + let _ = FillRgn(hdc_scaled, rgn, fg_brush); + let _ = DeleteObject(rgn); + } + + let cx = scaled_border_size + scaled_icon_outer_size * (i as i32); + let _ = DrawIconEx( + hdc_scaled, + cx, + scaled_border_size, + *icon, + scaled_icon_inner_size, + scaled_icon_inner_size, + 0, + HBRUSH::default(), + DI_NORMAL, + ); + } + + SetStretchBltMode(hdc_tmp, HALFTONE); + let _ = StretchBlt( + hdc_tmp, + 0, + 0, + width, + height, + hdc_scaled, + 0, + 0, + scaled_width, + scaled_height, + SRCCOPY, ); - let _ = FillRgn(hdc, rgn, brush); - let _ = DeleteObject(rgn); + let _ = DeleteObject(fg_brush); + let _ = DeleteObject(bg_brush); + let _ = DeleteObject(bitmap_scaled); + let _ = DeleteDC(hdc_scaled); + let _ = DeleteDC(hdc_tmp); + + bitmap_tmp } } -fn is_light_theme() -> Result { - let reg_key = RegKey::new_hkcu( - w!("Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"), - w!("SystemUsesLightTheme"), - )?; - let value = reg_key.get_int()?; - Ok(value == 1) +struct Coordinate { + x: i32, + y: i32, + width: i32, + height: i32, + icon_size: i32, + item_size: i32, } -const fn theme_color(light_theme: bool) -> (COLORREF, COLORREF) { - match light_theme { - true => (FG_LIGHT_COLOR, BG_LIGHT_COLOR), - false => (FG_DARK_COLOR, BG_DARK_COLOR), +impl Coordinate { + fn new(num_apps: i32) -> Self { + let monitor_rect = get_moinitor_rect(); + let monitor_width = monitor_rect.right - monitor_rect.left; + let monitor_height = monitor_rect.bottom - monitor_rect.top; + + let icon_size = ((monitor_width - 2 * WINDOW_BORDER_SIZE) / num_apps + - ICON_BORDER_SIZE * 2) + .min(ICON_SIZE); + + let item_size = icon_size + ICON_BORDER_SIZE * 2; + let width = item_size * num_apps + WINDOW_BORDER_SIZE * 2; + let height = item_size + WINDOW_BORDER_SIZE * 2; + let x = monitor_rect.left + (monitor_width - width) / 2; + let y = monitor_rect.top + (monitor_height - height) / 2; + + Self { + x, + y, + width, + height, + icon_size, + item_size, + } } } diff --git a/src/painter2.rs b/src/painter2.rs deleted file mode 100644 index bfef34b..0000000 --- a/src/painter2.rs +++ /dev/null @@ -1,433 +0,0 @@ -use crate::app::SwitchAppsState; -use crate::utils::{check_error, get_moinitor_rect, is_light_theme, is_win11}; - -use anyhow::{Context, Result}; -use windows::Win32::Foundation::{COLORREF, POINT, RECT, SIZE}; -use windows::Win32::Graphics::Gdi::{ - CreateCompatibleBitmap, CreateCompatibleDC, CreateRoundRectRgn, CreateSolidBrush, DeleteDC, - DeleteObject, FillRect, FillRgn, ReleaseDC, SelectObject, SetStretchBltMode, StretchBlt, - AC_SRC_ALPHA, AC_SRC_OVER, BLENDFUNCTION, HALFTONE, HBITMAP, HBRUSH, HDC, HPALETTE, SRCCOPY, -}; -use windows::Win32::Graphics::GdiPlus::{ - FillModeAlternate, GdipAddPathArc, GdipClosePathFigure, GdipCreateBitmapFromHBITMAP, - GdipCreateFromHDC, GdipCreatePath, GdipCreatePen1, GdipDeleteBrush, GdipDeleteGraphics, - GdipDeletePath, GdipDeletePen, GdipDisposeImage, GdipDrawImageRect, GdipFillPath, - GdipFillRectangle, GdipGetPenBrushFill, GdipSetInterpolationMode, GdipSetSmoothingMode, - GdiplusShutdown, GdiplusStartup, GdiplusStartupInput, GpBitmap, GpBrush, GpGraphics, GpImage, - GpPath, GpPen, InterpolationModeHighQualityBicubic, SmoothingModeAntiAlias, Unit, -}; -use windows::Win32::UI::Input::KeyboardAndMouse::SetFocus; -use windows::Win32::UI::WindowsAndMessaging::{ - DestroyIcon, DrawIconEx, GetCursorPos, ShowWindow, UpdateLayeredWindow, DI_NORMAL, SW_HIDE, - SW_SHOW, ULW_ALPHA, -}; -use windows::Win32::{Foundation::HWND, Graphics::Gdi::GetDC}; - -pub const BG_DARK_COLOR: u32 = 0x4c4c4c; -pub const FG_DARK_COLOR: u32 = 0x3b3b3b; -pub const BG_LIGHT_COLOR: u32 = 0xe0e0e0; -pub const FG_LIGHT_COLOR: u32 = 0xf2f2f2; -pub const ALPHA_MASK: u32 = 0xff000000; -pub const ICON_SIZE: i32 = 64; -pub const WINDOW_BORDER_SIZE: i32 = 10; -pub const ICON_BORDER_SIZE: i32 = 4; -pub const SCALE_FACTOR: i32 = 6; - -// GDI Antialiasing Painter -pub struct GdiAAPainter { - token: usize, - hwnd: HWND, - hdc_screen: HDC, - rounded_corner: bool, - light: bool, - show: bool, -} - -impl GdiAAPainter { - pub fn new(hwnd: HWND) -> Result { - let startup_input = GdiplusStartupInput { - GdiplusVersion: 1, - ..Default::default() - }; - let mut token: usize = 0; - check_error(|| unsafe { GdiplusStartup(&mut token, &startup_input, std::ptr::null_mut()) }) - .context("Failed to initialize GDI+")?; - - let hdc_screen = unsafe { GetDC(hwnd) }; - let rounded_corner = is_win11(); - let light = is_light_theme(); - - Ok(Self { - token, - hwnd, - hdc_screen, - rounded_corner, - light, - show: false, - }) - } - - pub fn paint(&mut self, state: &SwitchAppsState) { - let Coordinate { - x, - y, - width, - height, - icon_size, - item_size, - } = Coordinate::new(state.apps.len() as i32); - - let corner_radius = if self.rounded_corner { - item_size / 4 - } else { - 0 - }; - - let hwnd = self.hwnd; - let hdc_screen = self.hdc_screen; - - let (fg_color, bg_color) = theme_color(self.light); - - unsafe { - let hdc_mem = CreateCompatibleDC(hdc_screen); - let bitmap_mem = CreateCompatibleBitmap(hdc_screen, width, height); - SelectObject(hdc_mem, bitmap_mem); - - let mut graphics = GpGraphics::default(); - let mut graphics_ptr: *mut GpGraphics = &mut graphics; - GdipCreateFromHDC(hdc_mem, &mut graphics_ptr as _); - GdipSetSmoothingMode(graphics_ptr, SmoothingModeAntiAlias); - GdipSetInterpolationMode(graphics_ptr, InterpolationModeHighQualityBicubic); - - let mut bg_pen = GpPen::default(); - let mut bg_pen_ptr: *mut GpPen = &mut bg_pen; - GdipCreatePen1(ALPHA_MASK | bg_color, 0.0, Unit(0), &mut bg_pen_ptr as _); - - let mut bg_brush = GpBrush::default(); - let mut bg_brush_ptr: *mut GpBrush = &mut bg_brush; - GdipGetPenBrushFill(bg_pen_ptr, &mut bg_brush_ptr as _); - - if self.rounded_corner { - draw_round_rect( - graphics_ptr, - bg_brush_ptr, - 0.0, - 0.0, - width as f32, - height as f32, - corner_radius as f32, - ); - } else { - GdipFillRectangle( - graphics_ptr, - bg_brush_ptr, - 0.0, - 0.0, - width as f32, - height as f32, - ); - } - - let icons_width = item_size * state.apps.len() as i32; - let icons_height = item_size; - let bitmap_icons = draw_icons( - state, - hdc_screen, - icon_size, - icons_width, - icons_height, - corner_radius, - fg_color, - bg_color, - ); - - let mut bitmap = GpBitmap::default(); - let mut bitmap_ptr: *mut GpBitmap = &mut bitmap as _; - GdipCreateBitmapFromHBITMAP(bitmap_icons, HPALETTE::default(), &mut bitmap_ptr as _); - - let image_ptr: *mut GpImage = bitmap_ptr as *mut GpImage; - GdipDrawImageRect( - graphics_ptr, - image_ptr, - WINDOW_BORDER_SIZE as f32, - WINDOW_BORDER_SIZE as f32, - icons_width as f32, - icons_height as f32, - ); - - let blend = BLENDFUNCTION { - BlendOp: AC_SRC_OVER as _, - SourceConstantAlpha: 255, - AlphaFormat: AC_SRC_ALPHA as _, - ..Default::default() - }; - let _ = UpdateLayeredWindow( - hwnd, - hdc_screen, - Some(&POINT { x, y }), - Some(&SIZE { - cx: width, - cy: height, - }), - hdc_mem, - Some(&POINT::default()), - COLORREF(0), - Some(&blend), - ULW_ALPHA, - ); - - GdipDisposeImage(image_ptr); - GdipDeleteBrush(bg_brush_ptr); - GdipDeletePen(bg_pen_ptr); - GdipDeleteGraphics(graphics_ptr); - - let _ = DeleteObject(bitmap_icons); - let _ = DeleteObject(bitmap_mem); - let _ = DeleteDC(hdc_mem); - } - - if self.show { - return; - } - unsafe { - let _ = ShowWindow(self.hwnd, SW_SHOW); - let _ = SetFocus(self.hwnd); - } - self.show = true; - } - - pub fn unpaint(&mut self, state: SwitchAppsState) { - unsafe { - let _ = ShowWindow(self.hwnd, SW_HIDE); - } - for (hicon, _) in state.apps { - let _ = unsafe { DestroyIcon(hicon) }; - } - self.show = false; - } -} - -impl Drop for GdiAAPainter { - fn drop(&mut self) { - unsafe { - ReleaseDC(self.hwnd, self.hdc_screen); - GdiplusShutdown(self.token); - } - } -} - -pub fn find_clicked_app_index(state: &SwitchAppsState) -> Option { - let Coordinate { - x, y, item_size, .. - } = Coordinate::new(state.apps.len() as i32); - - let mut cursor_pos = POINT::default(); - let _ = unsafe { GetCursorPos(&mut cursor_pos) }; - - let xpos = cursor_pos.x - x; - let ypos = cursor_pos.y - y; - - let cy = WINDOW_BORDER_SIZE; - for (i, _) in state.apps.iter().enumerate() { - let cx = WINDOW_BORDER_SIZE + item_size * (i as i32); - if xpos >= cx && xpos < cx + item_size && ypos >= cy && ypos < cy + item_size { - return Some(i); - } - } - None -} - -const fn theme_color(light_theme: bool) -> (u32, u32) { - match light_theme { - true => (FG_LIGHT_COLOR, BG_LIGHT_COLOR), - false => (FG_DARK_COLOR, BG_DARK_COLOR), - } -} - -unsafe fn draw_round_rect( - graphic_ptr: *mut GpGraphics, - brush_ptr: *mut GpBrush, - left: f32, - top: f32, - right: f32, - bottom: f32, - corner_radius: f32, -) { - unsafe { - let mut path = GpPath::default(); - let mut path_ptr: *mut GpPath = &mut path; - GdipCreatePath(FillModeAlternate, &mut path_ptr as _); - GdipAddPathArc( - path_ptr, - left, - top, - corner_radius, - corner_radius, - 180.0, - 90.0, - ); - GdipAddPathArc( - path_ptr, - right - corner_radius, - top, - corner_radius, - corner_radius, - 270.0, - 90.0, - ); - GdipAddPathArc( - path_ptr, - right - corner_radius, - bottom - corner_radius, - corner_radius, - corner_radius, - 0.0, - 90.0, - ); - GdipAddPathArc( - path_ptr, - left, - bottom - corner_radius, - corner_radius, - corner_radius, - 90.0, - 90.0, - ); - GdipClosePathFigure(path_ptr); - GdipFillPath(graphic_ptr, brush_ptr, path_ptr); - GdipDeletePath(path_ptr); - } -} - -#[allow(clippy::too_many_arguments)] -fn draw_icons( - state: &SwitchAppsState, - hdc_screen: HDC, - icon_size: i32, - width: i32, - height: i32, - corner_radius: i32, - fg_color: u32, - bg_color: u32, -) -> HBITMAP { - let scaled_width = width * SCALE_FACTOR; - let scaled_height = height * SCALE_FACTOR; - let scaled_corner_radius = corner_radius * SCALE_FACTOR; - let scaled_border_size = ICON_BORDER_SIZE * SCALE_FACTOR; - let scaled_icon_inner_size = icon_size * SCALE_FACTOR; - let scaled_icon_outer_size = scaled_icon_inner_size + scaled_border_size * 2; - - unsafe { - let hdc_tmp = CreateCompatibleDC(hdc_screen); - let bitmap_tmp = CreateCompatibleBitmap(hdc_screen, width, height); - SelectObject(hdc_tmp, bitmap_tmp); - - let hdc_scaled = CreateCompatibleDC(hdc_screen); - let bitmap_scaled = CreateCompatibleBitmap(hdc_screen, scaled_width, scaled_height); - SelectObject(hdc_scaled, bitmap_scaled); - - let fg_brush = CreateSolidBrush(COLORREF(fg_color)); - let bg_brush = CreateSolidBrush(COLORREF(bg_color)); - - let rect = RECT { - left: 0, - top: 0, - right: scaled_width, - bottom: scaled_height, - }; - - FillRect(hdc_scaled, &rect, bg_brush); - - for (i, (icon, _)) in state.apps.iter().enumerate() { - // draw the box for selected icon - if i == state.index { - let left = scaled_icon_outer_size * (i as i32); - let top = 0; - let right = left + scaled_icon_outer_size; - let bottom = top + scaled_icon_outer_size; - let rgn = CreateRoundRectRgn( - left, - top, - right, - bottom, - scaled_corner_radius, - scaled_corner_radius, - ); - let _ = FillRgn(hdc_scaled, rgn, fg_brush); - let _ = DeleteObject(rgn); - } - - let cx = scaled_border_size + scaled_icon_outer_size * (i as i32); - let _ = DrawIconEx( - hdc_scaled, - cx, - scaled_border_size, - *icon, - scaled_icon_inner_size, - scaled_icon_inner_size, - 0, - HBRUSH::default(), - DI_NORMAL, - ); - } - - SetStretchBltMode(hdc_tmp, HALFTONE); - let _ = StretchBlt( - hdc_tmp, - 0, - 0, - width, - height, - hdc_scaled, - 0, - 0, - scaled_width, - scaled_height, - SRCCOPY, - ); - - let _ = DeleteObject(fg_brush); - let _ = DeleteObject(bg_brush); - let _ = DeleteObject(bitmap_scaled); - let _ = DeleteDC(hdc_scaled); - let _ = DeleteDC(hdc_tmp); - - bitmap_tmp - } -} - -struct Coordinate { - x: i32, - y: i32, - width: i32, - height: i32, - icon_size: i32, - item_size: i32, -} - -impl Coordinate { - fn new(num_apps: i32) -> Self { - let monitor_rect = get_moinitor_rect(); - let monitor_width = monitor_rect.right - monitor_rect.left; - let monitor_height = monitor_rect.bottom - monitor_rect.top; - - let icon_size = ((monitor_width - 2 * WINDOW_BORDER_SIZE) / num_apps - - ICON_BORDER_SIZE * 2) - .min(ICON_SIZE); - - let item_size = icon_size + ICON_BORDER_SIZE * 2; - let width = item_size * num_apps + WINDOW_BORDER_SIZE * 2; - let height = item_size + WINDOW_BORDER_SIZE * 2; - let x = monitor_rect.left + (monitor_width - width) / 2; - let y = monitor_rect.top + (monitor_height - height) / 2; - - Self { - x, - y, - width, - height, - icon_size, - item_size, - } - } -} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 3e5e020..23de7c7 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -6,8 +6,8 @@ mod scheduled_task; mod single_instance; mod window; mod windows_icon; -mod windows_version; mod windows_theme; +mod windows_version; pub use admin::*; pub use check_error::*; @@ -17,8 +17,8 @@ pub use scheduled_task::*; pub use single_instance::*; pub use window::*; pub use windows_icon::get_module_icon_ex; -pub use windows_version::*; pub use windows_theme::*; +pub use windows_version::*; pub fn to_wstring(value: &str) -> Vec { value.encode_utf16().chain(Some(0)).collect::>()