diff --git a/Cargo.toml b/Cargo.toml index 86c6be6..0cd0fb1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,58 @@ [package] name = "minifb" -version = "0.1.0" +version = "0.2.0" authors = ["Daniel Collin "] +description = "Cross-platform window setup for bitmap rendering" +keywords = ["windowing", "framebuffer"] +repository = "https://github.com/emoon/rust_minifb" +documentation = "http://prodbg.com/minifb/minifb/index.html" build = "build.rs" [build-dependencies] gcc = "0.3.19" [dependencies] -libc = "0.1.10" +libc = "0.2" +time = "0.1.34" +[target.x86_64-pc-windows-msvc.dependencies] +user32-sys = "0.1.2" +winapi = "0.2.4" +kernel32-sys = "0.1.4" +gdi32-sys = "0.1.1" + +[target.x86_64-pc-windows-gnu.dependencies] +user32-sys = "0.1.2" +winapi = "0.2.4" +kernel32-sys = "0.1.4" +gdi32-sys = "0.1.1" + +[target.i686-pc-windows-msvc.dependencies] +user32-sys = "0.1.2" +winapi = "0.2.4" +kernel32-sys = "0.1.4" +gdi32-sys = "0.1.1" + +[target.i686-pc-windows-gnu.dependencies] +user32-sys = "0.1.2" +winapi = "0.2.4" +kernel32-sys = "0.1.4" +gdi32-sys = "0.1.1" + +[target.i686-unknown-linux-gnu.dependencies] +x11-dl = "~2.2" + +[target.x86_64-unknown-linux-gnu.dependencies] +x11-dl = "~2.2" + +[target.arm-unknown-linux-gnueabihf.dependencies] +x11-dl = "~2.2" + +[target.aarch64-unknown-linux-gnu.dependencies] +x11-dl = "~2.2" + +[target.x86_64-unknown-dragonfly.dependencies] +x11-dl = "~2.2" + +[target.x86_64-unknown-freebsd.dependencies] +x11-dl = "~2.2" diff --git a/README.md b/README.md index f3654d0..49aa93d 100644 --- a/README.md +++ b/README.md @@ -7,32 +7,37 @@ rust_minifb (Mini FrameBuffer) is a small cross platform library written in [Rus ```rust extern crate minifb; -const WIDTH: usize = 1280; -const HEIGHT: usize = 720; +use minifb::*; + +const WIDTH: usize = 640; +const HEIGHT: usize = 360; fn main() { let mut buffer: [u32; WIDTH * HEIGHT] = [0; WIDTH * HEIGHT]; - if !(minifb::open("TestWindow", WIDTH, HEIGHT)) { - return; - } + let mut window = Window::new("Noise Test - Press ESC to exit", + WIDTH, + HEIGHT, + Scale::X1, + Vsync::No) + .unwrap(); - while minifb::update(&buffer) { + while window.is_open() && !window.is_key_down(Key::Escape) { for i in buffer.iter_mut() { - *i = ... // write something here + *i = 0; // write something more funny here! } - } - minifb::close(); + window.update(&buffer); + } } ``` Status ------ -Currently Mac, Windows and Linux has been tested which are the supported platforms for now. +Currently Windows and Mac are the current supported platforms. X11 (Linux/FreeBSD/etc) support is coming soon. -Build instructions +Build instruction ------------------ ``` @@ -40,6 +45,6 @@ cargo build cargo run --example noise ``` -This will run the [noise example](https://github.com/emoon/rust_minifb/blob/master/examples/noise.rs) which should look something like this (Mac screenshot) +This will run the [noise example](https://github.com/emoon/rust_minifb/blob/windows-rs/examples/noise.rs) which should look something like this (Mac screenshot) ![mac_screenshot](https://dl.dropboxusercontent.com/u/5205843/rust_minifb/noise_screen.png) diff --git a/build.rs b/build.rs index 308dd66..d5ed662 100644 --- a/build.rs +++ b/build.rs @@ -8,9 +8,7 @@ fn main() { &["src/native/macosx/MacMiniFB.m", "src/native/macosx/OSXWindow.m", "src/native/macosx/OSXWindowFrameView.m"]); // MacOS - } else if env.contains("windows") { - gcc::compile_library("libminifb_native.a", &["src/native/windows/WinMiniFB.c"]); // Windows - } else { + } else if env.contains("linux") { gcc::compile_library("libminifb_native.a", &["src/native/x11/X11MiniFB.c"]); // Unix } } diff --git a/examples/noise.rs b/examples/noise.rs index d1bf858..ab39f11 100644 --- a/examples/noise.rs +++ b/examples/noise.rs @@ -1,5 +1,7 @@ extern crate minifb; +use minifb::*; + const WIDTH: usize = 640; const HEIGHT: usize = 360; @@ -10,11 +12,15 @@ fn main() { let mut buffer: [u32; WIDTH * HEIGHT] = [0; WIDTH * HEIGHT]; - if !(minifb::open("Noise Test - Press ESC to exit", WIDTH, HEIGHT)) { - return; - } + let mut window = match Window::new("Noise Test - Press ESC to exit", WIDTH, HEIGHT, Scale::X2) { + Ok(win) => win, + Err(err) => { + println!("Unable to create window {}", err); + return; + } + }; - while minifb::update(&buffer) { + while window.is_open() && !window.is_key_down(Key::Escape) { for i in buffer.iter_mut() { noise = seed; noise >>= 3; @@ -26,7 +32,17 @@ fn main() { noise &= 0xFF; *i = (noise << 16) | (noise << 8) | noise; } - } - minifb::close(); + window.get_keys().map(|keys| { + for t in keys { + match t { + Key::W => println!("holding w!"), + Key::T => println!("holding t!"), + _ => (), + } + } + }); + + window.update(&buffer); + } } diff --git a/src/key_handler.rs b/src/key_handler.rs new file mode 100644 index 0000000..1f6233c --- /dev/null +++ b/src/key_handler.rs @@ -0,0 +1,123 @@ +extern crate time; + +use std::mem; +use {Key, KeyRepeat}; + +pub struct KeyHandler { + prev_time: f64, + delta_time: f32, + keys: [bool; 512], + keys_down_duration: [f32; 512], + key_repeat_delay: f32, + key_repeat_rate: f32, +} + +impl KeyHandler { + pub fn new() -> KeyHandler { + KeyHandler { + keys: [false; 512], + keys_down_duration: [-1.0; 512], + prev_time: time::precise_time_s(), + delta_time: 0.0, + key_repeat_delay: 0.250, + key_repeat_rate: 0.050, + } + } + + #[inline] + pub fn set_key_state(&mut self, key: Key, state: bool) { + self.keys[key as usize] = state; + } + + pub fn get_keys(&self) -> Option> { + let mut index: u16 = 0; + let mut keys: Vec = Vec::new(); + + for i in self.keys.iter() { + if *i { + unsafe { + keys.push(mem::transmute(index as u8)); + } + } + + index += 1; + } + + Some(keys) + } + + pub fn update(&mut self) { + let current_time = time::precise_time_s(); + let delta_time = (current_time - self.prev_time) as f32; + self.prev_time = current_time; + self.delta_time = delta_time; + + for i in 0..self.keys.len() { + if self.keys[i] { + if self.keys_down_duration[i] < 0.0 { + self.keys_down_duration[i] = 0.0; + } else { + self.keys_down_duration[i] += delta_time; + } + } else { + self.keys_down_duration[i] = -1.0; + } + } + } + + pub fn get_keys_pressed(&self, repeat: KeyRepeat) -> Option> { + let mut index: u16 = 0; + let mut keys: Vec = Vec::new(); + + for i in self.keys.iter() { + if *i { + unsafe { + if Self::key_pressed(self, index as usize, repeat) { + keys.push(mem::transmute(index as u8)); + } + } + } + + index += 1; + } + + Some(keys) + } + + #[inline] + pub fn is_key_down(&self, key: Key) -> bool { + return self.keys[key as usize]; + } + + #[inline] + pub fn set_key_repeat_delay(&mut self, delay: f32) { + self.key_repeat_delay = delay; + } + + #[inline] + pub fn set_key_repeat_rate(&mut self, rate: f32) { + self.key_repeat_rate = rate; + } + + pub fn key_pressed(&self, index: usize, repeat: KeyRepeat) -> bool { + let t = self.keys_down_duration[index]; + + if t == 0.0 { + return true; + } + + if repeat == KeyRepeat::Yes && t > self.key_repeat_delay { + let delay = self.key_repeat_delay; + let rate = self.key_repeat_rate; + if ((((t - delay) % rate) > rate * 0.5)) != (((t - delay - self.delta_time) % rate) > rate * 0.5) { + return true; + } + } + + return false; + } + + pub fn is_key_pressed(&self, key: Key, repeat: KeyRepeat) -> bool { + return Self::key_pressed(self, key as usize, repeat); + } +} diff --git a/src/lib.rs b/src/lib.rs index 63dc6a0..2eb82b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,70 +1,351 @@ +/// Scale will scale the frame buffer and the window that is being sent in when calling the update +/// function. This is useful if you for example want to display a 320 x 256 window on a screen with +/// much higher resolution which would result in that the window is very small. +#[derive(Clone, Copy)] +pub enum Scale { + /// This mode checks your current screen resolution and will caluclate the largest window size + /// that can be used within that limit and resize it. Useful if you have a small buffer to + /// display on a high resolution screen. + FitScreen, + /// 1X scale (which means leave the corrdinates sent into Window::new untouched) + X1, + /// 2X window scale (Example: 320 x 200 -> 640 x 400) + X2, + /// 4X window scale (Example: 320 x 200 -> 1280 x 800) + X4, + /// 8X window scale (Example: 320 x 200 -> 2560 x 1600) + X8, + /// 16X window scale (Example: 320 x 200 -> 5120 x 3200) + X16, + /// 32 window scale (Example: 320 x 200 -> 10240 x 6400) + X32, +} + +/// Used for is_key_pressed and get_keys_pressed() to indicated if repeat of presses is wanted +#[derive(PartialEq, Clone, Copy)] +pub enum KeyRepeat { + /// Use repeat + Yes, + /// Don't use repeat + No, +} + +/// Key is used by the get key functions to check if some keys on the keyboard has been pressed +#[derive(PartialEq, Clone, Copy)] +pub enum Key { + Key0 = 0, + Key1 = 1, + Key2 = 2, + Key3 = 3, + Key4 = 4, + Key5 = 5, + Key6 = 6, + Key7 = 7, + Key8 = 8, + Key9 = 9, + + A = 10, + B = 11, + C = 12, + D = 13, + E = 14, + F = 15, + G = 16, + H = 17, + I = 18, + J = 19, + K = 20, + L = 21, + M = 22, + N = 23, + O = 24, + P = 25, + Q = 26, + R = 27, + S = 28, + T = 29, + U = 30, + V = 31, + W = 32, + X = 33, + Y = 34, + Z = 35, + + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + F13, + F14, + F15, + + Down, + Left, + Right, + Up, + Apostrophe, + Backquote, + + Backslash, + Comma, + Equal, + LeftBracket, + Minus, + Period, + RightBracket, + Semicolon, + + Slash, + Backspace, + Delete, + End, + Enter, + + Escape, + + Home, + Insert, + Menu, + + PageDown, + PageUp, + + Pause, + Space, + Tab, + NumLock, + CapsLock, + ScrollLock, + LeftShift, + RightShift, + LeftCtrl, + RightCtrl, + + NumPad0, + NumPad1, + NumPad2, + NumPad3, + NumPad4, + NumPad5, + NumPad6, + NumPad7, + NumPad8, + NumPad9, + NumPadDot, + NumPadSlash, + NumPadAsterisk, + NumPadMinus, + NumPadPlus, + NumPadEnter, + + LeftAlt, + RightAlt, + + LeftSuper, + RightSuper, + + /// Used when an Unknown key has been pressed + Unknown, + + Count = 107, +} + extern crate libc; -use std::ffi::CString; -use std::mem::transmute; -use libc::{c_char, c_int, c_void}; + +pub mod os; +mod key_handler; #[cfg(target_os = "macos")] -#[link(name = "Cocoa", kind = "framework")] -extern { - fn mfb_open(name: *const c_char, width: c_int, height: c_int) -> c_int; - fn mfb_update(buffer: *mut c_void) -> c_int; - fn mfb_close(); -} - +use self::os::macos as imp; #[cfg(target_os = "windows")] -#[link(name = "gdi32")] -extern { - fn mfb_open(name: *const c_char, width: c_int, height: c_int) -> c_int; - fn mfb_update(buffer: *mut c_void) -> c_int; - fn mfb_close(); -} - -#[cfg(target_os = "linux")] -#[link(name = "X11")] -extern { - fn mfb_open(name: *const c_char, width: c_int, height: c_int) -> c_int; - fn mfb_update(buffer: *mut c_void) -> c_int; - fn mfb_close(); -} +use self::os::windows as imp; /// -/// Open up a window +/// Window used for displaying a 32-bit RGB buffer. Here is a small example on how to use it: +/// (without error checking +/// +/// ```ignore +/// +/// const WIDTH: usize = 640; +/// const HEIGHT: usize = 360; +/// +/// let mut buffer: [u32; WIDTH * HEIGHT] = [0; WIDTH * HEIGHT]; +/// +/// let mut window = match Window::new("Test - Press ESC to exit", WIDTH, HEIGHT, Scale::X1).unwrap() +/// +/// while window.is_open() && !window.is_key_down(Key::Escape) { +/// for i in buffer.iter_mut() { +/// *i = 0; // write something interesting here +/// } +/// window.update(&buffer); +/// } +/// ``` /// -pub fn open(name: &str, width: usize, height: usize) -> bool { - let s = CString::new(name).unwrap(); - let ret; - unsafe { - ret = mfb_open(s.as_ptr(), width as c_int, height as c_int); +pub struct Window(imp::Window); + +impl Window { + /// + /// Opens up a new window + /// + /// ```ignore + /// let mut window = match Window::new("Test", 640, 400, Scale::X1) { + /// Ok(win) => win, + /// Err(err) => { + /// println!("Unable to create window {}", err); + /// return; + /// } + ///}; + /// ``` + pub fn new(name: &str, width: usize, height: usize, scale: Scale) -> Result { + imp::Window::new(name, width, height, scale) } - match ret { - 0 => false, - _ => true, - } -} - -/// -/// Update -/// -pub fn update(buffer: &[u32]) -> bool { - let ret; - unsafe { - ret = mfb_update(transmute(buffer.as_ptr())); + /// + /// Updates the window with a 32-bit pixel buffer. Notice that the buffer needs to be at least + /// the size of the created window + /// + /// # Examples + /// + /// ```ignore + /// let mut buffer: [u32; 640 * 400] = [0; 640 * 400]; + /// + /// let mut window = match Window::new("Test", 640, 400, Scale::X1).unwrap(); + /// + /// window.update(&buffer); + /// ``` + pub fn update(&mut self, buffer: &[u32]) { + self.0.update(buffer) } - if ret < 0 { - return false; - } else { - return true; + /// + /// Checks if the window is still open. A window can be closed by the user (by for example + /// pressing the close button on the window) It's up to the user to make sure that this is + /// being checked and take action depending on the state. + /// + /// # Examples + /// + /// ```ignore + /// while window.is_open() { + /// window.update(...) + /// } + /// ``` + #[inline] + pub fn is_open(&self) -> bool { + self.0.is_open() } -} -/// -/// Close -/// -pub fn close() { - unsafe { - mfb_close(); + /// + /// Get the current keys that are down. + /// + /// # Examples + /// + /// ```ignore + /// window.get_keys().map(|keys| { + /// for t in keys { + /// match t { + /// Key::W => println!("holding w"), + /// Key::T => println!("holding t"), + /// _ => (), + /// } + /// } + /// }); + /// ``` + #[inline] + pub fn get_keys(&self) -> Option> { + self.0.get_keys() + } + + /// + /// Get the current pressed keys. Repeat can be used to control if keys should + /// be repeated if down or not. + /// + /// # Examples + /// + /// ```ignore + /// window.get_keys_pressed(KeyRepeat::No).map(|keys| { + /// for t in keys { + /// match t { + /// Key::W => println!("pressed w"), + /// Key::T => println!("pressed t"), + /// _ => (), + /// } + /// } + /// }); + /// ``` + #[inline] + pub fn get_keys_pressed(&self, repeat: KeyRepeat) -> Option> { + self.0.get_keys_pressed(repeat) + } + + /// + /// Check if a single key is down. + /// + /// # Examples + /// + /// ```ignore + /// if window.is_key_down(Key::A) { + /// println!("Key A is down"); + /// } + /// ``` + /// + #[inline] + pub fn is_key_down(&self, key: Key) -> bool { + self.0.is_key_down(key) + } + + /// + /// Check if a single key is pressed. KeyRepeat will control if the key should be repeated or + /// not while being pressed. + /// + /// # Examples + /// + /// ```ignore + /// if window.is_key_pressed(KeyRepeat::No) { + /// println!("Key A is down"); + /// } + /// ``` + /// + #[inline] + pub fn is_key_pressed(&self, key: Key, repeat: KeyRepeat) -> bool { + self.0.is_key_pressed(key, repeat) + } + + /// + /// Sets the delay for when a key is being held before it starts being repeated the default + /// value is 0.25 sec + /// + /// # Examples + /// + /// ```ignore + /// window.set_key_repeat_delay(0.5) // 0.5 sec before repeat starts + /// ``` + /// + #[inline] + pub fn set_key_repeat_delay(&mut self, delay: f32) { + self.0.set_key_repeat_delay(delay) + } + + /// + /// Sets the rate in between when the keys has passed the intital repeat_delay. The default + /// value is 0.05 sec + /// + /// # Examples + /// + /// ```ignore + /// window.set_key_repeat_rate(0.01) // 0.01 sec between keys + /// ``` + /// + #[inline] + pub fn set_key_repeat_rate(&mut self, rate: f32) { + self.0.set_key_repeat_rate(rate) } } diff --git a/src/native/macosx/MacMiniFB.m b/src/native/macosx/MacMiniFB.m index 83742d1..c761c42 100644 --- a/src/native/macosx/MacMiniFB.m +++ b/src/native/macosx/MacMiniFB.m @@ -3,83 +3,76 @@ #include #include -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +static bool s_init = false; -void* g_updateBuffer = 0; -int g_width = 0; -int g_height = 0; -static NSWindow* window_; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -int mfb_open(const char* name, int width, int height) +void* mfb_open(const char* name, int width, int height, int scale) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; - g_width = width; - g_height = height; - - [NSApplication sharedApplication]; - [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + if (!s_init) { + [NSApplication sharedApplication]; + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + s_init = true; + } unsigned int styles = NSResizableWindowMask | NSClosableWindowMask | NSTitledWindowMask; - NSRect rectangle = NSMakeRect(0, 0, width, height); - window_ = [[OSXWindow alloc] initWithContentRect:rectangle styleMask:styles backing:NSBackingStoreBuffered defer:NO]; + NSRect rectangle = NSMakeRect(0, 0, width * scale, height * scale); + OSXWindow* window = [[OSXWindow alloc] initWithContentRect:rectangle styleMask:styles backing:NSBackingStoreBuffered defer:NO]; - if (!window_) + if (!window) return 0; - [window_ setTitle:[NSString stringWithUTF8String:name]]; - [window_ setReleasedWhenClosed:NO]; - [window_ performSelectorOnMainThread:@selector(makeKeyAndOrderFront:) withObject:nil waitUntilDone:YES]; + window->draw_buffer = malloc(width * height * 4); - [window_ center]; + if (!window->draw_buffer) + return 0; + + window->width = width; + window->height = height; + window->scale = scale; + window->key_callback = 0; + + [window updateSize]; + + [window setTitle:[NSString stringWithUTF8String:name]]; + [window setReleasedWhenClosed:NO]; + [window performSelectorOnMainThread:@selector(makeKeyAndOrderFront:) withObject:nil waitUntilDone:YES]; + + [window center]; [NSApp activateIgnoringOtherApps:YES]; [pool drain]; - return 1; + return window; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -void mfb_close() +void mfb_close(void* win) { + NSWindow* window = (NSWindow*)win; + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; - if (window_) - [window_ close]; + if (window) + [window close]; [pool drain]; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -static int updateEvents() +static int update_events() { int state = 0; NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES]; - if (event) - { - switch ([event type]) - { - case NSKeyDown: - case NSKeyUp: - { - state = -1; - break; - } - - default : - { - [NSApp sendEvent:event]; - break; - } - } - } + [NSApp sendEvent:event]; [pool release]; return state; @@ -87,10 +80,41 @@ static int updateEvents() /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -int mfb_update(void* buffer) +int mfb_update(void* window, void* buffer) { - g_updateBuffer = buffer; - int state = updateEvents(); - [[window_ contentView] setNeedsDisplay:YES]; + OSXWindow* win = (OSXWindow*)window; + memcpy(win->draw_buffer, buffer, win->width * win->height * 4); + + //g_updateBuffer = buffer; + int state = update_events(); + [[win contentView] setNeedsDisplay:YES]; return state; } + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +int mfb_should_close(void* window) +{ + OSXWindow* win = (OSXWindow*)window; + return win->should_close; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +uint32_t mfb_get_screen_size() +{ + NSRect e = [[NSScreen mainScreen] frame]; + uint32_t w = (uint32_t)e.size.width; + uint32_t h = (uint32_t)e.size.height; + return (w << 16) | h; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void mfb_set_key_callback(void* window, void* rust_data, void (*key_callback)(void* user_data, int key, int state)) +{ + OSXWindow* win = (OSXWindow*)window; + win->key_callback = key_callback; + win->rust_data = rust_data; +} + diff --git a/src/native/macosx/OSXWindow.h b/src/native/macosx/OSXWindow.h index 5314f9c..48b7532 100644 --- a/src/native/macosx/OSXWindow.h +++ b/src/native/macosx/OSXWindow.h @@ -1,10 +1,15 @@ #import -// @class OSXWindowFrameView; - @interface OSXWindow : NSWindow { NSView* childContentView; + @public void (*key_callback)(void* user_data, int key, int state); + @public int width; + @public int height; + @public int scale; + @public void* draw_buffer; + @public void* rust_data; + @public bool should_close; } @end diff --git a/src/native/macosx/OSXWindow.m b/src/native/macosx/OSXWindow.m index a910e10..fb5f196 100644 --- a/src/native/macosx/OSXWindow.m +++ b/src/native/macosx/OSXWindow.m @@ -57,18 +57,88 @@ NSSize newFrameSize = [frameView bounds].size; newFrameSize.width += sizeDelta.width; newFrameSize.height += sizeDelta.height; - + [super setContentSize:newFrameSize]; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +-(void)flagsChanged:(NSEvent *)event +{ + const uint32_t flags = [event modifierFlags]; + + // Left Shift + key_callback(rust_data, 0x38, flags == 0x20102 ? 1 : 0); + + // RightShift + key_callback(rust_data, 0x3c, flags == 0x20104 ? 1 : 0); + + // Left Ctrl + key_callback(rust_data, 0x3b, flags == 0x40101 ? 1 : 0); + + // Right Ctrl + key_callback(rust_data, 0x3b, flags == 0x42101 ? 1 : 0); + + // Left Alt + key_callback(rust_data, 0x3a, flags == 0x80120 ? 1 : 0); + + // Right Super + key_callback(rust_data, 0x3d, flags == 0x80140 ? 1 : 0); + + // Left Super + key_callback(rust_data, 0x37, flags == 0x100108 ? 1 : 0); + + // Right Super + key_callback(rust_data, 0x36, flags == 0x100110 ? 1 : 0); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)keyDown:(NSEvent *)event +{ + // Cmd+Q always closes app + if ([event.characters.uppercaseString isEqualToString:@"Q"] && ([event modifierFlags] & NSCommandKeyMask)) { + [self performClose:self]; + return; + } + + if (key_callback) { + key_callback(rust_data, [event keyCode], 1); + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)keyUp:(NSEvent *)event +{ + if (key_callback) { + key_callback(rust_data, [event keyCode], 0); + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + - (void)mainWindowChanged:(NSNotification *)aNotification { } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +- (void)windowWillClose:(NSNotification *)notification +{ + should_close = true; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)windowShouldClose:(id)sender +{ + should_close = true; + return TRUE; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + - (void)setContentView:(NSView *)aView { if ([childContentView isEqualTo:aView]) @@ -79,18 +149,20 @@ NSRect bounds = [self frame]; bounds.origin = NSZeroPoint; - OSXWindowFrameView *frameView = [super contentView]; + OSXWindowFrameView* frameView = [super contentView]; if (!frameView) { frameView = [[[OSXWindowFrameView alloc] initWithFrame:bounds] autorelease]; - + frameView->width = width; + frameView->height = height; + frameView->draw_buffer = draw_buffer; + frameView->scale = scale; [super setContentView:frameView]; } if (childContentView) - { [childContentView removeFromSuperview]; - } + childContentView = aView; [childContentView setFrame:[self contentRectForFrameRect:bounds]]; [childContentView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; @@ -133,4 +205,18 @@ return NSInsetRect(windowContentRect, 0, 0); } +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)updateSize +{ + OSXWindowFrameView* frameView = [super contentView]; + if (frameView) + { + frameView->width = width; + frameView->height = height; + frameView->draw_buffer = draw_buffer; + frameView->scale = scale; + } +} + @end diff --git a/src/native/macosx/OSXWindowFrameView.h b/src/native/macosx/OSXWindowFrameView.h index 96408e9..c12bc52 100644 --- a/src/native/macosx/OSXWindowFrameView.h +++ b/src/native/macosx/OSXWindowFrameView.h @@ -2,6 +2,10 @@ @interface OSXWindowFrameView : NSView { + @public int scale; + @public int width; + @public int height; + @public void* draw_buffer; } @end diff --git a/src/native/macosx/OSXWindowFrameView.m b/src/native/macosx/OSXWindowFrameView.m index 1f01a41..09b0b65 100644 --- a/src/native/macosx/OSXWindowFrameView.m +++ b/src/native/macosx/OSXWindowFrameView.m @@ -2,10 +2,6 @@ @implementation OSXWindowFrameView -extern void* g_updateBuffer; -extern int g_width; -extern int g_height; - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - (NSRect)resizeRect @@ -27,21 +23,18 @@ extern int g_height; - (void)drawRect:(NSRect)rect { - if (!g_updateBuffer) - return; - CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort]; CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); - CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, g_updateBuffer, g_width * g_height * 4, NULL); + CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, draw_buffer, width * height * 4, NULL); - CGImageRef img = CGImageCreate(g_width, g_height, 8, 32, g_width * 4, space, kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little, + CGImageRef img = CGImageCreate(width, height, 8, 32, width * 4, space, kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little, provider, NULL, false, kCGRenderingIntentDefault); CGColorSpaceRelease(space); CGDataProviderRelease(provider); - CGContextDrawImage(context, CGRectMake(0, 0, g_width, g_height), img); + CGContextDrawImage(context, CGRectMake(0, 0, width * scale, height * scale), img); CGImageRelease(img); } diff --git a/src/os/macos/mod.rs b/src/os/macos/mod.rs new file mode 100644 index 0000000..afa6de7 --- /dev/null +++ b/src/os/macos/mod.rs @@ -0,0 +1,280 @@ +#![cfg(target_os = "macos")] + +use {Scale, Key, KeyRepeat}; +use key_handler::KeyHandler; + +use libc::{c_void, c_char, c_uchar}; +use std::ffi::{CString}; +use std::ptr; +use std::mem; + +// Table taken from GLFW and slightly modified + +static KEY_MAPPINGS: [Key; 128] = [ + /* 00 */ Key::A, + /* 01 */ Key::S, + /* 02 */ Key::D, + /* 03 */ Key::F, + /* 04 */ Key::H, + /* 05 */ Key::G, + /* 06 */ Key::Z, + /* 07 */ Key::X, + /* 08 */ Key::C, + /* 09 */ Key::V, + /* 0a */ Key::Unknown, // GraveAccent + /* 0b */ Key::B, + /* 0c */ Key::Q, + /* 0d */ Key::W, + /* 0e */ Key::E, + /* 0f */ Key::R, + /* 10 */ Key::Y, + /* 11 */ Key::T, + /* 12 */ Key::Key1, + /* 13 */ Key::Key2, + /* 14 */ Key::Key3, + /* 15 */ Key::Key4, + /* 16 */ Key::Key6, + /* 17 */ Key::Key5, + /* 18 */ Key::Equal, + /* 19 */ Key::Key9, + /* 1a */ Key::Key7, + /* 1b */ Key::Minus, + /* 1c */ Key::Key8, + /* 1d */ Key::Key0, + /* 1e */ Key::RightBracket, + /* 1f */ Key::O, + /* 20 */ Key::U, + /* 21 */ Key::LeftBracket, + /* 22 */ Key::I, + /* 23 */ Key::P, + /* 24 */ Key::Enter, + /* 25 */ Key::L, + /* 26 */ Key::J, + /* 27 */ Key::Apostrophe, + /* 28 */ Key::K, + /* 29 */ Key::Semicolon, + /* 2a */ Key::Backslash, + /* 2b */ Key::Comma, + /* 2c */ Key::Slash, + /* 2d */ Key::N, + /* 2e */ Key::M, + /* 2f */ Key::Period, + /* 30 */ Key::Tab, + /* 31 */ Key::Space, + /* 32 */ Key::Unknown, // World1 + /* 33 */ Key::Backspace, + /* 34 */ Key::Unknown, + /* 35 */ Key::Escape, + /* 36 */ Key::RightSuper, + /* 37 */ Key::LeftSuper, + /* 38 */ Key::LeftShift, + /* 39 */ Key::CapsLock, + /* 3a */ Key::LeftAlt, + /* 3b */ Key::LeftCtrl, + /* 3c */ Key::RightShift, + /* 3d */ Key::RightAlt, + /* 3e */ Key::RightCtrl, + /* 3f */ Key::Unknown, // Function + /* 40 */ Key::Unknown, // F17 + /* 41 */ Key::Unknown, // Decimal + /* 42 */ Key::Unknown, + /* 43 */ Key::Unknown, // Multiply + /* 44 */ Key::Unknown, + /* 45 */ Key::Unknown, // Add + /* 46 */ Key::Unknown, + /* 47 */ Key::NumLock, // Really KeypadClear... + /* 48 */ Key::Unknown, // VolumeUp + /* 49 */ Key::Unknown, // VolumeDown + /* 4a */ Key::Unknown, // Mute + /* 4b */ Key::Unknown, + /* 4c */ Key::Enter, + /* 4d */ Key::Unknown, + /* 4e */ Key::Unknown, // Subtrackt + /* 4f */ Key::Unknown, // F18 + /* 50 */ Key::Unknown, // F19 + /* 51 */ Key::Equal, + /* 52 */ Key::NumPad0, + /* 53 */ Key::NumPad1, + /* 54 */ Key::NumPad2, + /* 55 */ Key::NumPad3, + /* 56 */ Key::NumPad4, + /* 57 */ Key::NumPad5, + /* 58 */ Key::NumPad6, + /* 59 */ Key::NumPad7, + /* 5a */ Key::Unknown, // F20 + /* 5b */ Key::NumPad8, + /* 5c */ Key::NumPad9, + /* 5d */ Key::Unknown, + /* 5e */ Key::Unknown, + /* 5f */ Key::Unknown, + /* 60 */ Key::F5, + /* 61 */ Key::F6, + /* 62 */ Key::F7, + /* 63 */ Key::F3, + /* 64 */ Key::F8, + /* 65 */ Key::F9, + /* 66 */ Key::Unknown, + /* 67 */ Key::F11, + /* 68 */ Key::Unknown, + /* 69 */ Key::Unknown, // PrintScreen + /* 6a */ Key::Unknown, // F16 + /* 6b */ Key::F14, + /* 6c */ Key::Unknown, + /* 6d */ Key::F10, + /* 6e */ Key::Unknown, + /* 6f */ Key::F12, + /* 70 */ Key::Unknown, + /* 71 */ Key::F15, + /* 72 */ Key::Insert, /* Really Help... */ + /* 73 */ Key::Home, + /* 74 */ Key::PageUp, + /* 75 */ Key::Delete, + /* 76 */ Key::F4, + /* 77 */ Key::End, + /* 78 */ Key::F2, + /* 79 */ Key::PageDown, + /* 7a */ Key::F1, + /* 7b */ Key::Left, + /* 7c */ Key::Right, + /* 7d */ Key::Down, + /* 7e */ Key::Up, + /* 7f */ Key::Unknown, +]; + +#[link(name = "Cocoa", kind = "framework")] +extern { + fn mfb_open(name: *const c_char, width: u32, height: u32, scale: i32) -> *mut c_void; + fn mfb_close(window: *mut c_void); + fn mfb_update(window: *mut c_void, buffer: *const c_uchar); + fn mfb_set_key_callback(window: *mut c_void, target: *mut c_void, cb: unsafe extern fn(*mut c_void, i32, i32)); + fn mfb_should_close(window: *mut c_void) -> i32; + fn mfb_get_screen_size() -> u32; +} + +pub struct Window { + window_handle: *mut c_void, + key_handler: KeyHandler, +} + +unsafe extern "C" fn key_callback(window: *mut c_void, key: i32, state: i32) { + let win: *mut Window = mem::transmute(window); + + let s = state == 1; + + if key > 128 { + (*win).key_handler.set_key_state(Key::Unknown, s); + } else { + (*win).key_handler.set_key_state(KEY_MAPPINGS[key as usize], s); + } +} + +impl Window { + pub fn new(name: &str, width: usize, height: usize, scale: Scale) -> Result { + let n = match CString::new(name) { + Err(_) => { + println!("Unable to convert {} to c_string", name); + return Err("Unable to set correct name"); + } + Ok(n) => n, + }; + + unsafe { + let handle = mfb_open(n.as_ptr(), width as u32, height as u32, Self::get_scale_factor(width, height, scale)); + + if handle == ptr::null_mut() { + return Err("Unable to open Window"); + } + + Ok(Window { + window_handle: handle, + key_handler: KeyHandler::new(), + }) + } + } + + pub fn update(&mut self, buffer: &[u32]) { + self.key_handler.update(); + + unsafe { + mfb_update(self.window_handle, buffer.as_ptr() as *const u8); + mfb_set_key_callback(self.window_handle, mem::transmute(self), key_callback); + } + } + + #[inline] + pub fn get_keys(&self) -> Option> { + self.key_handler.get_keys() + } + + #[inline] + pub fn get_keys_pressed(&self, repeat: KeyRepeat) -> Option> { + self.key_handler.get_keys_pressed(repeat) + } + + #[inline] + pub fn is_key_down(&self, key: Key) -> bool { + self.key_handler.is_key_down(key) + } + + #[inline] + pub fn set_key_repeat_delay(&mut self, delay: f32) { + self.key_handler.set_key_repeat_delay(delay) + } + + #[inline] + pub fn set_key_repeat_rate(&mut self, rate: f32) { + self.key_handler.set_key_repeat_rate(rate) + } + + #[inline] + pub fn is_key_pressed(&self, key: Key, repeat: KeyRepeat) -> bool { + self.key_handler.is_key_pressed(key, repeat) + } + + #[inline] + pub fn is_open(&self) -> bool { + unsafe { mfb_should_close(self.window_handle) == 0 } + } + + unsafe fn get_scale_factor(width: usize, height: usize, scale: Scale) -> i32 { + let factor: i32 = match scale { + Scale::X1 => 1, + Scale::X2 => 2, + Scale::X4 => 4, + Scale::X8 => 8, + Scale::X16 => 16, + Scale::X32 => 32, + Scale::FitScreen => { + let wh: u32 = mfb_get_screen_size(); + let screen_x = (wh >> 16) as i32; + let screen_y = (wh & 0xffff) as i32; + + let mut scale = 1i32; + + loop { + let w = width as i32 * (scale + 1); + let h = height as i32 * (scale + 1); + + if w > screen_x || h > screen_y { + break; + } + + scale *= 2; + } + + scale + } + }; + + return factor; + } +} + +impl Drop for Window { + fn drop(&mut self) { + unsafe { + mfb_close(self.window_handle); + } + } +} + diff --git a/src/os/mod.rs b/src/os/mod.rs new file mode 100644 index 0000000..0669925 --- /dev/null +++ b/src/os/mod.rs @@ -0,0 +1,4 @@ +#[cfg(target_os = "macos")] +pub mod macos; +#[cfg(target_os = "windows")] +pub mod windows; diff --git a/src/os/windows/mod.rs b/src/os/windows/mod.rs new file mode 100644 index 0000000..876427a --- /dev/null +++ b/src/os/windows/mod.rs @@ -0,0 +1,428 @@ +#![cfg(target_os = "windows")] + +extern crate user32; +extern crate kernel32; +extern crate winapi; +extern crate gdi32; +extern crate time; + +use {Scale, Key, KeyRepeat}; + +use key_handler::KeyHandler; + +use std::ptr; +use std::os::windows::ffi::OsStrExt; +use std::ffi::OsStr; +use std::mem; + +use self::winapi::windef::HWND; +use self::winapi::windef::HDC; +use self::winapi::winuser::WS_OVERLAPPEDWINDOW; +use self::winapi::winuser::WNDCLASSW; +use self::winapi::wingdi::BITMAPINFOHEADER; +use self::winapi::wingdi::RGBQUAD; + +// Wrap this so we can have a proper numbef of bmiColors to write in +#[repr(C)] +struct BitmapInfo { + pub bmi_header: BITMAPINFOHEADER, + pub bmi_colors: [RGBQUAD; 3], +} + +fn update_key_state(window: &mut Window, wparam: u32, state: bool) { + match wparam & 0x1ff { + 0x00B => window.key_handler.set_key_state(Key::Key0, state), + 0x002 => window.key_handler.set_key_state(Key::Key1, state), + 0x003 => window.key_handler.set_key_state(Key::Key2, state), + 0x004 => window.key_handler.set_key_state(Key::Key3, state), + 0x005 => window.key_handler.set_key_state(Key::Key4, state), + 0x006 => window.key_handler.set_key_state(Key::Key5, state), + 0x007 => window.key_handler.set_key_state(Key::Key6, state), + 0x008 => window.key_handler.set_key_state(Key::Key7, state), + 0x009 => window.key_handler.set_key_state(Key::Key8, state), + 0x00A => window.key_handler.set_key_state(Key::Key9, state), + 0x01E => window.key_handler.set_key_state(Key::A, state), + 0x030 => window.key_handler.set_key_state(Key::B, state), + 0x02E => window.key_handler.set_key_state(Key::C, state), + 0x020 => window.key_handler.set_key_state(Key::D, state), + 0x012 => window.key_handler.set_key_state(Key::E, state), + 0x021 => window.key_handler.set_key_state(Key::F, state), + 0x022 => window.key_handler.set_key_state(Key::G, state), + 0x023 => window.key_handler.set_key_state(Key::H, state), + 0x017 => window.key_handler.set_key_state(Key::I, state), + 0x024 => window.key_handler.set_key_state(Key::J, state), + 0x025 => window.key_handler.set_key_state(Key::K, state), + 0x026 => window.key_handler.set_key_state(Key::L, state), + 0x032 => window.key_handler.set_key_state(Key::M, state), + 0x031 => window.key_handler.set_key_state(Key::N, state), + 0x018 => window.key_handler.set_key_state(Key::O, state), + 0x019 => window.key_handler.set_key_state(Key::P, state), + 0x010 => window.key_handler.set_key_state(Key::Q, state), + 0x013 => window.key_handler.set_key_state(Key::R, state), + 0x01F => window.key_handler.set_key_state(Key::S, state), + 0x014 => window.key_handler.set_key_state(Key::T, state), + 0x016 => window.key_handler.set_key_state(Key::U, state), + 0x02F => window.key_handler.set_key_state(Key::V, state), + 0x011 => window.key_handler.set_key_state(Key::W, state), + 0x02D => window.key_handler.set_key_state(Key::X, state), + 0x015 => window.key_handler.set_key_state(Key::Y, state), + 0x02C => window.key_handler.set_key_state(Key::Z, state), + 0x03B => window.key_handler.set_key_state(Key::F1, state), + 0x03C => window.key_handler.set_key_state(Key::F2, state), + 0x03D => window.key_handler.set_key_state(Key::F3, state), + 0x03E => window.key_handler.set_key_state(Key::F4, state), + 0x03F => window.key_handler.set_key_state(Key::F5, state), + 0x040 => window.key_handler.set_key_state(Key::F6, state), + 0x041 => window.key_handler.set_key_state(Key::F7, state), + 0x042 => window.key_handler.set_key_state(Key::F8, state), + 0x043 => window.key_handler.set_key_state(Key::F9, state), + 0x044 => window.key_handler.set_key_state(Key::F10, state), + 0x057 => window.key_handler.set_key_state(Key::F11, state), + 0x058 => window.key_handler.set_key_state(Key::F12, state), + 0x150 => window.key_handler.set_key_state(Key::Down, state), + 0x14B => window.key_handler.set_key_state(Key::Left, state), + 0x14D => window.key_handler.set_key_state(Key::Right, state), + 0x148 => window.key_handler.set_key_state(Key::Up, state), + 0x028 => window.key_handler.set_key_state(Key::Apostrophe, state), + 0x029 => window.key_handler.set_key_state(Key::Backquote, state), + 0x02B => window.key_handler.set_key_state(Key::Backslash, state), + 0x033 => window.key_handler.set_key_state(Key::Comma, state), + 0x00D => window.key_handler.set_key_state(Key::Equal, state), + 0x01A => window.key_handler.set_key_state(Key::LeftBracket, state), + 0x00C => window.key_handler.set_key_state(Key::Minus, state), + 0x034 => window.key_handler.set_key_state(Key::Period, state), + 0x01B => window.key_handler.set_key_state(Key::RightBracket, state), + 0x027 => window.key_handler.set_key_state(Key::Semicolon, state), + 0x035 => window.key_handler.set_key_state(Key::Slash, state), + 0x00E => window.key_handler.set_key_state(Key::Backspace, state), + 0x153 => window.key_handler.set_key_state(Key::Delete, state), + 0x14F => window.key_handler.set_key_state(Key::End, state), + 0x01C => window.key_handler.set_key_state(Key::Enter, state), + 0x001 => window.key_handler.set_key_state(Key::Escape, state), + 0x147 => window.key_handler.set_key_state(Key::Home, state), + 0x152 => window.key_handler.set_key_state(Key::Insert, state), + 0x15D => window.key_handler.set_key_state(Key::Menu, state), + 0x151 => window.key_handler.set_key_state(Key::PageDown, state), + 0x149 => window.key_handler.set_key_state(Key::PageUp, state), + 0x045 => window.key_handler.set_key_state(Key::Pause, state), + 0x039 => window.key_handler.set_key_state(Key::Space, state), + 0x00F => window.key_handler.set_key_state(Key::Tab, state), + 0x145 => window.key_handler.set_key_state(Key::NumLock, state), + 0x03A => window.key_handler.set_key_state(Key::CapsLock, state), + 0x046 => window.key_handler.set_key_state(Key::ScrollLock, state), + 0x02A => window.key_handler.set_key_state(Key::LeftShift, state), + 0x036 => window.key_handler.set_key_state(Key::RightShift, state), + 0x01D => window.key_handler.set_key_state(Key::LeftCtrl, state), + 0x11D => window.key_handler.set_key_state(Key::RightCtrl, state), + 0x052 => window.key_handler.set_key_state(Key::NumPad0, state), + 0x04F => window.key_handler.set_key_state(Key::NumPad1, state), + 0x050 => window.key_handler.set_key_state(Key::NumPad2, state), + 0x051 => window.key_handler.set_key_state(Key::NumPad3, state), + 0x04B => window.key_handler.set_key_state(Key::NumPad4, state), + 0x04C => window.key_handler.set_key_state(Key::NumPad5, state), + 0x04D => window.key_handler.set_key_state(Key::NumPad6, state), + 0x047 => window.key_handler.set_key_state(Key::NumPad7, state), + 0x048 => window.key_handler.set_key_state(Key::NumPad8, state), + 0x049 => window.key_handler.set_key_state(Key::NumPad9, state), + 0x053 => window.key_handler.set_key_state(Key::NumPadDot, state), + 0x135 => window.key_handler.set_key_state(Key::NumPadSlash, state), + 0x037 => window.key_handler.set_key_state(Key::NumPadAsterisk, state), + 0x04A => window.key_handler.set_key_state(Key::NumPadMinus, state), + 0x04E => window.key_handler.set_key_state(Key::NumPadPlus, state), + 0x11C => window.key_handler.set_key_state(Key::NumPadEnter, state), + _ => (), + } +} + + +unsafe extern "system" fn wnd_proc(window: winapi::HWND, + msg: winapi::UINT, + wparam: winapi::WPARAM, + lparam: winapi::LPARAM) + -> winapi::LRESULT { + // This make sure we actually don't do anything before the user data has been setup for the + // window + + let user_data = user32::GetWindowLongPtrW(window, winapi::winuser::GWLP_USERDATA); + + if user_data == 0 { + return user32::DefWindowProcW(window, msg, wparam, lparam); + } + + let mut wnd: &mut Window = mem::transmute(user_data); + + match msg { + winapi::winuser::WM_KEYDOWN => { + update_key_state(wnd, (lparam as u32) >> 16, true); + return 0; + } + + winapi::winuser::WM_CLOSE => { + wnd.is_open = false; + } + + winapi::winuser::WM_KEYUP => { + update_key_state(wnd, (lparam as u32) >> 16, false); + return 0; + } + + winapi::winuser::WM_PAINT => { + let mut bitmap_info: BitmapInfo = mem::zeroed(); + + bitmap_info.bmi_header.biSize = mem::size_of::() as u32; + bitmap_info.bmi_header.biPlanes = 1; + bitmap_info.bmi_header.biBitCount = 32; + bitmap_info.bmi_header.biCompression = winapi::wingdi::BI_BITFIELDS; + bitmap_info.bmi_header.biWidth = wnd.width; + bitmap_info.bmi_header.biHeight = -wnd.height; + bitmap_info.bmi_colors[0].rgbRed = 0xff; + bitmap_info.bmi_colors[1].rgbGreen = 0xff; + bitmap_info.bmi_colors[2].rgbBlue = 0xff; + + gdi32::StretchDIBits(wnd.dc.unwrap(), + 0, + 0, + wnd.width * wnd.scale_factor, + wnd.height * wnd.scale_factor, + 0, + 0, + wnd.width, + wnd.height, + mem::transmute(wnd.buffer.as_ptr()), + mem::transmute(&bitmap_info), + winapi::wingdi::DIB_RGB_COLORS, + winapi::wingdi::SRCCOPY); + + user32::ValidateRect(window, ptr::null_mut()); + + return 0; + } + + _ => (), + } + + return user32::DefWindowProcW(window, msg, wparam, lparam); +} + +pub enum MinifbError { + UnableToCreateWindow, +} + +fn to_wstring(str: &str) -> Vec { + let mut v: Vec = OsStr::new(str).encode_wide().chain(Some(0).into_iter()).collect(); + v.push(0u16); + v +} + +pub struct Window { + dc: Option, + window: Option, + buffer: Vec, + is_open : bool, + scale_factor: i32, + width: i32, + height: i32, + key_handler: KeyHandler, +} + +impl Window { + fn open_window(name: &str, width: usize, height: usize, scale_factor: i32) -> Option { + unsafe { + let class_name = to_wstring("minifb_window"); + let class = WNDCLASSW { + style: winapi::CS_HREDRAW | winapi::CS_VREDRAW | winapi::CS_OWNDC, + lpfnWndProc: Some(wnd_proc), + cbClsExtra: 0, + cbWndExtra: 0, + hInstance: kernel32::GetModuleHandleA(ptr::null()), + hIcon: ptr::null_mut(), + hCursor: ptr::null_mut(), + hbrBackground: ptr::null_mut(), + lpszMenuName: ptr::null(), + lpszClassName: class_name.as_ptr(), + }; + + if user32::RegisterClassW(&class) == 0 { + // ignore the "Class already exists" error for multiple windows + if kernel32::GetLastError() as u32 != 1410 { + println!("Unable to register class, error {}", kernel32::GetLastError() as u32); + return None; + } + } + + let new_width = width * scale_factor as usize; + let new_height = height * scale_factor as usize; + + let mut rect = winapi::RECT { + left: 0, + right: new_width as winapi::LONG, + top: 0, + bottom: new_height as winapi::LONG, + }; + + user32::AdjustWindowRect(&mut rect, + winapi::WS_POPUP | winapi::WS_SYSMENU | winapi::WS_CAPTION, + 0); + + rect.right -= rect.left; + rect.bottom -= rect.top; + + let window_name = to_wstring(name); + + let handle = user32::CreateWindowExW(0, + class_name.as_ptr(), + window_name.as_ptr(), + winapi::WS_OVERLAPPEDWINDOW & + !winapi::WS_MAXIMIZEBOX & + !winapi::WS_THICKFRAME, + winapi::CW_USEDEFAULT, + winapi::CW_USEDEFAULT, + rect.right, + rect.bottom, + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut()); + if handle.is_null() { + println!("Unable to create window, error {}", kernel32::GetLastError() as u32); + return None; + } + + user32::ShowWindow(handle, winapi::SW_NORMAL); + + return Some(handle); + } + } + + pub fn new(name: &str, + width: usize, + height: usize, + scale: Scale) + -> Result { + unsafe { + let scale_factor = Self::get_scale_factor(width, height, scale); + + let handle = Self::open_window(name, width, height, scale_factor); + + if handle.is_none() { + return Err("Unable to create Window"); + } + + let window = Window { + dc: Some(user32::GetDC(handle.unwrap())), + window: Some(handle.unwrap()), + buffer: Vec::new(), + key_handler: KeyHandler::new(), + is_open: true, + scale_factor: scale_factor, + width: width as i32, + height: height as i32, + }; + + Ok(window) + } + } + + #[inline] + pub fn get_keys(&self) -> Option> { + self.key_handler.get_keys() + } + + #[inline] + pub fn get_keys_pressed(&self, repeat: KeyRepeat) -> Option> { + self.key_handler.get_keys_pressed(repeat) + } + + #[inline] + pub fn is_key_down(&self, key: Key) -> bool { + self.key_handler.is_key_down(key) + } + + #[inline] + pub fn set_key_repeat_delay(&mut self, delay: f32) { + self.key_handler.set_key_repeat_delay(delay) + } + + #[inline] + pub fn set_key_repeat_rate(&mut self, rate: f32) { + self.key_handler.set_key_repeat_rate(rate) + } + + #[inline] + pub fn is_key_pressed(&self, key: Key, repeat: KeyRepeat) -> bool { + self.key_handler.is_key_pressed(key, repeat) + } + + + #[inline] + pub fn is_open(&self) -> bool { + return self.is_open + } + + pub fn update(&mut self, buffer: &[u32]) { + unsafe { + let mut msg = mem::uninitialized(); + let window = self.window.unwrap(); + + self.key_handler.update(); + + // TODO: Optimize + + self.buffer = buffer.iter().cloned().collect(); + + user32::SetWindowLongPtrW(window, winapi::winuser::GWLP_USERDATA, mem::transmute(self)); + user32::InvalidateRect(window, ptr::null_mut(), winapi::TRUE); + + while user32::PeekMessageW(&mut msg, window, 0, 0, winapi::winuser::PM_REMOVE) != 0 { + user32::TranslateMessage(&mut msg); + user32::DispatchMessageW(&mut msg); + } + } + } + + unsafe fn get_scale_factor(width: usize, height: usize, scale: Scale) -> i32 { + let factor: i32 = match scale { + Scale::X1 => 1, + Scale::X2 => 2, + Scale::X4 => 4, + Scale::X8 => 8, + Scale::X16 => 16, + Scale::X32 => 32, + Scale::FitScreen => { + let screen_x = user32::GetSystemMetrics(winapi::winuser::SM_CXSCREEN) as i32; + let screen_y = user32::GetSystemMetrics(winapi::winuser::SM_CYSCREEN) as i32; + + let mut scale = 1i32; + + loop { + let w = width as i32 * (scale + 1); + let h = height as i32 * (scale + 1); + + if w > screen_x || h > screen_y { + break; + } + + scale *= 2; + } + + scale + } + }; + + return factor; + } +} + +impl Drop for Window { + fn drop(&mut self) { + unsafe { + if self.dc.is_some() { + user32::ReleaseDC(self.window.unwrap(), self.dc.unwrap()); + } + + if self.window.is_some() { + user32::CloseWindow(self.window.unwrap()); + } + } + } +}