Skip to content

Commit

Permalink
feat: add rounded corners to match Windows 11 design (#141)
Browse files Browse the repository at this point in the history
* refactor: add rounded corner to UI

* switch to GDI+ to avoid jagged edge on rounded corner

* anti-aliasing app icons

* add rounded corner only in win11

* split is_light_theme to utils

* replace old painter
  • Loading branch information
sigoden authored Oct 22, 2024
1 parent 6d6bcff commit 131502f
Show file tree
Hide file tree
Showing 7 changed files with 484 additions and 306 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ features = [
"Win32_UI_Accessibility",
"Win32_Graphics_Dwm",
"Win32_Graphics_Gdi",
"Win32_Graphics_GdiPlus",
"Win32_Security",
"Win32_Security_Authorization",
"Win32_System_Console",
Expand All @@ -31,6 +32,7 @@ features = [
"Win32_System_SystemInformation",
"Win32_System_Threading",
"Win32_Storage_FileSystem",
"Wdk_System_SystemServices",
]

[build-dependencies]
Expand Down
132 changes: 25 additions & 107 deletions src/app.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::config::{edit_config_file, Config};
use crate::foreground::ForegroundWatcher;
use crate::keyboard::KeyboardListener;
use crate::painter::{find_clicked_app_index, GdiAAPainter};
use crate::startup::Startup;
use crate::trayicon::TrayIcon;
use crate::utils::{
Expand All @@ -9,28 +10,19 @@ use crate::utils::{
is_running_as_admin, list_windows, set_foreground_window, set_window_user_data,
};

use crate::painter::{GdiAAPainter, ICON_BORDER_SIZE, WINDOW_BORDER_SIZE};
use anyhow::{anyhow, Result};
use indexmap::IndexSet;
use std::collections::HashMap;
use windows::core::w;
use windows::core::PCWSTR;
use windows::Win32::Foundation::{
GetLastError, HINSTANCE, HMODULE, HWND, LPARAM, LRESULT, POINT, WPARAM,
};
use windows::Win32::Graphics::Gdi::{
GetMonitorInfoW, MonitorFromPoint, RedrawWindow, HRGN, MONITORINFO, MONITOR_DEFAULTTONEAREST,
RDW_ERASE, RDW_INVALIDATE,
};
use windows::Win32::Foundation::{GetLastError, HINSTANCE, HWND, LPARAM, LRESULT, WPARAM};
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
use windows::Win32::UI::Input::KeyboardAndMouse::SetFocus;
use windows::Win32::UI::WindowsAndMessaging::{
CreateWindowExW, DefWindowProcW, DestroyIcon, DispatchMessageW, GetCursorPos, GetMessageW,
GetWindowLongPtrW, LoadCursorW, PostMessageW, PostQuitMessage, RegisterClassW,
RegisterWindowMessageW, SetCursor, SetWindowLongPtrW, SetWindowPos, ShowWindow,
TranslateMessage, CS_HREDRAW, CS_VREDRAW, CW_USEDEFAULT, GWL_STYLE, HICON, HWND_TOPMOST,
IDC_ARROW, MSG, SWP_SHOWWINDOW, SW_HIDE, WINDOW_STYLE, WM_COMMAND, WM_ERASEBKGND, WM_LBUTTONUP,
WM_PAINT, WM_RBUTTONUP, WNDCLASSW, WS_CAPTION, WS_EX_TOOLWINDOW,
CreateWindowExW, DefWindowProcW, DispatchMessageW, GetMessageW, GetWindowLongPtrW, LoadCursorW,
PostMessageW, PostQuitMessage, RegisterClassW, RegisterWindowMessageW, SetWindowLongPtrW,
TranslateMessage, CS_HREDRAW, CS_VREDRAW, CW_USEDEFAULT, GWL_STYLE, HICON, HTCLIENT, IDC_ARROW,
MSG, WINDOW_STYLE, WM_COMMAND, WM_ERASEBKGND, WM_LBUTTONUP, WM_NCHITTEST, WM_RBUTTONUP,
WNDCLASSW, WS_CAPTION, WS_EX_LAYERED, WS_EX_TOOLWINDOW, WS_EX_TOPMOST,
};

pub const NAME: PCWSTR = w!("Window Switcher");
Expand Down Expand Up @@ -67,7 +59,7 @@ pub struct App {
impl App {
pub fn start(config: &Config) -> Result<()> {
let hwnd = Self::create_window()?;
let painter = GdiAAPainter::new(hwnd, 6);
let painter = GdiAAPainter::new(hwnd)?;

let _foreground_watcher = ForegroundWatcher::init(&config.switch_windows_blacklist)?;
let _keyboard_listener = KeyboardListener::init(hwnd, &config.to_hotkeys())?;
Expand Down Expand Up @@ -130,7 +122,11 @@ impl App {
let hinstance = unsafe { GetModuleHandleW(None) }
.map_err(|err| anyhow!("Failed to get current module handle, {err}"))?;

let hcursor = unsafe { LoadCursorW(None, IDC_ARROW) }
.map_err(|err| anyhow!("Failed to load arrow cursor, {err}"))?;

let window_class = WNDCLASSW {
hCursor: hcursor,
hInstance: HINSTANCE(hinstance.0),
lpszClassName: NAME,
style: CS_HREDRAW | CS_VREDRAW,
Expand All @@ -143,7 +139,7 @@ impl App {

let hwnd = unsafe {
CreateWindowExW(
WS_EX_TOOLWINDOW,
WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_TOOLWINDOW,
PCWSTR(atom as _),
NAME,
WINDOW_STYLE(0),
Expand Down Expand Up @@ -224,9 +220,6 @@ impl App {
let app = get_app(hwnd)?;
let reverse = lparam.0 == 1;
app.switch_apps(reverse)?;
unsafe {
let _ = RedrawWindow(hwnd, None, HRGN::default(), RDW_ERASE | RDW_INVALIDATE);
}
if let Some(state) = &app.switch_apps_state {
app.painter.paint(state);
}
Expand Down Expand Up @@ -257,11 +250,12 @@ impl App {
let app = get_app(hwnd)?;
app.switch_windows_state.modifier_released = true;
}
WM_NCHITTEST => {
return Ok(LRESULT(HTCLIENT as _));
}
WM_LBUTTONUP => {
let app = get_app(hwnd)?;
let xpos = ((lparam.0 as usize) & 0xFFFF) as u16 as i32;
let ypos = (((lparam.0 as usize) & 0xFFFF_0000) >> 16) as u16 as i32;
app.click(xpos, ypos)?;
app.click();
}
WM_COMMAND => {
let value = wparam.0 as u32;
Expand Down Expand Up @@ -291,10 +285,6 @@ impl App {
WM_ERASEBKGND => {
return Ok(LRESULT(0));
}
WM_PAINT => {
let app = get_app(hwnd)?;
app.paint()?;
}
_ if msg == WM_USER_REGISTER_TRAYICON || unsafe { msg == WM_TASKBARCREATED } => {
let app = get_app(hwnd)?;
app.set_trayicon();
Expand Down Expand Up @@ -403,7 +393,6 @@ impl App {
debug!("switch apps: new index:{}", state.index);
return Ok(());
}
let hwnd = self.hwnd;
let windows = list_windows(self.config.switch_apps_ignore_minimal)?;
let mut apps = vec![];
for (module_path, hwnds) in windows.iter() {
Expand Down Expand Up @@ -431,44 +420,6 @@ impl App {
if num_apps == 0 {
return Ok(());
}
let mut mi = MONITORINFO {
cbSize: std::mem::size_of::<MONITORINFO>() as u32,
..MONITORINFO::default()
};
unsafe {
let mut cursor = POINT::default();
let _ = GetCursorPos(&mut cursor);

let hmonitor = MonitorFromPoint(cursor, MONITOR_DEFAULTTONEAREST);
let _ = GetMonitorInfoW(hmonitor, &mut mi);
}

let monitor_rect = mi.rcMonitor;
let monitor_width = monitor_rect.right - monitor_rect.left;
let monitor_height = monitor_rect.bottom - monitor_rect.top;
let (icon_size, window_width, window_height) =
self.painter.init(monitor_width, apps.len() as i32);

// Calculate the position to center the window
let x = monitor_rect.left + (monitor_width - window_width) / 2;
let y = monitor_rect.top + (monitor_height - window_height) / 2;

unsafe {
// Change busy cursor to array cursor
if let Ok(hcursor) = LoadCursorW(HMODULE::default(), IDC_ARROW) {
SetCursor(hcursor);
}
let _ = SetFocus(hwnd);
let _ = SetWindowPos(
hwnd,
HWND_TOPMOST,
x,
y,
window_width,
window_height,
SWP_SHOWWINDOW,
);
}

let index = if apps.len() == 1 {
0
Expand All @@ -478,65 +429,33 @@ impl App {
1
};

self.switch_apps_state = Some(SwitchAppsState {
apps,
index,
icon_size,
});
let state = SwitchAppsState { apps, index };
self.switch_apps_state = Some(state);
debug!("switch apps, new state:{:?}", self.switch_apps_state);
Ok(())
}

fn paint(&mut self) -> Result<()> {
self.painter.display();
Ok(())
}

fn click(&mut self, xpos: i32, ypos: i32) -> Result<()> {
fn click(&mut self) {
if let Some(state) = self.switch_apps_state.as_mut() {
let cy = WINDOW_BORDER_SIZE + ICON_BORDER_SIZE;
let item_size = state.icon_size + 2 * ICON_BORDER_SIZE;
for (i, (_, _)) in state.apps.iter().enumerate() {
let cx = WINDOW_BORDER_SIZE + item_size * (i as i32) + ICON_BORDER_SIZE;
if xpos >= cx
&& xpos <= cx + state.icon_size
&& ypos >= cy
&& ypos <= cy + state.icon_size
{
state.index = i;
self.do_switch_app();
break;
}
if let Some(i) = find_clicked_app_index(state) {
state.index = i;
self.do_switch_app();
}
}

Ok(())
}

fn do_switch_app(&mut self) {
let hwnd = self.hwnd;
if let Some(state) = self.switch_apps_state.take() {
if let Some((_, id)) = state.apps.get(state.index) {
set_foreground_window(*id);
}
for (hicon, _) in state.apps {
let _ = unsafe { DestroyIcon(hicon) };
}
unsafe {
let _ = ShowWindow(hwnd, SW_HIDE);
}
self.painter.unpaint(state);
}
}

fn cancel_switch_app(&mut self) {
let hwnd = self.hwnd;
if let Some(state) = self.switch_apps_state.take() {
for (hicon, _) in state.apps {
let _ = unsafe { DestroyIcon(hicon) };
}
unsafe {
let _ = ShowWindow(hwnd, SW_HIDE);
}
self.painter.unpaint(state);
}
}
}
Expand All @@ -560,5 +479,4 @@ struct SwitchWindowsState {
pub struct SwitchAppsState {
pub apps: Vec<(HICON, HWND)>,
pub index: usize,
pub icon_size: i32,
}
Loading

0 comments on commit 131502f

Please sign in to comment.