diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c69be92..b7eaa75c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ - On X11, drag and drop now works reliably in release mode. - Added `WindowBuilderExt::with_resize_increments` and `WindowBuilderExt::with_base_size` to X11, allowing for more optional hints to be set. - Rework of the wayland backend, migrating it to use [Smithay's Client Toolkit](https://github.com/Smithay/client-toolkit). +- Added `WindowBuilder::with_window_icon` and `Window::set_window_icon`, finally making it possible to set the window icon on Windows and X11. The `icon_loading` feature can be enabled to allow for icons to be easily loaded; see example program `window_icon.rs` for usage. +- Windows additionally has `WindowBuilderExt::with_taskbar_icon` and `WindowExt::set_taskbar_icon`. # Version 0.13.1 (2018-04-26) diff --git a/Cargo.toml b/Cargo.toml index 7df51f3f..89dc078c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,9 +10,13 @@ repository = "https://github.com/tomaka/winit" documentation = "https://docs.rs/winit" categories = ["gui"] +[features] +icon_loading = ["image"] + [dependencies] lazy_static = "1" libc = "0.2" +image = { version = "0.19", optional = true } [target.'cfg(target_os = "android")'.dependencies.android_glue] version = "0.2" diff --git a/examples/icon.png b/examples/icon.png new file mode 100644 index 00000000..aa3fbf3c Binary files /dev/null and b/examples/icon.png differ diff --git a/examples/window_icon.rs b/examples/window_icon.rs new file mode 100644 index 00000000..012faea9 --- /dev/null +++ b/examples/window_icon.rs @@ -0,0 +1,95 @@ +// Heads up: you need to compile this example with `--features icon_loading`. +// `Icon::from_path` won't be available otherwise, though for your own applications, you could use +// `Icon::from_rgba` if you don't want to depend on the `image` crate. + +extern crate winit; +#[cfg(feature = "icon_loading")] +extern crate image; + +use winit::Icon; + +#[cfg(feature = "icon_loading")] +fn main() { + // 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"); + // While `Icon::from_path` is the most straightforward, you have a few other options. If you + // want to use the `include_bytes` macro, then pass the result to `Icon::from_bytes`. See the + // docs for the full list of options (you'll have to generate the docs with the `icon_loading` + // feature enabled). + let icon = Icon::from_path(path).expect("Failed to open icon"); + + let mut events_loop = winit::EventsLoop::new(); + + let window = winit::WindowBuilder::new() + .with_title("An iconic window!") + // At present, this only does anything on Windows and X11, so if you want to save load + // time, you can put icon loading behind a function that returns `None` on other platforms. + .with_window_icon(Some(icon)) + .build(&events_loop) + .unwrap(); + + events_loop.run_forever(|event| { + if let winit::Event::WindowEvent { event, .. } = event { + use winit::WindowEvent::*; + match event { + CloseRequested => return winit::ControlFlow::Break, + DroppedFile(path) => { + use image::GenericImage; + + let icon_image = image::open(path).expect("Failed to open window icon"); + + let (width, height) = icon_image.dimensions(); + const DESIRED_SIZE: u32 = 32; + let (new_width, new_height) = if width == height { + (DESIRED_SIZE, DESIRED_SIZE) + } else { + // Note that this will never divide by zero, due to the previous condition. + let aspect_adjustment = DESIRED_SIZE as f64 + / std::cmp::max(width, height) as f64; + ( + (width as f64 * aspect_adjustment) as u32, + (height as f64 * aspect_adjustment) as u32, + ) + }; + + // By scaling the icon ourselves, we get higher-quality filtering and save + // some memory. + let icon = image::imageops::resize( + &icon_image, + new_width, + new_height, + image::FilterType::Lanczos3, + ); + + let (offset_x, offset_y) = ( + (DESIRED_SIZE - new_width) / 2, + (DESIRED_SIZE - new_height) / 2, + ); + + let mut canvas = image::ImageBuffer::new(DESIRED_SIZE, DESIRED_SIZE); + image::imageops::replace( + &mut canvas, + &icon, + offset_x, + offset_y, + ); + + window.set_window_icon(Some(canvas.into())); + }, + _ => (), + } + } + winit::ControlFlow::Continue + }); +} + +#[cfg(not(feature = "icon_loading"))] +fn main() { + print!( +r#"This example requires the `icon_loading` feature: + cargo run --example window_icon --features icon_loading +"#); +} diff --git a/src/icon.rs b/src/icon.rs new file mode 100644 index 00000000..c5106e44 --- /dev/null +++ b/src/icon.rs @@ -0,0 +1,160 @@ +use std::{fmt, mem}; +use std::error::Error; +#[cfg(feature = "icon_loading")] +use std::io::{BufRead, Seek}; +#[cfg(feature = "icon_loading")] +use std::path::Path; + +#[cfg(feature = "icon_loading")] +use image; + +#[repr(C)] +#[derive(Debug)] +pub(crate) struct Pixel { + pub(crate) r: u8, + pub(crate) g: u8, + pub(crate) b: u8, + pub(crate) a: u8, +} + +pub(crate) const PIXEL_SIZE: usize = mem::size_of::(); + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// An error produced when using `Icon::from_rgba` with invalid arguments. +pub enum BadIcon { + /// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be + /// safely interpreted as 32bpp RGBA pixels. + ByteCountNotDivisibleBy4 { + byte_count: usize, + }, + /// Produced when the number of pixels (`rgba.len() / 4`) isn't equal to `width * height`. + /// At least one of your arguments is incorrect. + DimensionsVsPixelCount { + width: u32, + height: u32, + width_x_height: usize, + pixel_count: usize, + }, +} + +impl fmt::Display for BadIcon { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + let msg = match self { + &BadIcon::ByteCountNotDivisibleBy4 { byte_count } => format!( + "The length of the `rgba` argument ({:?}) isn't divisible by 4, making it impossible to interpret as 32bpp RGBA pixels.", + byte_count, + ), + &BadIcon::DimensionsVsPixelCount { + width, + height, + width_x_height, + pixel_count, + } => format!( + "The specified dimensions ({:?}x{:?}) don't match the number of pixels supplied by the `rgba` argument ({:?}). For those dimensions, the expected pixel count is {:?}.", + width, height, pixel_count, width_x_height, + ), + }; + write!(formatter, "{}", msg) + } +} + +impl Error for BadIcon { + fn description(&self) -> &str { + "A valid icon cannot be created from these arguments" + } + + fn cause(&self) -> Option<&Error> { + Some(self) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +/// An icon used for the window titlebar, taskbar, etc. +/// +/// Enabling the `icon_loading` feature provides you with several convenience methods for creating +/// an `Icon` from any format supported by the [image](https://github.com/PistonDevelopers/image) +/// crate. +pub struct Icon { + pub(crate) rgba: Vec, + pub(crate) width: u32, + pub(crate) height: u32, +} + +impl Icon { + /// Creates an `Icon` from 32bpp RGBA data. + /// + /// The length of `rgba` must be divisible by 4, and `width * height` must equal + /// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error. + pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { + if rgba.len() % PIXEL_SIZE != 0 { + return Err(BadIcon::ByteCountNotDivisibleBy4 { byte_count: rgba.len() }); + } + let pixel_count = rgba.len() / PIXEL_SIZE; + if pixel_count != (width * height) as usize { + Err(BadIcon::DimensionsVsPixelCount { + width, + height, + width_x_height: (width * height) as usize, + pixel_count, + }) + } else { + Ok(Icon { rgba, width, height }) + } + } + + #[cfg(feature = "icon_loading")] + /// Loads an `Icon` from the path of an image on the filesystem. + pub fn from_path>(path: P) -> image::ImageResult { + image::open(path).map(Into::into) + } + + #[cfg(feature = "icon_loading")] + /// Loads an `Icon` from anything implementing `BufRead` and `Seek`. + pub fn from_reader( + reader: R, + format: image::ImageFormat, + ) -> image::ImageResult { + image::load(reader, format).map(Into::into) + } + + #[cfg(feature = "icon_loading")] + /// Loads an `Icon` from the unprocessed bytes of an image file. + /// Uses heuristics to determine format. + pub fn from_bytes(bytes: &[u8]) -> image::ImageResult { + image::load_from_memory(bytes).map(Into::into) + } + + #[cfg(feature = "icon_loading")] + /// Loads an `Icon` from the unprocessed bytes of an image. + pub fn from_bytes_with_format( + bytes: &[u8], + format: image::ImageFormat, + ) -> image::ImageResult { + image::load_from_memory_with_format(bytes, format).map(Into::into) + } +} + +#[cfg(feature = "icon_loading")] +impl From for Icon { + fn from(image: image::DynamicImage) -> Self { + use image::{GenericImage, Pixel}; + let (width, height) = image.dimensions(); + let mut rgba = Vec::with_capacity((width * height) as usize * PIXEL_SIZE); + for (_, _, pixel) in image.pixels() { + rgba.extend_from_slice(&pixel.to_rgba().data); + } + Icon { rgba, width, height } + } +} + +#[cfg(feature = "icon_loading")] +impl From for Icon { + fn from(buf: image::RgbaImage) -> Self { + let (width, height) = buf.dimensions(); + let mut rgba = Vec::with_capacity((width * height) as usize * PIXEL_SIZE); + for (_, _, pixel) in buf.enumerate_pixels() { + rgba.extend_from_slice(&pixel.data); + } + Icon { rgba, width, height } + } +} diff --git a/src/lib.rs b/src/lib.rs index cfa30e5d..70a38eb2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,8 +83,9 @@ #[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "windows"))] #[macro_use] extern crate lazy_static; - extern crate libc; +#[cfg(feature = "icon_loading")] +extern crate image; #[cfg(target_os = "windows")] #[macro_use] @@ -109,10 +110,12 @@ extern crate smithay_client_toolkit as sctk; pub use events::*; pub use window::{AvailableMonitorsIter, MonitorId}; +pub use icon::*; mod platform; mod events; mod window; +mod icon; pub mod os; @@ -439,6 +442,11 @@ pub struct WindowAttributes { /// The default is `true`. pub decorations: bool, + /// The window icon. + /// + /// The default is `None`. + pub window_icon: Option, + /// [iOS only] Enable multitouch, /// see [multipleTouchEnabled](https://developer.apple.com/documentation/uikit/uiview/1622519-multipletouchenabled) pub multitouch: bool, @@ -457,6 +465,7 @@ impl Default for WindowAttributes { visible: true, transparent: false, decorations: true, + window_icon: None, multitouch: false, } } diff --git a/src/os/windows.rs b/src/os/windows.rs index 1526d3b7..15c80a55 100644 --- a/src/os/windows.rs +++ b/src/os/windows.rs @@ -1,19 +1,21 @@ #![cfg(target_os = "windows")] use std::os::raw::c_void; + use libc; -use MonitorId; -use DeviceId; -use Window; -use WindowBuilder; use winapi::shared::windef::HWND; +use {DeviceId, Icon, MonitorId, Window, WindowBuilder}; + /// Additional methods on `Window` that are specific to Windows. pub trait WindowExt { /// Returns the native handle that is used by this window. /// /// The pointer will become invalid when the native window was destroyed. fn get_hwnd(&self) -> *mut libc::c_void; + + /// This sets `ICON_BIG`. A good ceiling here is 256x256. + fn set_taskbar_icon(&self, taskbar_icon: Option); } impl WindowExt for Window { @@ -21,20 +23,34 @@ impl WindowExt for Window { fn get_hwnd(&self) -> *mut libc::c_void { self.window.hwnd() as *mut _ } + + #[inline] + fn set_taskbar_icon(&self, taskbar_icon: Option) { + self.window.set_taskbar_icon(taskbar_icon) + } } /// Additional methods on `WindowBuilder` that are specific to Windows. pub trait WindowBuilderExt { + /// Sets a parent to the window to be created. fn with_parent_window(self, parent: HWND) -> WindowBuilder; + + /// This sets `ICON_BIG`. A good ceiling here is 256x256. + fn with_taskbar_icon(self, taskbar_icon: Option) -> WindowBuilder; } impl WindowBuilderExt for WindowBuilder { - /// Sets a parent to the window to be created. #[inline] fn with_parent_window(mut self, parent: HWND) -> WindowBuilder { self.platform_specific.parent = Some(parent); self } + + #[inline] + fn with_taskbar_icon(mut self, taskbar_icon: Option) -> WindowBuilder { + self.platform_specific.taskbar_icon = taskbar_icon; + self + } } /// Additional methods on `MonitorId` that are specific to Windows. diff --git a/src/platform/android/mod.rs b/src/platform/android/mod.rs index 7d1e4600..5b70efad 100644 --- a/src/platform/android/mod.rs +++ b/src/platform/android/mod.rs @@ -201,8 +201,8 @@ pub struct PlatformSpecificWindowBuilderAttributes; pub struct PlatformSpecificHeadlessBuilderAttributes; impl Window { - pub fn new(_: &EventsLoop, win_attribs: &WindowAttributes, - _: &PlatformSpecificWindowBuilderAttributes) + pub fn new(_: &EventsLoop, win_attribs: WindowAttributes, + _: PlatformSpecificWindowBuilderAttributes) -> Result { // not implemented @@ -323,6 +323,11 @@ impl Window { // N/A } + #[inline] + pub fn set_window_icon(&self, _icon: Option<::Icon>) { + // N/A + } + #[inline] pub fn get_current_monitor(&self) -> RootMonitorId { RootMonitorId{inner: MonitorId} diff --git a/src/platform/emscripten/mod.rs b/src/platform/emscripten/mod.rs index 23a229d2..afe4c4c5 100644 --- a/src/platform/emscripten/mod.rs +++ b/src/platform/emscripten/mod.rs @@ -347,8 +347,8 @@ fn em_try(res: ffi::EMSCRIPTEN_RESULT) -> Result<(), String> { } impl Window { - pub fn new(events_loop: &EventsLoop, attribs: &::WindowAttributes, - _pl_attribs: &PlatformSpecificWindowBuilderAttributes) + pub fn new(events_loop: &EventsLoop, attribs: ::WindowAttributes, + _pl_attribs: PlatformSpecificWindowBuilderAttributes) -> Result { if events_loop.window.lock().unwrap().is_some() { @@ -543,6 +543,11 @@ impl Window { // N/A } + #[inline] + pub fn set_window_icon(&self, _icon: Option<::Icon>) { + // N/A + } + #[inline] pub fn get_current_monitor(&self) -> ::MonitorId { ::MonitorId{inner: MonitorId} diff --git a/src/platform/ios/mod.rs b/src/platform/ios/mod.rs index 2280925f..336c33e3 100644 --- a/src/platform/ios/mod.rs +++ b/src/platform/ios/mod.rs @@ -263,7 +263,7 @@ pub struct DeviceId; pub struct PlatformSpecificWindowBuilderAttributes; impl Window { - pub fn new(ev: &EventsLoop, _: &WindowAttributes, _: &PlatformSpecificWindowBuilderAttributes) + pub fn new(ev: &EventsLoop, _: WindowAttributes, _: PlatformSpecificWindowBuilderAttributes) -> Result { Ok(Window { @@ -370,6 +370,11 @@ impl Window { // N/A } + #[inline] + pub fn set_window_icon(&self, _icon: Option<::Icon>) { + // N/A + } + #[inline] pub fn get_current_monitor(&self) -> RootMonitorId { RootMonitorId{inner: MonitorId} diff --git a/src/platform/linux/mod.rs b/src/platform/linux/mod.rs index 88ef90ef..85fa550a 100644 --- a/src/platform/linux/mod.rs +++ b/src/platform/linux/mod.rs @@ -6,12 +6,20 @@ use std::ffi::CStr; use std::os::raw::*; use std::sync::Arc; +// `std::os::raw::c_void` and `libc::c_void` are NOT interchangeable! use libc; -use {CreationError, CursorState, EventsLoopClosed, MouseCursor, ControlFlow}; +use { + CreationError, + CursorState, + EventsLoopClosed, + Icon, + MouseCursor, + ControlFlow, + WindowAttributes, +}; use window::MonitorId as RootMonitorId; -use self::x11::XConnection; -use self::x11::XError; +use self::x11::{XConnection, XError}; use self::x11::ffi::XVisualInfo; pub use self::x11::XNotSupported; @@ -109,18 +117,17 @@ impl MonitorId { impl Window { #[inline] - pub fn new(events_loop: &EventsLoop, - window: &::WindowAttributes, - pl_attribs: &PlatformSpecificWindowBuilderAttributes) - -> Result - { + pub fn new( + events_loop: &EventsLoop, + attribs: WindowAttributes, + pl_attribs: PlatformSpecificWindowBuilderAttributes, + ) -> Result { match *events_loop { - EventsLoop::Wayland(ref evlp) => { - wayland::Window::new(evlp, window).map(Window::Wayland) + EventsLoop::Wayland(ref events_loop) => { + wayland::Window::new(events_loop, attribs).map(Window::Wayland) }, - - EventsLoop::X(ref el) => { - x11::Window::new(el, window, pl_attribs).map(Window::X) + EventsLoop::X(ref events_loop) => { + x11::Window::new(events_loop, attribs, pl_attribs).map(Window::X) }, } } @@ -293,6 +300,14 @@ impl Window { } } + #[inline] + pub fn set_window_icon(&self, window_icon: Option) { + match self { + &Window::X(ref w) => w.set_window_icon(window_icon), + &Window::Wayland(_) => (), + } + } + #[inline] pub fn get_current_monitor(&self) -> RootMonitorId { match self { diff --git a/src/platform/linux/wayland/window.rs b/src/platform/linux/wayland/window.rs index 4720ea17..216b8a4f 100644 --- a/src/platform/linux/wayland/window.rs +++ b/src/platform/linux/wayland/window.rs @@ -23,9 +23,8 @@ pub struct Window { } impl Window { - pub fn new(evlp: &EventsLoop, attributes: &WindowAttributes) -> Result { + pub fn new(evlp: &EventsLoop, attributes: WindowAttributes) -> Result { let (width, height) = attributes.dimensions.unwrap_or((800, 600)); - // Create the window let size = Arc::new(Mutex::new((width, height))); diff --git a/src/platform/linux/x11/mod.rs b/src/platform/linux/x11/mod.rs index 5c188687..b0422a18 100644 --- a/src/platform/linux/x11/mod.rs +++ b/src/platform/linux/x11/mod.rs @@ -1,28 +1,6 @@ #![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))] -pub use self::monitor::{MonitorId, get_available_monitors, get_primary_monitor}; -pub use self::window::{Window2, XWindow}; -pub use self::xdisplay::{XConnection, XNotSupported, XError}; - pub mod ffi; - -use platform::PlatformSpecificWindowBuilderAttributes; -use {CreationError, Event, EventsLoopClosed, WindowEvent, DeviceEvent, - KeyboardInput, ControlFlow}; -use events::ModifiersState; - -use std::{mem, ptr, slice}; -use std::sync::{Arc, Weak}; -use std::sync::atomic::{self, AtomicBool}; -use std::sync::mpsc; -use std::cell::RefCell; -use std::collections::HashMap; -use std::ffi::CStr; -use std::os::raw::*; - -use libc::{self, setlocale, LC_CTYPE}; -use parking_lot::Mutex; - mod events; mod monitor; mod window; @@ -31,6 +9,33 @@ mod dnd; mod ime; mod util; +pub use self::monitor::{MonitorId, get_available_monitors, get_primary_monitor}; +pub use self::window::{Window2, XWindow}; +pub use self::xdisplay::{XConnection, XNotSupported, XError}; + +use std::{mem, ptr, slice}; +use std::sync::{Arc, mpsc, Weak}; +use std::sync::atomic::{self, AtomicBool}; +use std::cell::RefCell; +use std::collections::HashMap; +use std::ffi::CStr; +use std::os::raw::*; + +use libc::{self, setlocale, LC_CTYPE}; +use parking_lot::Mutex; + +use { + ControlFlow, + CreationError, + DeviceEvent, + Event, + EventsLoopClosed, + KeyboardInput, + WindowAttributes, + WindowEvent, +}; +use events::ModifiersState; +use platform::PlatformSpecificWindowBuilderAttributes; use self::dnd::{Dnd, DndState}; use self::ime::{ImeReceiver, ImeSender, ImeCreationError, Ime}; @@ -1150,10 +1155,11 @@ impl ::std::ops::Deref for Window { impl Window { pub fn new( x_events_loop: &EventsLoop, - window: &::WindowAttributes, - pl_attribs: &PlatformSpecificWindowBuilderAttributes + attribs: WindowAttributes, + pl_attribs: PlatformSpecificWindowBuilderAttributes ) -> Result { - let win = Arc::new(Window2::new(&x_events_loop, window, pl_attribs)?); + let multitouch = attribs.multitouch; + let win = Arc::new(Window2::new(&x_events_loop, attribs, pl_attribs)?); x_events_loop.shared_state .borrow_mut() @@ -1166,7 +1172,7 @@ impl Window { x_events_loop.windows.lock().insert(win.id(), WindowData { config: Default::default(), - multitouch: window.multitouch, + multitouch, cursor_pos: None, }); diff --git a/src/platform/linux/x11/util/icon.rs b/src/platform/linux/x11/util/icon.rs new file mode 100644 index 00000000..7daea8e4 --- /dev/null +++ b/src/platform/linux/x11/util/icon.rs @@ -0,0 +1,34 @@ +use {Icon, Pixel, PIXEL_SIZE}; +use super::*; + +impl Pixel { + pub fn to_packed_argb(&self) -> Cardinal { + let mut cardinal = 0; + assert!(CARDINAL_SIZE >= PIXEL_SIZE); + let as_bytes = &mut cardinal as *mut _ as *mut u8; + unsafe { + *as_bytes.offset(0) = self.b; + *as_bytes.offset(1) = self.g; + *as_bytes.offset(2) = self.r; + *as_bytes.offset(3) = self.a; + } + cardinal + } +} + +impl Icon { + pub fn to_cardinals(&self) -> Vec { + assert_eq!(self.rgba.len() % PIXEL_SIZE, 0); + let pixel_count = self.rgba.len() / PIXEL_SIZE; + assert_eq!(pixel_count, (self.width * self.height) as usize); + let mut data = Vec::with_capacity(pixel_count); + data.push(self.width as Cardinal); + data.push(self.height as Cardinal); + let pixels = self.rgba.as_ptr() as *const Pixel; + for pixel_index in 0..pixel_count { + let pixel = unsafe { &*pixels.offset(pixel_index as isize) }; + data.push(pixel.to_packed_argb()); + } + data + } +} diff --git a/src/platform/linux/x11/util/mod.rs b/src/platform/linux/x11/util/mod.rs index 5ac95d52..790ecb0e 100644 --- a/src/platform/linux/x11/util/mod.rs +++ b/src/platform/linux/x11/util/mod.rs @@ -4,6 +4,7 @@ mod atom; mod geometry; mod hint; +mod icon; mod input; mod window_property; mod wm; @@ -11,6 +12,7 @@ mod wm; pub use self::atom::*; pub use self::geometry::*; pub use self::hint::*; +pub use self::icon::*; pub use self::input::*; pub use self::window_property::*; pub use self::wm::*; diff --git a/src/platform/linux/x11/util/window_property.rs b/src/platform/linux/x11/util/window_property.rs index 0b33d03e..d87be219 100644 --- a/src/platform/linux/x11/util/window_property.rs +++ b/src/platform/linux/x11/util/window_property.rs @@ -3,6 +3,9 @@ use std::fmt::Debug; use super::*; +pub type Cardinal = c_long; +pub const CARDINAL_SIZE: usize = mem::size_of::(); + #[derive(Debug, Clone)] pub enum GetPropertyError { XError(XError), diff --git a/src/platform/linux/x11/window.rs b/src/platform/linux/x11/window.rs index 5f83b90e..a41e54d3 100644 --- a/src/platform/linux/x11/window.rs +++ b/src/platform/linux/x11/window.rs @@ -1,24 +1,19 @@ -use MouseCursor; -use CreationError; -use CreationError::OsError; -use libc; +use std::{cmp, mem}; use std::borrow::Borrow; -use std::{mem, cmp}; -use std::sync::Arc; -use std::os::raw::*; use std::ffi::CString; +use std::os::raw::*; +use std::sync::Arc; +use libc; use parking_lot::Mutex; -use CursorState; -use WindowAttributes; -use platform::PlatformSpecificWindowBuilderAttributes; - +use {CursorState, Icon, MouseCursor, WindowAttributes}; +use CreationError::{self, OsError}; use platform::MonitorId as PlatformMonitorId; +use platform::PlatformSpecificWindowBuilderAttributes; use platform::x11::MonitorId as X11MonitorId; -use window::MonitorId as RootMonitorId; - use platform::x11::monitor::get_available_monitors; +use window::MonitorId as RootMonitorId; use super::{ffi, util, XConnection, XError, WindowId, EventsLoop}; @@ -60,8 +55,8 @@ pub struct Window2 { impl Window2 { pub fn new( ctx: &EventsLoop, - window_attrs: &WindowAttributes, - pl_attribs: &PlatformSpecificWindowBuilderAttributes, + window_attrs: WindowAttributes, + pl_attribs: PlatformSpecificWindowBuilderAttributes, ) -> Result { let xconn = &ctx.display; @@ -241,6 +236,11 @@ impl Window2 { }//.queue(); } + // Set window icons + if let Some(icon) = window_attrs.window_icon { + window.set_icon_inner(icon).queue(); + } + // Opt into handling window close unsafe { (xconn.xlib.XSetWMProtocols)( @@ -522,6 +522,53 @@ impl Window2 { self.invalidate_cached_frame_extents(); } + fn set_icon_inner(&self, icon: Icon) -> util::Flusher { + let xconn = &self.x.display; + + let icon_atom = unsafe { util::get_atom(xconn, b"_NET_WM_ICON\0") } + .expect("Failed to call XInternAtom (_NET_WM_ICON)"); + + let data = icon.to_cardinals(); + unsafe { + util::change_property( + xconn, + self.x.window, + icon_atom, + ffi::XA_CARDINAL, + util::Format::Long, + util::PropMode::Replace, + data.as_slice(), + ) + } + } + + fn unset_icon_inner(&self) -> util::Flusher { + let xconn = &self.x.display; + + let icon_atom = unsafe { util::get_atom(xconn, b"_NET_WM_ICON\0") } + .expect("Failed to call XInternAtom (_NET_WM_ICON)"); + + let empty_data: [util::Cardinal; 0] = []; + unsafe { + util::change_property( + xconn, + self.x.window, + icon_atom, + ffi::XA_CARDINAL, + util::Format::Long, + util::PropMode::Replace, + &empty_data, + ) + } + } + + pub fn set_window_icon(&self, icon: Option) { + match icon { + Some(icon) => self.set_icon_inner(icon), + None => self.unset_icon_inner(), + }.flush().expect("Failed to set icons"); + } + pub fn show(&self) { unsafe { (self.x.display.xlib.XMapRaised)(self.x.display.display, self.x.window); diff --git a/src/platform/macos/mod.rs b/src/platform/macos/mod.rs index db35c9b4..f957b3ac 100644 --- a/src/platform/macos/mod.rs +++ b/src/platform/macos/mod.rs @@ -25,8 +25,8 @@ impl ::std::ops::Deref for Window { impl Window { pub fn new(events_loop: &EventsLoop, - attributes: &::WindowAttributes, - pl_attribs: &PlatformSpecificWindowBuilderAttributes) -> Result + attributes: ::WindowAttributes, + pl_attribs: PlatformSpecificWindowBuilderAttributes) -> Result { let weak_shared = Arc::downgrade(&events_loop.shared); let window = Arc::new(try!(Window2::new(weak_shared, attributes, pl_attribs))); diff --git a/src/platform/macos/window.rs b/src/platform/macos/window.rs index c9ff4917..4163cf19 100644 --- a/src/platform/macos/window.rs +++ b/src/platform/macos/window.rs @@ -559,8 +559,8 @@ impl WindowExt for Window2 { impl Window2 { pub fn new( shared: Weak, - win_attribs: &WindowAttributes, - pl_attribs: &PlatformSpecificWindowBuilderAttributes, + win_attribs: WindowAttributes, + pl_attribs: PlatformSpecificWindowBuilderAttributes, ) -> Result { unsafe { if !msg_send![cocoa::base::class("NSThread"), isMainThread] { @@ -579,7 +579,7 @@ impl Window2 { }, }; - let window = match Window2::create_window(win_attribs, pl_attribs) + let window = match Window2::create_window(&win_attribs, &pl_attribs) { Some(window) => window, None => { @@ -700,8 +700,8 @@ impl Window2 { fn create_window( attrs: &WindowAttributes, - pl_attrs: &PlatformSpecificWindowBuilderAttributes) - -> Option { + pl_attrs: &PlatformSpecificWindowBuilderAttributes + ) -> Option { unsafe { let autoreleasepool = NSAutoreleasePool::new(nil); let screen = match attrs.fullscreen { @@ -1072,6 +1072,16 @@ impl Window2 { } } + #[inline] + pub fn set_window_icon(&self, _icon: Option<::Icon>) { + // macOS doesn't have window icons. Though, there is `setRepresentedFilename`, but that's + // semantically distinct and should only be used when the window is in some representing a + // specific file/directory. For instance, Terminal.app uses this for the CWD. Anyway, that + // should eventually be implemented as `WindowBuilderExt::with_represented_file` or + // something, and doesn't have anything to do with this. + // https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/WinPanel/Tasks/SettingWindowTitle.html + } + #[inline] pub fn get_current_monitor(&self) -> RootMonitorId { unsafe { diff --git a/src/platform/windows/events_loop.rs b/src/platform/windows/events_loop.rs index 91af29f1..23652252 100644 --- a/src/platform/windows/events_loop.rs +++ b/src/platform/windows/events_loop.rs @@ -279,8 +279,9 @@ impl EventsLoopProxy { /// Executes a function in the background thread. /// - /// Note that we use a FnMut instead of a FnOnce because we're too lazy to create an equivalent - /// to the unstable FnBox. + /// Note that we use FnMut instead of FnOnce because boxing FnOnce won't work on stable Rust + /// until 2030 when the design of Box is finally complete. + /// https://github.com/rust-lang/rust/issues/28796 /// /// The `Inserted` can be used to inject a `WindowState` for the callback to use. The state is /// removed automatically if the callback receives a `WM_CLOSE` message for the window. @@ -302,10 +303,7 @@ impl EventsLoopProxy { ); // PostThreadMessage can only fail if the thread ID is invalid (which shouldn't happen // as the events loop is still alive) or if the queue is full. - assert!( - res != 0, - "PostThreadMessage failed ; is the messages queue full?" - ); + assert!(res != 0, "PostThreadMessage failed; is the messages queue full?"); } } } diff --git a/src/platform/windows/icon.rs b/src/platform/windows/icon.rs new file mode 100644 index 00000000..b21131b5 --- /dev/null +++ b/src/platform/windows/icon.rs @@ -0,0 +1,112 @@ +use std::{self, mem, ptr}; +use std::os::windows::ffi::OsStrExt; +use std::path::Path; + +use winapi::ctypes::{c_int, wchar_t}; +use winapi::shared::minwindef::{BYTE, LPARAM, WPARAM}; +use winapi::shared::windef::{HICON, HWND}; +use winapi::um::winuser; + +use {Pixel, PIXEL_SIZE, Icon}; +use platform::platform::util; + +impl Pixel { + fn to_bgra(&mut self) { + mem::swap(&mut self.r, &mut self.b); + } +} + +#[derive(Debug)] +pub enum IconType { + Small = winuser::ICON_SMALL as isize, + Big = winuser::ICON_BIG as isize, +} + +#[derive(Debug)] +pub struct WinIcon { + pub handle: HICON, +} + +impl WinIcon { + #[allow(dead_code)] + pub fn from_path>(path: P) -> Result { + let wide_path: Vec = path.as_ref().as_os_str().encode_wide().collect(); + let handle = unsafe { + winuser::LoadImageW( + ptr::null_mut(), + wide_path.as_ptr() as *const wchar_t, + winuser::IMAGE_ICON, + 0, // 0 indicates that we want to use the actual width + 0, // and height + winuser::LR_LOADFROMFILE, + ) as HICON + }; + if !handle.is_null() { + Ok(WinIcon { handle }) + } else { + Err(util::WinError::from_last_error()) + } + } + + pub fn from_icon(icon: Icon) -> Result { + Self::from_rgba(icon.rgba, icon.width, icon.height) + } + + pub fn from_rgba(mut rgba: Vec, width: u32, height: u32) -> Result { + assert_eq!(rgba.len() % PIXEL_SIZE, 0); + let pixel_count = rgba.len() / PIXEL_SIZE; + assert_eq!(pixel_count, (width * height) as usize); + let mut and_mask = Vec::with_capacity(pixel_count); + let pixels = rgba.as_mut_ptr() as *mut Pixel; // how not to write idiomatic Rust + for pixel_index in 0..pixel_count { + let pixel = unsafe { &mut *pixels.offset(pixel_index as isize) }; + and_mask.push(pixel.a.wrapping_sub(std::u8::MAX)); // invert alpha channel + pixel.to_bgra(); + } + assert_eq!(and_mask.len(), pixel_count); + let handle = unsafe { + winuser::CreateIcon( + ptr::null_mut(), + width as c_int, + height as c_int, + 1, + (PIXEL_SIZE * 8) as BYTE, + and_mask.as_ptr() as *const BYTE, + rgba.as_ptr() as *const BYTE, + ) as HICON + }; + if !handle.is_null() { + Ok(WinIcon { handle }) + } else { + Err(util::WinError::from_last_error()) + } + } + + pub fn set_for_window(&self, hwnd: HWND, icon_type: IconType) { + unsafe { + winuser::SendMessageW( + hwnd, + winuser::WM_SETICON, + icon_type as WPARAM, + self.handle as LPARAM, + ); + } + } +} + +impl Drop for WinIcon { + fn drop(&mut self) { + unsafe { winuser::DestroyIcon(self.handle) }; + } +} + +pub fn unset_for_window(hwnd: HWND, icon_type: IconType) { + unsafe { + winuser::SendMessageW( + hwnd, + winuser::WM_SETICON, + icon_type as WPARAM, + 0 as LPARAM, + ); + } +} diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 0ea6ab88..44fe89ea 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -10,6 +10,7 @@ pub use self::window::Window; #[derive(Clone, Default)] pub struct PlatformSpecificWindowBuilderAttributes { pub parent: Option, + pub taskbar_icon: Option<::Icon>, } unsafe impl Send for PlatformSpecificWindowBuilderAttributes {} @@ -45,6 +46,7 @@ unsafe impl Sync for WindowId {} mod event; mod events_loop; +mod icon; mod monitor; mod raw_input; mod util; diff --git a/src/platform/windows/util.rs b/src/platform/windows/util.rs index 3377a542..4037a116 100644 --- a/src/platform/windows/util.rs +++ b/src/platform/windows/util.rs @@ -1,6 +1,23 @@ +use std::{self, mem, ptr}; use std::ops::BitAnd; use winapi::ctypes::wchar_t; +use winapi::shared::minwindef::DWORD; +use winapi::um::errhandlingapi::GetLastError; +use winapi::um::winbase::{ + FormatMessageW, + FORMAT_MESSAGE_ALLOCATE_BUFFER, + FORMAT_MESSAGE_FROM_SYSTEM, + FORMAT_MESSAGE_IGNORE_INSERTS, + lstrlenW, + LocalFree, +}; +use winapi::um::winnt::{ + LPCWSTR, + MAKELANGID, + LANG_NEUTRAL, + SUBLANG_DEFAULT, +}; pub fn has_flag(bitset: T, flag: T) -> bool where T: @@ -14,3 +31,42 @@ pub fn wchar_to_string(wchar: &[wchar_t]) -> String { .trim_right_matches(0 as char) .to_string() } + +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct WinError(Option); + +impl WinError { + pub fn from_last_error() -> Self { + WinError(unsafe { get_last_error() }) + } +} + +pub unsafe fn get_last_error() -> Option { + let err = GetLastError(); + if err != 0 { + let buf_addr: LPCWSTR = { + let mut buf_addr: LPCWSTR = mem::uninitialized(); + FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_IGNORE_INSERTS, + ptr::null(), + err, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) as DWORD, + // This is a pointer to a pointer + &mut buf_addr as *mut LPCWSTR as *mut _, + 0, + ptr::null_mut(), + ); + buf_addr + }; + if !buf_addr.is_null() { + let buf_len = lstrlenW(buf_addr) as usize; + let buf_slice = std::slice::from_raw_parts(buf_addr, buf_len); + let string = wchar_to_string(buf_slice); + LocalFree(buf_addr as *mut _); + return Some(string); + } + } + None +} diff --git a/src/platform/windows/window.rs b/src/platform/windows/window.rs index 2e71f88b..8afa5394 100644 --- a/src/platform/windows/window.rs +++ b/src/platform/windows/window.rs @@ -1,35 +1,31 @@ #![cfg(target_os = "windows")] +use std::cell::Cell; use std::ffi::OsStr; -use std::io; -use std::mem; +use std::{io, mem, ptr}; use std::os::raw; use std::os::windows::ffi::OsStrExt; -use std::ptr; -use std::sync::Arc; -use std::sync::Mutex; +use std::sync::{Arc, Mutex}; use std::sync::mpsc::channel; -use std::cell::Cell; -use platform::platform::events_loop::{self, DESTROY_MSG_ID}; -use platform::platform::EventsLoop; -use platform::platform::PlatformSpecificWindowBuilderAttributes; -use platform::platform::raw_input::register_all_mice_and_keyboards_for_raw_input; -use platform::platform::WindowId; +use winapi::shared::minwindef::{BOOL, DWORD, UINT}; +use winapi::shared::windef::{HDC, HWND, POINT, RECT}; +use winapi::um::{combaseapi, dwmapi, libloaderapi, processthreadsapi, winuser}; +use winapi::um::objbase::{COINIT_MULTITHREADED}; +use winapi::um::unknwnbase::{IUnknown, IUnknownVtbl}; +use winapi::um::winnt::{HRESULT, LONG, LPCWSTR}; use CreationError; use CursorState; +use Icon; +use MonitorId as RootMonitorId; use MouseCursor; use WindowAttributes; -use MonitorId as RootMonitorId; -use winapi::shared::minwindef::{UINT, DWORD, BOOL}; -use winapi::shared::windef::{HWND, HDC, RECT, POINT}; -use winapi::um::{winuser, dwmapi, libloaderapi, processthreadsapi}; -use winapi::um::winnt::{LPCWSTR, LONG, HRESULT}; -use winapi::um::combaseapi; -use winapi::um::objbase::{COINIT_MULTITHREADED}; -use winapi::um::unknwnbase::{IUnknown, IUnknownVtbl}; +use platform::platform::{EventsLoop, PlatformSpecificWindowBuilderAttributes, WindowId}; +use platform::platform::events_loop::{self, DESTROY_MSG_ID}; +use platform::platform::icon::{self, IconType, WinIcon}; +use platform::platform::raw_input::register_all_mice_and_keyboards_for_raw_input; /// The Win32 implementation of the main `Window` object. pub struct Window { @@ -39,6 +35,9 @@ pub struct Window { /// The current window state. window_state: Arc>, + window_icon: Cell>, + taskbar_icon: Cell>, + // The events loop proxy. events_loop_proxy: events_loop::EventsLoopProxy, } @@ -72,19 +71,19 @@ unsafe fn unjust_window_rect(prc: &mut RECT, style: DWORD, ex_style: DWORD) -> B } impl Window { - pub fn new(events_loop: &EventsLoop, w_attr: &WindowAttributes, - pl_attr: &PlatformSpecificWindowBuilderAttributes) -> Result - { - let mut w_attr = Some(w_attr.clone()); - let mut pl_attr = Some(pl_attr.clone()); - + pub fn new( + events_loop: &EventsLoop, + w_attr: WindowAttributes, + pl_attr: PlatformSpecificWindowBuilderAttributes, + ) -> Result { let (tx, rx) = channel(); let proxy = events_loop.create_proxy(); events_loop.execute_in_thread(move |inserter| { // We dispatch an `init` function because of code style. - let win = unsafe { init(w_attr.take().unwrap(), pl_attr.take().unwrap(), inserter, proxy.clone()) }; + // First person to remove the need for cloning here gets a cookie! + let win = unsafe { init(w_attr.clone(), pl_attr.clone(), inserter, proxy.clone()) }; let _ = tx.send(win); }); @@ -92,10 +91,11 @@ impl Window { } pub fn set_title(&self, text: &str) { + let text = OsStr::new(text) + .encode_wide() + .chain(Some(0).into_iter()) + .collect::>(); unsafe { - let text = OsStr::new(text).encode_wide().chain(Some(0).into_iter()) - .collect::>(); - winuser::SetWindowTextW(self.window.0, text.as_ptr() as LPCWSTR); } } @@ -610,6 +610,32 @@ impl Window { inner: EventsLoop::get_current_monitor(self.window.0), } } + + #[inline] + pub fn set_window_icon(&self, mut window_icon: Option) { + let window_icon = window_icon + .take() + .map(|icon| WinIcon::from_icon(icon).expect("Failed to create `ICON_SMALL`")); + if let Some(ref window_icon) = window_icon { + window_icon.set_for_window(self.window.0, IconType::Small); + } else { + icon::unset_for_window(self.window.0, IconType::Small); + } + self.window_icon.replace(window_icon); + } + + #[inline] + pub fn set_taskbar_icon(&self, mut taskbar_icon: Option) { + let taskbar_icon = taskbar_icon + .take() + .map(|icon| WinIcon::from_icon(icon).expect("Failed to create `ICON_BIG`")); + if let Some(ref taskbar_icon) = taskbar_icon { + taskbar_icon.set_for_window(self.window.0, IconType::Big); + } else { + icon::unset_for_window(self.window.0, IconType::Big); + } + self.taskbar_icon.replace(taskbar_icon); + } } impl Drop for Window { @@ -642,13 +668,44 @@ pub unsafe fn adjust_size( (rect.right - rect.left, rect.bottom - rect.top) } -unsafe fn init(window: WindowAttributes, pl_attribs: PlatformSpecificWindowBuilderAttributes, - inserter: events_loop::Inserter, events_loop_proxy: events_loop::EventsLoopProxy) -> Result { - let title = OsStr::new(&window.title).encode_wide().chain(Some(0).into_iter()) +unsafe fn init( + mut window: WindowAttributes, + mut pl_attribs: PlatformSpecificWindowBuilderAttributes, + inserter: events_loop::Inserter, + events_loop_proxy: events_loop::EventsLoopProxy, +) -> Result { + let title = OsStr::new(&window.title) + .encode_wide() + .chain(Some(0).into_iter()) .collect::>(); + let window_icon = { + let icon = window.window_icon + .take() + .map(WinIcon::from_icon); + if icon.is_some() { + Some(icon.unwrap().map_err(|err| { + CreationError::OsError(format!("Failed to create `ICON_SMALL`: {:?}", err)) + })?) + } else { + None + } + }; + let taskbar_icon = { + let icon = pl_attribs.taskbar_icon + .take() + .map(WinIcon::from_icon); + if icon.is_some() { + Some(icon.unwrap().map_err(|err| { + CreationError::OsError(format!("Failed to create `ICON_BIG`: {:?}", err)) + })?) + } else { + None + } + }; + // registering the window class - let class_name = register_window_class(); + let class_name = register_window_class(&window_icon, &taskbar_icon); // building a RECT object with coordinates let mut rect = RECT { @@ -738,17 +795,21 @@ unsafe fn init(window: WindowAttributes, pl_attribs: PlatformSpecificWindowBuild } } + let (transparent, maximized, fullscreen) = ( + window.transparent.clone(), window.maximized.clone(), window.fullscreen.clone() + ); + // Creating a mutex to track the current window state let window_state = Arc::new(Mutex::new(events_loop::WindowState { cursor: winuser::IDC_ARROW, // use arrow by default cursor_state: CursorState::Normal, - attributes: window.clone(), + attributes: window, mouse_in_window: false, saved_window_info: None, })); // making the window transparent - if window.transparent { + if transparent { let bb = dwmapi::DWM_BLURBEHIND { dwFlags: 0x1, // FIXME: DWM_BB_ENABLE; fEnable: 1, @@ -762,12 +823,14 @@ unsafe fn init(window: WindowAttributes, pl_attribs: PlatformSpecificWindowBuild let win = Window { window: real_window, window_state: window_state, - events_loop_proxy + window_icon: Cell::new(window_icon), + taskbar_icon: Cell::new(taskbar_icon), + events_loop_proxy, }; - win.set_maximized(window.maximized); - if let Some(_) = window.fullscreen { - win.set_fullscreen(window.fullscreen); + win.set_maximized(maximized); + if let Some(_) = fullscreen { + win.set_fullscreen(fullscreen); force_window_active(win.window.0); } @@ -776,9 +839,23 @@ unsafe fn init(window: WindowAttributes, pl_attribs: PlatformSpecificWindowBuild Ok(win) } -unsafe fn register_window_class() -> Vec { - let class_name = OsStr::new("Window Class").encode_wide().chain(Some(0).into_iter()) - .collect::>(); +unsafe fn register_window_class( + window_icon: &Option, + taskbar_icon: &Option, +) -> Vec { + let class_name: Vec<_> = OsStr::new("Window Class") + .encode_wide() + .chain(Some(0).into_iter()) + .collect(); + + let h_icon = taskbar_icon + .as_ref() + .map(|icon| icon.handle) + .unwrap_or(ptr::null_mut()); + let h_icon_small = window_icon + .as_ref() + .map(|icon| icon.handle) + .unwrap_or(ptr::null_mut()); let class = winuser::WNDCLASSEXW { cbSize: mem::size_of::() as UINT, @@ -787,12 +864,12 @@ unsafe fn register_window_class() -> Vec { cbClsExtra: 0, cbWndExtra: 0, hInstance: libloaderapi::GetModuleHandleW(ptr::null()), - hIcon: ptr::null_mut(), - hCursor: ptr::null_mut(), // must be null in order for cursor state to work properly + hIcon: h_icon, + hCursor: ptr::null_mut(), // must be null in order for cursor state to work properly hbrBackground: ptr::null_mut(), lpszMenuName: ptr::null(), lpszClassName: class_name.as_ptr(), - hIconSm: ptr::null_mut(), + hIconSm: h_icon_small, }; // We ignore errors because registering the same window class twice would trigger diff --git a/src/window.rs b/src/window.rs index 1d5ce02e..1da2f969 100644 --- a/src/window.rs +++ b/src/window.rs @@ -3,6 +3,7 @@ use std::collections::vec_deque::IntoIter as VecDequeIter; use CreationError; use CursorState; use EventsLoop; +use Icon; use MouseCursor; use Window; use WindowBuilder; @@ -91,6 +92,24 @@ impl WindowBuilder { self } + /// Sets the window icon. On Windows and X11, this is typically the small icon in the top-left + /// corner of the titlebar. + /// + /// ## Platform-specific + /// + /// This only has an effect on Windows and X11. + /// + /// On Windows, this sets `ICON_SMALL`. The base size for a window icon is 16x16, but it's + /// recommended to account for screen scaling and pick a multiple of that, i.e. 32x32. + /// + /// X11 has no universal guidelines for icon sizes, so you're at the whims of the WM. That + /// said, it's usually in the same ballpark as on Windows. + #[inline] + pub fn with_window_icon(mut self, window_icon: Option) -> WindowBuilder { + self.window.window_icon = window_icon; + self + } + /// Enables multitouch. #[inline] pub fn with_multitouch(mut self) -> WindowBuilder { @@ -103,22 +122,22 @@ impl WindowBuilder { /// Error should be very rare and only occur in case of permission denied, incompatible system, /// out of memory, etc. pub fn build(mut self, events_loop: &EventsLoop) -> Result { - // resizing the window to the dimensions of the monitor when fullscreen - if self.window.dimensions.is_none() { + self.window.dimensions = Some(self.window.dimensions.unwrap_or_else(|| { if let Some(ref monitor) = self.window.fullscreen { - self.window.dimensions = Some(monitor.get_dimensions()); + // resizing the window to the dimensions of the monitor when fullscreen + monitor.get_dimensions() + } else { + // default dimensions + (1024, 768) } - } - - // default dimensions - if self.window.dimensions.is_none() { - self.window.dimensions = Some((1024, 768)); - } + })); // building - let w = try!(platform::Window::new(&events_loop.events_loop, &self.window, &self.platform_specific)); - - Ok(Window { window: w }) + platform::Window::new( + &events_loop.events_loop, + self.window, + self.platform_specific, + ).map(|window| Window { window }) } } @@ -344,6 +363,19 @@ impl Window { self.window.set_decorations(decorations) } + /// Sets the window icon. On Windows and X11, this is typically the small icon in the top-left + /// corner of the titlebar. + /// + /// For more usage notes, see `WindowBuilder::with_window_icon`. + /// + /// ## Platform-specific + /// + /// This only has an effect on Windows and X11. + #[inline] + pub fn set_window_icon(&self, window_icon: Option) { + self.window.set_window_icon(window_icon) + } + /// Returns the monitor on which the window currently resides pub fn get_current_monitor(&self) -> MonitorId { self.window.get_current_monitor()