Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add custom cursor icon support on Windows #1617

Closed
wants to merge 31 commits into from
Closed
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ad3a1e0
Add ability to set custom cursor icon on Windows
Osspial Jun 29, 2020
0af0a12
Move to cross-platform API
Osspial Jul 4, 2020
9c438b6
Remove BadIcon in favor of io::Error and panicking when input values …
Osspial Jul 4, 2020
90b276c
Use PhysicalSize in icon creation fns
Osspial Jul 4, 2020
87b42d4
Add cursor hotspot support
Osspial Jul 4, 2020
1a8c250
Try to address compilation errors off of Windows
Osspial Jul 4, 2020
2bcb41a
Make compile on Linux, and Arc the pixel data on Linux
Osspial Jul 4, 2020
4506d5d
Make the examples compile again
Osspial Jul 4, 2020
72f4f23
Fix linux wayland-only builds
Osspial Jul 4, 2020
ab0931d
Try to fix macos build
Osspial Jul 4, 2020
4e9b7cc
Fix web build
Osspial Jul 4, 2020
23d398f
Format
Osspial Jul 4, 2020
962d44f
Don't print binary image data for RgbaIcon debug impl
Osspial Jul 4, 2020
cade872
Add changelog entry
Osspial Jul 9, 2020
54646ae
docs update
Osspial Jul 10, 2020
1fffadf
First pass at scalable icon support
Osspial Jul 12, 2020
3414511
Support automatic icon scaling on Windows
Osspial Jul 13, 2020
b7b8752
Format
Osspial Jul 13, 2020
8cef810
Try to fix macos and linux builds
Osspial Jul 13, 2020
3c61b2a
Update src/icon.rs
Osspial Jul 31, 2020
b402c2d
Update CHANGELOG.md
Osspial Jul 31, 2020
17d6f2e
Update src/icon.rs
Osspial Jul 31, 2020
da43221
Update src/icon.rs
Osspial Jul 31, 2020
5409114
Update src/icon.rs
Osspial Jul 31, 2020
9ee8a78
Improve code quality of X11 custom cursor ignore
Osspial Jul 31, 2020
4bea102
Redesign cursor API and implement on Windows
Osspial Aug 3, 2020
b21a92e
Make this compile on Linux
Osspial Aug 4, 2020
186c03b
Try to fix macos build
Osspial Aug 20, 2020
a030c6c
Attempt again
Osspial Aug 20, 2020
058fcd7
Format
Osspial Aug 20, 2020
9b7ea98
Implement both send and sync for RaiiIcon
Osspial Sep 16, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- On X11, fix deadlock when calling `set_fullscreen_inner`.
- On Web, prevent the webpage from scrolling when the user is focused on a winit canvas
- On Windows, drag and drop is now optional and must be enabled with `WindowBuilderExtWindows::with_drag_and_drop(true)`.
- Add custom cursor icon support on Windows.
Osspial marked this conversation as resolved.
Show resolved Hide resolved
- On Wayland, fix deadlock when calling to `set_inner_size` from a callback.
- On macOS, add `hide__other_applications` to `EventLoopWindowTarget` via existing `EventLoopWindowTargetExtMacOS` trait. `hide_other_applications` will hide other applications by calling `-[NSApplication hideOtherApplications: nil]`.
- On android added support for `run_return`.
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ raw-window-handle = "0.3"
bitflags = "1"

[dev-dependencies]
image = "0.23"
png = "0.16"
simple_logger = "1"

[target.'cfg(target_os = "android")'.dependencies]
Expand Down
113 changes: 71 additions & 42 deletions examples/cursor.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use std::{fs::File, path::Path};
use winit::{
dpi::{PhysicalPosition, PhysicalSize},
event::{ElementState, Event, KeyboardInput, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::{CursorIcon, WindowBuilder},
window::{CursorIcon, Icon, RgbaIcon, WindowBuilder},
};

fn main() {
Expand All @@ -13,6 +15,71 @@ fn main() {

let mut cursor_idx = 0;

let custom_cursor_icon = {
let base_path = Path::new(concat!(
env!("CARGO_MANIFEST_DIR"),
"/examples/icons/icon_folder/"
));

Icon::from_rgba_fn(move |size, _| {
let path = base_path.join(format!("{}.png", size.width));
let (icon_rgba, icon_size) = {
let decoder = png::Decoder::new(File::open(path)?);
let (info, mut reader) = decoder.read_info()?;

let mut rgba = vec![0; info.buffer_size()];
reader.next_frame(&mut rgba).unwrap();

(rgba, PhysicalSize::new(info.width, info.height))
};
Ok(RgbaIcon::from_rgba_with_hot_spot(
icon_rgba,
icon_size,
PhysicalPosition::new(0, 0),
))
})
.unwrap()
};

let cursors = vec![
CursorIcon::Custom(custom_cursor_icon),
CursorIcon::Default,
CursorIcon::Crosshair,
CursorIcon::Hand,
CursorIcon::Arrow,
CursorIcon::Move,
CursorIcon::Text,
CursorIcon::Wait,
CursorIcon::Help,
CursorIcon::Progress,
CursorIcon::NotAllowed,
CursorIcon::ContextMenu,
CursorIcon::Cell,
CursorIcon::VerticalText,
CursorIcon::Alias,
CursorIcon::Copy,
CursorIcon::NoDrop,
CursorIcon::Grab,
CursorIcon::Grabbing,
CursorIcon::AllScroll,
CursorIcon::ZoomIn,
CursorIcon::ZoomOut,
CursorIcon::EResize,
CursorIcon::NResize,
CursorIcon::NeResize,
CursorIcon::NwResize,
CursorIcon::SResize,
CursorIcon::SeResize,
CursorIcon::SwResize,
CursorIcon::WResize,
CursorIcon::EwResize,
CursorIcon::NsResize,
CursorIcon::NeswResize,
CursorIcon::NwseResize,
CursorIcon::ColResize,
CursorIcon::RowResize,
];

event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;

Expand All @@ -29,9 +96,9 @@ fn main() {
},
..
} => {
println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]);
window.set_cursor_icon(CURSORS[cursor_idx]);
if cursor_idx < CURSORS.len() - 1 {
println!("Setting cursor to \"{:?}\"", cursors[cursor_idx]);
window.set_cursor_icon(cursors[cursor_idx].clone());
if cursor_idx < cursors.len() - 1 {
cursor_idx += 1;
} else {
cursor_idx = 0;
Expand All @@ -48,41 +115,3 @@ fn main() {
}
});
}

const CURSORS: &[CursorIcon] = &[
CursorIcon::Default,
CursorIcon::Crosshair,
CursorIcon::Hand,
CursorIcon::Arrow,
CursorIcon::Move,
CursorIcon::Text,
CursorIcon::Wait,
CursorIcon::Help,
CursorIcon::Progress,
CursorIcon::NotAllowed,
CursorIcon::ContextMenu,
CursorIcon::Cell,
CursorIcon::VerticalText,
CursorIcon::Alias,
CursorIcon::Copy,
CursorIcon::NoDrop,
CursorIcon::Grab,
CursorIcon::Grabbing,
CursorIcon::AllScroll,
CursorIcon::ZoomIn,
CursorIcon::ZoomOut,
CursorIcon::EResize,
CursorIcon::NResize,
CursorIcon::NeResize,
CursorIcon::NwResize,
CursorIcon::SResize,
CursorIcon::SeResize,
CursorIcon::SwResize,
CursorIcon::WResize,
CursorIcon::EwResize,
CursorIcon::NsResize,
CursorIcon::NeswResize,
CursorIcon::NwseResize,
CursorIcon::ColResize,
CursorIcon::RowResize,
];
Binary file removed examples/icon.png
Binary file not shown.
Binary file added examples/icons/ferris.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/icons/icon_folder/128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/icons/icon_folder/16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/icons/icon_folder/24.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/icons/icon_folder/256.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/icons/icon_folder/32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/icons/icon_folder/48.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/icons/icon_folder/64.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/icons/icon_folder/96.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
51 changes: 34 additions & 17 deletions examples/window_icon.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
extern crate image;
use std::path::Path;
use std::{ffi::OsStr, fs::File, path::Path};
use winit::{
dpi::PhysicalSize,
event::Event,
event_loop::{ControlFlow, EventLoop},
window::{Icon, WindowBuilder},
window::{Icon, RgbaIcon, WindowBuilder},
};

fn main() {
simple_logger::init().unwrap();

// You'll have to choose an icon size at your own discretion. On X11, the desired size varies
// by WM, and on Windows, you still have to account for screen scaling. Here we use 32px,
// since it seems to work well enough in most cases. Be careful about going too high, or
// you'll be bitten by the low-quality downscaling built into the WM.
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/icon.png");
let path = Path::new(concat!(
env!("CARGO_MANIFEST_DIR"),
"/examples/icons/icon_folder/"
));

let icon = load_icon(Path::new(path));
let icon = load_icon(&path);

let event_loop = EventLoop::new();

Expand Down Expand Up @@ -44,13 +43,31 @@ fn main() {
}

fn load_icon(path: &Path) -> Icon {
let (icon_rgba, icon_width, icon_height) = {
let image = image::open(path)
.expect("Failed to open icon path")
.into_rgba();
let (width, height) = image.dimensions();
let rgba = image.into_raw();
(rgba, width, height)
let decode_png = |path: &Path| {
let decoder = png::Decoder::new(File::open(path).unwrap());
let (info, mut reader) = decoder.read_info().unwrap();

let mut rgba = vec![0; info.buffer_size()];
reader.next_frame(&mut rgba).unwrap();

(rgba, PhysicalSize::new(info.width, info.height))
};
Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon")
if path.is_file() {
if path.extension() == Some(OsStr::new("png")) {
let (icon_rgba, icon_size) = decode_png(path);
Icon::from_rgba(&icon_rgba, icon_size).unwrap()
} else {
panic!("unsupported file extension: {:?}", path.extension());
}
} else if path.is_dir() {
let path = path.to_owned();
Icon::from_rgba_fn(move |size, _| {
let path = path.join(format!("{}.png", size.width));
let (icon_rgba, icon_size) = decode_png(&path);
Ok(RgbaIcon::from_rgba(icon_rgba, icon_size))
})
.unwrap()
} else {
panic!("path {} is neither file nor directory", path.display());
}
}
2 changes: 1 addition & 1 deletion src/dpi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ impl<P: Pixel, X: Pixel> Into<[X; 2]> for LogicalPosition<P> {
}

/// A position represented in physical pixels.
#[derive(Debug, Copy, Clone, PartialEq)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PhysicalPosition<P> {
pub x: P,
Expand Down
Loading